[Cloud Security] Vulnerability dashboard api testings (#162217)

This commit is contained in:
Jordan 2023-10-03 12:13:09 +03:00 committed by GitHub
parent 62f17db60c
commit 084362f6b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1063 additions and 9 deletions

View file

@ -61,7 +61,7 @@ export const defineGetVulnerabilitiesDashboardRoute = (router: CspRouter): void
return response.customError({
body: { message: error.message },
statusCode: error.statusCode,
statusCode: 500,
});
}
}

View file

@ -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`,
],
},

View file

@ -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,
},
},
},
];

View file

@ -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);
});
});
}

View file

@ -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`,
],
},

View file

@ -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,
},
},
},
];

View file

@ -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 () => {

View file

@ -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,

View file

@ -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,
};

View file

@ -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,
};
}

View file

@ -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'));
});
}

View file

@ -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();
});
});
});
}

View file

@ -144,6 +144,8 @@
"@kbn/coloring",
"@kbn/profiling-utils",
"@kbn/profiling-data-access-plugin",
"@kbn/ecs",
"@kbn/coloring",
"@kbn/es",
]
}