mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cloud Security] Vulnerability dashboard api testings (#162217)
This commit is contained in:
parent
62f17db60c
commit
084362f6b3
13 changed files with 1063 additions and 9 deletions
|
@ -61,7 +61,7 @@ export const defineGetVulnerabilitiesDashboardRoute = (router: CspRouter): void
|
|||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
statusCode: 500,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
|
||||
return {
|
||||
...xpackFunctionalConfig.getAll(),
|
||||
testFiles: [require.resolve('./telemetry/telemetry.ts')],
|
||||
testFiles: [
|
||||
require.resolve('./telemetry/telemetry.ts'),
|
||||
require.resolve('./routes/vulnerabilities_dashboard.ts'),
|
||||
],
|
||||
junit: {
|
||||
reportName: 'X-Pack Cloud Security Posture API Tests',
|
||||
},
|
||||
|
@ -36,7 +39,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
* 2. merge the updated version number change to kibana
|
||||
*/
|
||||
`--xpack.fleet.packages.0.name=cloud_security_posture`,
|
||||
`--xpack.fleet.packages.0.version=1.2.8`,
|
||||
`--xpack.fleet.packages.0.version=1.5.0`,
|
||||
// `--xpack.fleet.registryUrl=https://localhost:8080`,
|
||||
],
|
||||
},
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const vulnerabilitiesLatestMock = [
|
||||
{
|
||||
agent: {
|
||||
name: 'ip-172-31-17-52',
|
||||
id: '9e69d889-c0c0-4c3e-9dc7-01dcd7c7db10',
|
||||
ephemeral_id: '4fce6ed8-c9b3-40bb-b805-77e884cce0ef',
|
||||
type: 'cloudbeat',
|
||||
version: '8.8.0',
|
||||
},
|
||||
package: {
|
||||
path: 'snap-07b9e0d5eb5f324a1 (amazon 2 (Karoo))',
|
||||
fixed_version: '2.56.1-9.amzn2.0.6',
|
||||
name: 'glib2',
|
||||
type: 'amazon',
|
||||
version: '2.56.1-9.amzn2.0.5',
|
||||
},
|
||||
resource: {
|
||||
name: 'name-ng-1-Node',
|
||||
id: '02d62a7df23951b19',
|
||||
},
|
||||
elastic_agent: {
|
||||
id: '9e69d889-c0c0-4c3e-9dc7-01dcd7c7db10',
|
||||
version: '8.8.0',
|
||||
snapshot: false,
|
||||
},
|
||||
vulnerability: {
|
||||
severity: 'MEDIUM',
|
||||
package: {
|
||||
fixed_version: '2.56.1-9.amzn2.0.6',
|
||||
name: 'glib2',
|
||||
version: '2.56.1-9.amzn2.0.5',
|
||||
},
|
||||
description:
|
||||
'PCRE before 8.38 mishandles the [: and \\\\ substrings in character classes, which allows remote attackers to cause a denial of service (uninitialized memory read) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.',
|
||||
title:
|
||||
'pcre: uninitialized memory read triggered by malformed posix character class (8.38/22)',
|
||||
classification: 'CVSS',
|
||||
data_source: {
|
||||
ID: 'amazon',
|
||||
URL: 'https://alas.aws.amazon.com/',
|
||||
Name: 'Amazon Linux Security Center',
|
||||
},
|
||||
cwe: ['CWE-908'],
|
||||
reference: 'https://avd.aquasec.com/nvd/cve-2015-8390',
|
||||
score: {
|
||||
version: '3.1',
|
||||
base: 9.8,
|
||||
},
|
||||
report_id: 1687955586,
|
||||
scanner: {
|
||||
vendor: 'Trivy',
|
||||
version: 'v0.35.0',
|
||||
},
|
||||
id: 'CVE-2015-8390',
|
||||
enumeration: 'CVE',
|
||||
cvss: {
|
||||
redhat: {
|
||||
V2Vector: 'AV:N/AC:M/Au:N/C:N/I:N/A:P',
|
||||
V2Score: 4.3,
|
||||
},
|
||||
nvd: {
|
||||
V3Vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H',
|
||||
V2Vector: 'AV:N/AC:L/Au:N/C:P/I:P/A:P',
|
||||
V3Score: 9.8,
|
||||
V2Score: 7.5,
|
||||
},
|
||||
},
|
||||
class: 'os-pkgs',
|
||||
published_date: '2015-12-02T01:59:00Z',
|
||||
},
|
||||
cloud: {
|
||||
provider: 'aws',
|
||||
region: 'eu-west-1',
|
||||
account: {
|
||||
name: 'elastic-security-cloud-security-dev',
|
||||
id: '704479110758',
|
||||
},
|
||||
},
|
||||
'@timestamp': '2023-06-29T02:08:44.993Z',
|
||||
cloudbeat: {
|
||||
commit_sha: '4d990caa0c9c1594441da6bf24a685599aeb2bd5',
|
||||
commit_time: '2023-05-15T14:48:10Z',
|
||||
version: '8.8.0',
|
||||
},
|
||||
ecs: {
|
||||
version: '8.6.0',
|
||||
},
|
||||
data_stream: {
|
||||
namespace: 'default',
|
||||
type: 'logs',
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
},
|
||||
host: {
|
||||
name: 'ip-172-31-17-52',
|
||||
},
|
||||
event: {
|
||||
agent_id_status: 'auth_metadata_missing',
|
||||
sequence: 1687955586,
|
||||
ingested: '2023-07-13T09:55:39Z',
|
||||
kind: 'state',
|
||||
created: '2023-06-29T02:08:44.993386561Z',
|
||||
id: '80d05bca-9900-4038-ac8d-bcefaf6afd0c',
|
||||
type: ['info'],
|
||||
category: ['vulnerability'],
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
outcome: 'success',
|
||||
},
|
||||
},
|
||||
{
|
||||
agent: {
|
||||
name: 'ip-172-31-17-52',
|
||||
id: '9e69d889-c0c0-4c3e-9dc7-01dcd7c7db11',
|
||||
type: 'cloudbeat',
|
||||
ephemeral_id: '4fce6ed8-c9b3-40bb-b805-77e884cce0ef',
|
||||
version: '8.8.0',
|
||||
},
|
||||
package: {
|
||||
path: 'snap-08c227d5c8a3dc1f2 (amazon 2 (Karoo))',
|
||||
fixed_version: '2.56.1-9.amzn2.0.6',
|
||||
name: 'glib2',
|
||||
type: 'amazon',
|
||||
version: '2.56.1-9.amzn2.0.5',
|
||||
},
|
||||
resource: {
|
||||
name: 'othername-june12-8-8-0-1',
|
||||
id: '09d11277683ea41c5',
|
||||
},
|
||||
elastic_agent: {
|
||||
id: '9e69d889-c0c0-4c3e-9dc7-01dcd7c7db11',
|
||||
version: '8.8.0',
|
||||
snapshot: false,
|
||||
},
|
||||
vulnerability: {
|
||||
severity: 'HIGH',
|
||||
package: {
|
||||
fixed_version: '2.56.1-9.amzn2.0.6',
|
||||
name: 'glib2',
|
||||
version: '2.56.1-9.amzn2.0.5',
|
||||
},
|
||||
description:
|
||||
'PCRE before 8.38 mishandles the (?(<digits>) and (?(R<digits>) conditions, which allows remote attackers to cause a denial of service (integer overflow) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.',
|
||||
classification: 'CVSS',
|
||||
title: 'pcre: Integer overflow caused by missing check for certain conditions (8.38/31)',
|
||||
data_source: {
|
||||
ID: 'amazon',
|
||||
URL: 'https://alas.aws.amazon.com/',
|
||||
Name: 'Amazon Linux Security Center',
|
||||
},
|
||||
reference: 'https://avd.aquasec.com/nvd/cve-2015-8394',
|
||||
cwe: ['CWE-190'],
|
||||
score: {
|
||||
version: '3.1',
|
||||
base: 9.8,
|
||||
},
|
||||
report_id: 1687955586,
|
||||
scanner: {
|
||||
vendor: 'Trivy',
|
||||
version: 'v0.35.0',
|
||||
},
|
||||
id: 'CVE-2015-8394',
|
||||
enumeration: 'CVE',
|
||||
class: 'os-pkgs',
|
||||
published_date: '2015-12-02T01:59:00Z',
|
||||
cvss: {
|
||||
redhat: {
|
||||
V2Vector: 'AV:N/AC:M/Au:N/C:N/I:N/A:P',
|
||||
V2Score: 4.3,
|
||||
},
|
||||
nvd: {
|
||||
V3Vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H',
|
||||
V2Vector: 'AV:N/AC:L/Au:N/C:P/I:P/A:P',
|
||||
V3Score: 9.8,
|
||||
V2Score: 7.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
cloud: {
|
||||
provider: 'aws',
|
||||
region: 'eu-west-1',
|
||||
account: {
|
||||
name: 'elastic-security-cloud-security-dev',
|
||||
id: '704479110758',
|
||||
},
|
||||
},
|
||||
'@timestamp': '2023-06-29T02:08:16.535Z',
|
||||
ecs: {
|
||||
version: '8.6.0',
|
||||
},
|
||||
cloudbeat: {
|
||||
commit_sha: '4d990caa0c9c1594441da6bf24a685599aeb2bd5',
|
||||
commit_time: '2023-05-15T14:48:10Z',
|
||||
version: '8.8.0',
|
||||
},
|
||||
data_stream: {
|
||||
namespace: 'default',
|
||||
type: 'logs',
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
},
|
||||
host: {
|
||||
name: 'ip-172-31-17-52',
|
||||
},
|
||||
event: {
|
||||
agent_id_status: 'auth_metadata_missing',
|
||||
sequence: 1687955586,
|
||||
ingested: '2023-07-13T09:55:39Z',
|
||||
created: '2023-06-29T02:08:16.535506246Z',
|
||||
kind: 'state',
|
||||
id: '7cd4309a-01dd-433c-8c44-14019f8b1522',
|
||||
category: ['vulnerability'],
|
||||
type: ['info'],
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
outcome: 'success',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const scoresVulnerabilitiesMock = [
|
||||
{
|
||||
'@timestamp': '2023-09-03T11:36:58.441344Z',
|
||||
critical: 0,
|
||||
high: 1,
|
||||
medium: 1,
|
||||
low: 0,
|
||||
policy_template: 'vuln_mgmt',
|
||||
vulnerabilities_stats_by_cloud_account: {
|
||||
'704479110758': {
|
||||
cloudAccountName: 'elastic-security-cloud-security-dev',
|
||||
cloudAccountId: '704479110758',
|
||||
critical: 0,
|
||||
high: 1,
|
||||
medium: 1,
|
||||
low: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
'@timestamp': '2023-09-03T11:36:58.441344Z',
|
||||
critical: 0,
|
||||
high: 1,
|
||||
medium: 1,
|
||||
low: 0,
|
||||
policy_template: 'vuln_mgmt',
|
||||
vulnerabilities_stats_by_cloud_account: {
|
||||
'704479110758': {
|
||||
cloudAccountName: 'elastic-security-cloud-security-dev',
|
||||
cloudAccountId: '704479110758',
|
||||
critical: 0,
|
||||
high: 1,
|
||||
medium: 1,
|
||||
low: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
|
@ -0,0 +1,311 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
import { EcsEvent } from '@kbn/ecs';
|
||||
import type { FtrProviderContext } from '../ftr_provider_context';
|
||||
import {
|
||||
vulnerabilitiesLatestMock,
|
||||
scoresVulnerabilitiesMock,
|
||||
} from './mocks/vulnerabilities_latest_mock';
|
||||
|
||||
export interface CnvmStatistics {
|
||||
criticalCount?: number;
|
||||
highCount?: number;
|
||||
mediumCount?: number;
|
||||
resourcesScanned?: number;
|
||||
cloudRegions?: number;
|
||||
}
|
||||
|
||||
export interface AccountVulnStats {
|
||||
cloudAccountId: string;
|
||||
cloudAccountName: string;
|
||||
critical?: number;
|
||||
high?: number;
|
||||
medium?: number;
|
||||
low?: number;
|
||||
}
|
||||
|
||||
export interface VulnStatsTrend {
|
||||
'@timestamp': string;
|
||||
policy_template: 'vuln_mgmt';
|
||||
critical: number;
|
||||
high: number;
|
||||
medium: number;
|
||||
low: number;
|
||||
vulnerabilities_stats_by_cloud_account?: Record<
|
||||
AccountVulnStats['cloudAccountId'],
|
||||
AccountVulnStats
|
||||
>;
|
||||
event: EcsEvent;
|
||||
}
|
||||
|
||||
export interface VulnerableResourceStat {
|
||||
vulnerabilityCount?: number;
|
||||
resource: {
|
||||
id?: string;
|
||||
name?: string;
|
||||
};
|
||||
cloudRegion?: string;
|
||||
}
|
||||
|
||||
export interface PatchableVulnerabilityStat {
|
||||
vulnerabilityCount?: number;
|
||||
packageFixVersion?: string;
|
||||
cve?: string;
|
||||
cvss: {
|
||||
score?: number;
|
||||
version?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface VulnerabilityStat {
|
||||
packageFixVersion?: string;
|
||||
packageName?: string;
|
||||
packageVersion?: string;
|
||||
severity?: string;
|
||||
vulnerabilityCount?: number;
|
||||
cvss: {
|
||||
score?: number;
|
||||
version?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CnvmDashboardData {
|
||||
cnvmStatistics: CnvmStatistics;
|
||||
vulnTrends: VulnStatsTrend[];
|
||||
topVulnerableResources: VulnerableResourceStat[];
|
||||
topPatchableVulnerabilities: PatchableVulnerabilityStat[];
|
||||
topVulnerabilities: VulnerabilityStat[];
|
||||
}
|
||||
|
||||
const VULNERABILITIES_LATEST_INDEX = 'logs-cloud_security_posture.vulnerabilities_latest-default';
|
||||
const BENCHMARK_SCORES_INDEX = 'logs-cloud_security_posture.scores-default';
|
||||
|
||||
type CnvmDashboardDataWithoutTimestamp = Omit<CnvmDashboardData, 'vulnTrends'> & {
|
||||
vulnTrends: Array<Omit<VulnStatsTrend, '@timestamp' | 'event'>>;
|
||||
};
|
||||
|
||||
const removeRealtimeCalculatedFields = (
|
||||
responseBody: CnvmDashboardData
|
||||
): CnvmDashboardDataWithoutTimestamp => {
|
||||
const cleanedVulnTrends = responseBody.vulnTrends.map((trend) => {
|
||||
const { ['@timestamp']: timestamp, event, ...rest } = trend;
|
||||
return rest;
|
||||
});
|
||||
|
||||
return {
|
||||
...responseBody,
|
||||
vulnTrends: cleanedVulnTrends,
|
||||
};
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const es = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const log = getService('log');
|
||||
|
||||
/**
|
||||
* required before indexing findings
|
||||
*/
|
||||
const waitForPluginInitialized = (): Promise<void> =>
|
||||
retry.try(async () => {
|
||||
log.debug('Check CSP plugin is initialized');
|
||||
const response = await supertest
|
||||
.get('/internal/cloud_security_posture/status?check=init')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.expect(200);
|
||||
expect(response.body).to.eql({ isPluginInitialized: true });
|
||||
log.debug('CSP plugin is initialized');
|
||||
});
|
||||
|
||||
const index = {
|
||||
addFindings: async <T>(vulnerabilitiesMock: T[]) => {
|
||||
await Promise.all(
|
||||
vulnerabilitiesMock.map((vulnerabilityDoc) =>
|
||||
es.index({
|
||||
index: VULNERABILITIES_LATEST_INDEX,
|
||||
body: vulnerabilityDoc,
|
||||
refresh: true,
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
addScores: async <T>(scoresMock: T[]) => {
|
||||
await Promise.all(
|
||||
scoresMock.map((scoreDoc) =>
|
||||
es.index({
|
||||
index: BENCHMARK_SCORES_INDEX,
|
||||
body: scoreDoc,
|
||||
refresh: true,
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
removeFindings: async () => {
|
||||
const indexExists = await es.indices.exists({ index: VULNERABILITIES_LATEST_INDEX });
|
||||
|
||||
if (indexExists) {
|
||||
es.deleteByQuery({
|
||||
index: VULNERABILITIES_LATEST_INDEX,
|
||||
query: { match_all: {} },
|
||||
refresh: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
removeScores: async () => {
|
||||
const indexExists = await es.indices.exists({ index: BENCHMARK_SCORES_INDEX });
|
||||
|
||||
if (indexExists) {
|
||||
es.deleteByQuery({
|
||||
index: BENCHMARK_SCORES_INDEX,
|
||||
query: { match_all: {} },
|
||||
refresh: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
deleteFindingsIndex: async () => {
|
||||
const indexExists = await es.indices.exists({ index: VULNERABILITIES_LATEST_INDEX });
|
||||
|
||||
if (indexExists) {
|
||||
await es.indices.delete({ index: VULNERABILITIES_LATEST_INDEX });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
describe('Vulnerability Dashboard API', async () => {
|
||||
beforeEach(async () => {
|
||||
await waitForPluginInitialized();
|
||||
await index.addScores(scoresVulnerabilitiesMock);
|
||||
await index.addFindings(vulnerabilitiesLatestMock);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await index.removeFindings();
|
||||
await index.removeScores();
|
||||
});
|
||||
|
||||
it('responds with a 200 status code and matching data mock', async () => {
|
||||
const { body } = await supertest
|
||||
.get(`/internal/cloud_security_posture/vulnerabilities_dashboard`)
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.expect(200);
|
||||
|
||||
// @timestamp and event are real time calculated fields, we need to remove them in order to remove inconsistencies between mock and actual result
|
||||
const cleanedBody = removeRealtimeCalculatedFields(body);
|
||||
|
||||
expect(cleanedBody).to.eql({
|
||||
cnvmStatistics: {
|
||||
criticalCount: 0,
|
||||
highCount: 1,
|
||||
mediumCount: 1,
|
||||
resourcesScanned: 2,
|
||||
cloudRegions: 1,
|
||||
},
|
||||
vulnTrends: [
|
||||
{
|
||||
high: 1,
|
||||
policy_template: 'vuln_mgmt',
|
||||
critical: 0,
|
||||
low: 0,
|
||||
vulnerabilities_stats_by_cloud_account: {
|
||||
'704479110758': {
|
||||
cloudAccountName: 'elastic-security-cloud-security-dev',
|
||||
high: 1,
|
||||
critical: 0,
|
||||
low: 0,
|
||||
cloudAccountId: '704479110758',
|
||||
medium: 1,
|
||||
},
|
||||
},
|
||||
medium: 1,
|
||||
},
|
||||
],
|
||||
topVulnerableResources: [
|
||||
{
|
||||
resource: {
|
||||
id: '02d62a7df23951b19',
|
||||
name: 'name-ng-1-Node',
|
||||
},
|
||||
vulnerabilityCount: 1,
|
||||
cloudRegion: 'eu-west-1',
|
||||
},
|
||||
{
|
||||
resource: {
|
||||
id: '09d11277683ea41c5',
|
||||
name: 'othername-june12-8-8-0-1',
|
||||
},
|
||||
vulnerabilityCount: 1,
|
||||
cloudRegion: 'eu-west-1',
|
||||
},
|
||||
],
|
||||
topPatchableVulnerabilities: [
|
||||
{
|
||||
cve: 'CVE-2015-8390',
|
||||
cvss: {
|
||||
score: 9.800000190734863,
|
||||
version: '3.1',
|
||||
},
|
||||
packageFixVersion: '2.56.1-9.amzn2.0.6',
|
||||
vulnerabilityCount: 1,
|
||||
},
|
||||
{
|
||||
cve: 'CVE-2015-8394',
|
||||
cvss: {
|
||||
score: 9.800000190734863,
|
||||
version: '3.1',
|
||||
},
|
||||
packageFixVersion: '2.56.1-9.amzn2.0.6',
|
||||
vulnerabilityCount: 1,
|
||||
},
|
||||
],
|
||||
topVulnerabilities: [
|
||||
{
|
||||
cve: 'CVE-2015-8390',
|
||||
packageFixVersion: '2.56.1-9.amzn2.0.6',
|
||||
packageName: 'glib2',
|
||||
packageVersion: '2.56.1-9.amzn2.0.5',
|
||||
severity: 'MEDIUM',
|
||||
vulnerabilityCount: 1,
|
||||
cvss: {
|
||||
score: 9.800000190734863,
|
||||
version: '3.1',
|
||||
},
|
||||
},
|
||||
{
|
||||
cve: 'CVE-2015-8394',
|
||||
packageFixVersion: '2.56.1-9.amzn2.0.6',
|
||||
packageName: 'glib2',
|
||||
packageVersion: '2.56.1-9.amzn2.0.5',
|
||||
severity: 'HIGH',
|
||||
vulnerabilityCount: 1,
|
||||
cvss: {
|
||||
score: 9.800000190734863,
|
||||
version: '3.1',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a 400 error when necessary indices are nonexistent', async () => {
|
||||
await index.deleteFindingsIndex();
|
||||
|
||||
await supertest
|
||||
.get('/internal/cloud_security_posture/vulnerabilities_dashboard')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.expect(500);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -38,7 +38,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
* 2. merge the updated version number change to kibana
|
||||
*/
|
||||
`--xpack.fleet.packages.0.name=cloud_security_posture`,
|
||||
`--xpack.fleet.packages.0.version=1.2.10`,
|
||||
`--xpack.fleet.packages.0.version=1.5.0`,
|
||||
// `--xpack.fleet.registryUrl=https://localhost:8080`,
|
||||
],
|
||||
},
|
||||
|
|
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const vulnerabilitiesLatestMock = [
|
||||
{
|
||||
agent: {
|
||||
name: 'ip-172-31-17-52',
|
||||
id: '9e69d889-c0c0-4c3e-9dc7-01dcd7c7db10',
|
||||
ephemeral_id: '4fce6ed8-c9b3-40bb-b805-77e884cce0ef',
|
||||
type: 'cloudbeat',
|
||||
version: '8.8.0',
|
||||
},
|
||||
package: {
|
||||
path: 'snap-07b9e0d5eb5f324a1 (amazon 2 (Karoo))',
|
||||
fixed_version: '2.56.1-9.amzn2.0.6',
|
||||
name: 'glib2',
|
||||
type: 'amazon',
|
||||
version: '2.56.1-9.amzn2.0.5',
|
||||
},
|
||||
resource: {
|
||||
name: 'name-ng-1-Node',
|
||||
id: '02d62a7df23951b19',
|
||||
},
|
||||
elastic_agent: {
|
||||
id: '9e69d889-c0c0-4c3e-9dc7-01dcd7c7db10',
|
||||
version: '8.8.0',
|
||||
snapshot: false,
|
||||
},
|
||||
vulnerability: {
|
||||
severity: 'MEDIUM',
|
||||
package: {
|
||||
fixed_version: '2.56.1-9.amzn2.0.6',
|
||||
name: 'glib2',
|
||||
version: '2.56.1-9.amzn2.0.5',
|
||||
},
|
||||
description:
|
||||
'PCRE before 8.38 mishandles the [: and \\\\ substrings in character classes, which allows remote attackers to cause a denial of service (uninitialized memory read) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.',
|
||||
title:
|
||||
'pcre: uninitialized memory read triggered by malformed posix character class (8.38/22)',
|
||||
classification: 'CVSS',
|
||||
data_source: {
|
||||
ID: 'amazon',
|
||||
URL: 'https://alas.aws.amazon.com/',
|
||||
Name: 'Amazon Linux Security Center',
|
||||
},
|
||||
cwe: ['CWE-908'],
|
||||
reference: 'https://avd.aquasec.com/nvd/cve-2015-8390',
|
||||
score: {
|
||||
version: '3.1',
|
||||
base: 9.8,
|
||||
},
|
||||
report_id: 1687955586,
|
||||
scanner: {
|
||||
vendor: 'Trivy',
|
||||
version: 'v0.35.0',
|
||||
},
|
||||
id: 'CVE-2015-8390',
|
||||
enumeration: 'CVE',
|
||||
cvss: {
|
||||
redhat: {
|
||||
V2Vector: 'AV:N/AC:M/Au:N/C:N/I:N/A:P',
|
||||
V2Score: 4.3,
|
||||
},
|
||||
nvd: {
|
||||
V3Vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H',
|
||||
V2Vector: 'AV:N/AC:L/Au:N/C:P/I:P/A:P',
|
||||
V3Score: 9.8,
|
||||
V2Score: 7.5,
|
||||
},
|
||||
},
|
||||
class: 'os-pkgs',
|
||||
published_date: '2015-12-02T01:59:00Z',
|
||||
},
|
||||
cloud: {
|
||||
provider: 'aws',
|
||||
region: 'eu-west-1',
|
||||
account: {
|
||||
name: 'elastic-security-cloud-security-dev',
|
||||
id: '704479110758',
|
||||
},
|
||||
},
|
||||
'@timestamp': '2023-06-29T02:08:44.993Z',
|
||||
cloudbeat: {
|
||||
commit_sha: '4d990caa0c9c1594441da6bf24a685599aeb2bd5',
|
||||
commit_time: '2023-05-15T14:48:10Z',
|
||||
version: '8.8.0',
|
||||
},
|
||||
ecs: {
|
||||
version: '8.6.0',
|
||||
},
|
||||
data_stream: {
|
||||
namespace: 'default',
|
||||
type: 'logs',
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
},
|
||||
host: {
|
||||
name: 'ip-172-31-17-52',
|
||||
},
|
||||
event: {
|
||||
agent_id_status: 'auth_metadata_missing',
|
||||
sequence: 1687955586,
|
||||
ingested: '2023-07-13T09:55:39Z',
|
||||
kind: 'state',
|
||||
created: '2023-06-29T02:08:44.993386561Z',
|
||||
id: '80d05bca-9900-4038-ac8d-bcefaf6afd0c',
|
||||
type: ['info'],
|
||||
category: ['vulnerability'],
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
outcome: 'success',
|
||||
},
|
||||
},
|
||||
{
|
||||
agent: {
|
||||
name: 'ip-172-31-17-52',
|
||||
id: '9e69d889-c0c0-4c3e-9dc7-01dcd7c7db11',
|
||||
type: 'cloudbeat',
|
||||
ephemeral_id: '4fce6ed8-c9b3-40bb-b805-77e884cce0ef',
|
||||
version: '8.8.0',
|
||||
},
|
||||
package: {
|
||||
path: 'snap-08c227d5c8a3dc1f2 (amazon 2 (Karoo))',
|
||||
fixed_version: '2.56.1-9.amzn2.0.6',
|
||||
name: 'glib2',
|
||||
type: 'amazon',
|
||||
version: '2.56.1-9.amzn2.0.5',
|
||||
},
|
||||
resource: {
|
||||
name: 'othername-june12-8-8-0-1',
|
||||
id: '09d11277683ea41c5',
|
||||
},
|
||||
elastic_agent: {
|
||||
id: '9e69d889-c0c0-4c3e-9dc7-01dcd7c7db11',
|
||||
version: '8.8.0',
|
||||
snapshot: false,
|
||||
},
|
||||
vulnerability: {
|
||||
severity: 'HIGH',
|
||||
package: {
|
||||
fixed_version: '2.56.1-9.amzn2.0.6',
|
||||
name: 'glib2',
|
||||
version: '2.56.1-9.amzn2.0.5',
|
||||
},
|
||||
description:
|
||||
'PCRE before 8.38 mishandles the (?(<digits>) and (?(R<digits>) conditions, which allows remote attackers to cause a denial of service (integer overflow) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.',
|
||||
classification: 'CVSS',
|
||||
title: 'pcre: Integer overflow caused by missing check for certain conditions (8.38/31)',
|
||||
data_source: {
|
||||
ID: 'amazon',
|
||||
URL: 'https://alas.aws.amazon.com/',
|
||||
Name: 'Amazon Linux Security Center',
|
||||
},
|
||||
reference: 'https://avd.aquasec.com/nvd/cve-2015-8394',
|
||||
cwe: ['CWE-190'],
|
||||
score: {
|
||||
version: '3.1',
|
||||
base: 9.8,
|
||||
},
|
||||
report_id: 1687955586,
|
||||
scanner: {
|
||||
vendor: 'Trivy',
|
||||
version: 'v0.35.0',
|
||||
},
|
||||
id: 'CVE-2015-8394',
|
||||
enumeration: 'CVE',
|
||||
class: 'os-pkgs',
|
||||
published_date: '2015-12-02T01:59:00Z',
|
||||
cvss: {
|
||||
redhat: {
|
||||
V2Vector: 'AV:N/AC:M/Au:N/C:N/I:N/A:P',
|
||||
V2Score: 4.3,
|
||||
},
|
||||
nvd: {
|
||||
V3Vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H',
|
||||
V2Vector: 'AV:N/AC:L/Au:N/C:P/I:P/A:P',
|
||||
V3Score: 9.8,
|
||||
V2Score: 7.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
cloud: {
|
||||
provider: 'aws',
|
||||
region: 'eu-west-1',
|
||||
account: {
|
||||
name: 'elastic-security-cloud-security-dev',
|
||||
id: '704479110758',
|
||||
},
|
||||
},
|
||||
'@timestamp': '2023-06-29T02:08:16.535Z',
|
||||
ecs: {
|
||||
version: '8.6.0',
|
||||
},
|
||||
cloudbeat: {
|
||||
commit_sha: '4d990caa0c9c1594441da6bf24a685599aeb2bd5',
|
||||
commit_time: '2023-05-15T14:48:10Z',
|
||||
version: '8.8.0',
|
||||
},
|
||||
data_stream: {
|
||||
namespace: 'default',
|
||||
type: 'logs',
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
},
|
||||
host: {
|
||||
name: 'ip-172-31-17-52',
|
||||
},
|
||||
event: {
|
||||
agent_id_status: 'auth_metadata_missing',
|
||||
sequence: 1687955586,
|
||||
ingested: '2023-07-13T09:55:39Z',
|
||||
created: '2023-06-29T02:08:16.535506246Z',
|
||||
kind: 'state',
|
||||
id: '7cd4309a-01dd-433c-8c44-14019f8b1522',
|
||||
category: ['vulnerability'],
|
||||
type: ['info'],
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
outcome: 'success',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const scoresVulnerabilitiesMock = [
|
||||
{
|
||||
critical: 0,
|
||||
high: 1,
|
||||
medium: 1,
|
||||
low: 0,
|
||||
policy_template: 'vuln_mgmt',
|
||||
vulnerabilities_stats_by_cloud_account: {
|
||||
'704479110758': {
|
||||
cloudAccountName: 'elastic-security-cloud-security-dev',
|
||||
cloudAccountId: '704479110758',
|
||||
critical: 0,
|
||||
high: 1,
|
||||
medium: 1,
|
||||
low: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
critical: 0,
|
||||
high: 1,
|
||||
medium: 1,
|
||||
low: 0,
|
||||
policy_template: 'vuln_mgmt',
|
||||
vulnerabilities_stats_by_cloud_account: {
|
||||
'704479110758': {
|
||||
cloudAccountName: 'elastic-security-cloud-security-dev',
|
||||
cloudAccountId: '704479110758',
|
||||
critical: 0,
|
||||
high: 1,
|
||||
medium: 1,
|
||||
low: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
|
@ -113,11 +113,6 @@ export function CspDashboardPageProvider({ getService, getPageObjects }: FtrProv
|
|||
await dashboard.getKubernetesSummarySection();
|
||||
return await testSubjects.find('dashboard-summary-section-compliance-score');
|
||||
},
|
||||
|
||||
getKubernetesComplianceScore2: async () => {
|
||||
// await dashboard.getKubernetesSummarySection();
|
||||
return await testSubjects.find('dashboard-summary-section-compliance-score');
|
||||
},
|
||||
};
|
||||
|
||||
const navigateToComplianceDashboardPage = async () => {
|
||||
|
|
|
@ -273,6 +273,10 @@ export function FindingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
const notInstalledVulnerabilities = createNotInstalledObject('cnvm-integration-not-installed');
|
||||
const notInstalledCSP = createNotInstalledObject('cloud_posture_page_package_not_installed');
|
||||
|
||||
const vulnerabilityDataGrid = {
|
||||
getVulnerabilityTable: async () => testSubjects.find('euiDataGrid'),
|
||||
};
|
||||
|
||||
const createFlyoutObject = (tableTestSubject: string) => ({
|
||||
async getElement() {
|
||||
return await testSubjects.find(tableTestSubject);
|
||||
|
@ -320,6 +324,7 @@ export function FindingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
index,
|
||||
waitForPluginInitialized,
|
||||
distributionBar,
|
||||
vulnerabilityDataGrid,
|
||||
misconfigurationsFlyout,
|
||||
toastMessage,
|
||||
detectionRuleApi,
|
||||
|
|
|
@ -8,9 +8,11 @@
|
|||
import { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects';
|
||||
import { FindingsPageProvider } from './findings_page';
|
||||
import { CspDashboardPageProvider } from './csp_dashboard_page';
|
||||
import { VulnerabilityDashboardPageProvider } from './vulnerability_dashboard_page_object';
|
||||
|
||||
export const pageObjects = {
|
||||
...xpackFunctionalPageObjects,
|
||||
findings: FindingsPageProvider,
|
||||
cloudPostureDashboard: CspDashboardPageProvider,
|
||||
vulnerabilityDashboard: VulnerabilityDashboardPageProvider,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common';
|
||||
import type { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
const VULNERABILITIES_LATEST_INDEX = 'logs-cloud_security_posture.vulnerabilities_latest-default';
|
||||
const BENCHMARK_SCORES_INDEX = 'logs-cloud_security_posture.scores-default';
|
||||
|
||||
export function VulnerabilityDashboardPageProvider({
|
||||
getService,
|
||||
getPageObjects,
|
||||
}: FtrProviderContext) {
|
||||
const PageObjects = getPageObjects(['common', 'header']);
|
||||
const retry = getService('retry');
|
||||
const es = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const log = getService('log');
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
||||
/**
|
||||
* required before indexing findings
|
||||
*/
|
||||
const waitForPluginInitialized = (): Promise<void> =>
|
||||
retry.try(async () => {
|
||||
log.debug('Check CSP plugin is initialized');
|
||||
const response = await supertest
|
||||
.get('/internal/cloud_security_posture/status?check=init')
|
||||
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
|
||||
.expect(200);
|
||||
expect(response.body).to.eql({ isPluginInitialized: true });
|
||||
log.debug('CSP plugin is initialized');
|
||||
});
|
||||
|
||||
const navigateToVulnerabilityDashboardPage = async () => {
|
||||
await PageObjects.common.navigateToUrl(
|
||||
'securitySolution', // Defined in Security Solution plugin
|
||||
'cloud_security_posture/vulnerability_dashboard',
|
||||
{ shouldUseHashForSubUrl: false }
|
||||
);
|
||||
};
|
||||
|
||||
const index = {
|
||||
addFindings: async <T>(vulnerabilitiesMock: T[]) => {
|
||||
await Promise.all(
|
||||
vulnerabilitiesMock.map((vulnerabilityDoc) =>
|
||||
es.index({
|
||||
index: VULNERABILITIES_LATEST_INDEX,
|
||||
body: vulnerabilityDoc,
|
||||
refresh: true,
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
addScores: async <T>(scoresMock: T[]) => {
|
||||
await Promise.all(
|
||||
scoresMock.map((scoreDoc) =>
|
||||
es.index({
|
||||
index: BENCHMARK_SCORES_INDEX,
|
||||
body: scoreDoc,
|
||||
refresh: true,
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
removeFindings: async () => {
|
||||
const indexExists = await es.indices.exists({ index: VULNERABILITIES_LATEST_INDEX });
|
||||
|
||||
if (indexExists) {
|
||||
await es.deleteByQuery({
|
||||
index: VULNERABILITIES_LATEST_INDEX,
|
||||
query: { match_all: {} },
|
||||
refresh: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
removeScores: async () => {
|
||||
const indexExists = await es.indices.exists({ index: BENCHMARK_SCORES_INDEX });
|
||||
|
||||
if (indexExists) {
|
||||
await es.deleteByQuery({
|
||||
index: BENCHMARK_SCORES_INDEX,
|
||||
query: { match_all: {} },
|
||||
refresh: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
deleteFindingsIndex: async () => {
|
||||
const indexExists = await es.indices.exists({ index: VULNERABILITIES_LATEST_INDEX });
|
||||
|
||||
if (indexExists) {
|
||||
await es.indices.delete({ index: VULNERABILITIES_LATEST_INDEX });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const dashboard = {
|
||||
getDashboardPageHeader: () => testSubjects.find('vulnerability-dashboard-page-header'),
|
||||
|
||||
getCriticalStat: () => testSubjects.find('critical-count-stat'),
|
||||
getHighStat: () => testSubjects.find('high-count-stat'),
|
||||
getMediumStat: () => testSubjects.find('medium-count-stat'),
|
||||
};
|
||||
|
||||
return {
|
||||
navigateToVulnerabilityDashboardPage,
|
||||
waitForPluginInitialized,
|
||||
index,
|
||||
dashboard,
|
||||
};
|
||||
}
|
|
@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./findings'));
|
||||
loadTestFile(require.resolve('./findings_alerts'));
|
||||
loadTestFile(require.resolve('./compliance_dashboard'));
|
||||
loadTestFile(require.resolve('./vulnerability_dashboard'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import type { FtrProviderContext } from '../ftr_provider_context';
|
||||
import { vulnerabilitiesLatestMock } from '../mocks/vulnerabilities_latest_mock';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
||||
const retry = getService('retry');
|
||||
const filterBar = getService('filterBar');
|
||||
const pageObjects = getPageObjects(['common', 'vulnerabilityDashboard', 'findings']);
|
||||
|
||||
describe('Vulnerability Dashboard Page', function () {
|
||||
this.tags(['cloud_security_vulnerability_dashboard']);
|
||||
|
||||
let navigateToVulnerabilityDashboardPage: typeof pageObjects.vulnerabilityDashboard.navigateToVulnerabilityDashboardPage;
|
||||
let waitForPluginInitialized: typeof pageObjects.vulnerabilityDashboard.waitForPluginInitialized;
|
||||
let index: typeof pageObjects.vulnerabilityDashboard.index;
|
||||
let dashboard: typeof pageObjects.vulnerabilityDashboard.dashboard;
|
||||
|
||||
before(async () => {
|
||||
navigateToVulnerabilityDashboardPage =
|
||||
pageObjects.vulnerabilityDashboard.navigateToVulnerabilityDashboardPage;
|
||||
waitForPluginInitialized = pageObjects.vulnerabilityDashboard.waitForPluginInitialized;
|
||||
index = pageObjects.vulnerabilityDashboard.index;
|
||||
dashboard = pageObjects.vulnerabilityDashboard.dashboard;
|
||||
|
||||
await waitForPluginInitialized();
|
||||
|
||||
await index.addFindings(vulnerabilitiesLatestMock);
|
||||
await navigateToVulnerabilityDashboardPage();
|
||||
await retry.waitFor(
|
||||
'Vulnerability dashboard to be displayed',
|
||||
async () => !!dashboard.getDashboardPageHeader()
|
||||
);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await index.removeFindings();
|
||||
});
|
||||
|
||||
describe('Vulnerability Dashboard', () => {
|
||||
it('Page Header renders on startup', async () => {
|
||||
const vulnPageHeader = await dashboard.getDashboardPageHeader();
|
||||
|
||||
expect(
|
||||
(await vulnPageHeader.getVisibleText()) === 'Cloud Native Vulnerability Management'
|
||||
).to.be(true);
|
||||
});
|
||||
|
||||
it('Stats render accurate output', async () => {
|
||||
const criticalStat = await dashboard.getCriticalStat();
|
||||
const highStat = await dashboard.getHighStat();
|
||||
const mediumStat = await dashboard.getMediumStat();
|
||||
|
||||
const criticalCount = await criticalStat.findByTagName('span');
|
||||
const highCount = await highStat.findByTagName('span');
|
||||
const mediumCount = await mediumStat.findByTagName('span');
|
||||
|
||||
expect((await criticalCount.getVisibleText()) === '0').to.be(true);
|
||||
expect((await highCount.getVisibleText()) === '1').to.be(true);
|
||||
expect((await mediumCount.getVisibleText()) === '1').to.be(true);
|
||||
});
|
||||
|
||||
it('should navigate to vulnerability findings page with high severity filter', async () => {
|
||||
const stat = await dashboard.getHighStat();
|
||||
await stat.click();
|
||||
|
||||
const isFilterApplied = await filterBar.hasFilter('vulnerability.severity', 'HIGH');
|
||||
expect(isFilterApplied).to.be(true);
|
||||
|
||||
// not removing the filter on purpose, to make sure it doesn't exist when navigating again from dashboard
|
||||
await navigateToVulnerabilityDashboardPage();
|
||||
});
|
||||
|
||||
it('should navigate to vulnerability findings page with critical severity filter and no high severity filter', async () => {
|
||||
const stat = await dashboard.getCriticalStat();
|
||||
await stat.click();
|
||||
|
||||
const isHighFilterApplied = await filterBar.hasFilter('vulnerability.severity', 'HIGH');
|
||||
expect(isHighFilterApplied).to.be(false);
|
||||
const isFilterApplied = await filterBar.hasFilter('vulnerability.severity', 'CRITICAL');
|
||||
expect(isFilterApplied).to.be(true);
|
||||
|
||||
await filterBar.removeFilter('vulnerability.severity');
|
||||
await navigateToVulnerabilityDashboardPage();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -144,6 +144,8 @@
|
|||
"@kbn/coloring",
|
||||
"@kbn/profiling-utils",
|
||||
"@kbn/profiling-data-access-plugin",
|
||||
"@kbn/ecs",
|
||||
"@kbn/coloring",
|
||||
"@kbn/es",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue