diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index 27fc64d44966f2..30cbcbce75f0a5 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -75,6 +75,8 @@ export const DATA_VIEW_INDEX_PATTERN = 'logs-*'; export const SECURITY_DEFAULT_DATA_VIEW_ID = 'security-solution-default'; +export const ALERTS_INDEX_PATTERN = '.alerts-security.alerts-*'; + export const CSP_INGEST_TIMESTAMP_PIPELINE = 'cloud_security_posture_add_ingest_timestamp_pipeline'; export const CSP_LATEST_FINDINGS_INGEST_TIMESTAMP_PIPELINE = 'cloud_security_posture_latest_index_add_ingest_timestamp_pipeline'; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts index a89c37d4397163..31b80b880bcc96 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/bulk_action/bulk_action.ts @@ -44,6 +44,9 @@ export const defineBulkActionCspBenchmarkRulesRoute = (router: CspRouter) => .post({ access: 'internal', path: CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH, + options: { + tags: ['access:cloud-security-posture-read'], + }, }) .addVersion( { diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts index 8dc8f36554600f..c1b481b01f2cdb 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts @@ -29,6 +29,9 @@ export const defineFindCspBenchmarkRuleRoute = (router: CspRouter) => .get({ access: 'internal', path: FIND_CSP_BENCHMARK_RULE_ROUTE_PATH, + options: { + tags: ['access:cloud-security-posture-read'], + }, }) .addVersion( { diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/get_states/get_states.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/get_states/get_states.ts index e7c28d345c52d1..a55bcd92ab3c81 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/get_states/get_states.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/get_states/get_states.ts @@ -16,6 +16,9 @@ export const defineGetCspBenchmarkRulesStatesRoute = (router: CspRouter) => .get({ access: 'internal', path: CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH, + options: { + tags: ['access:cloud-security-posture-read'], + }, }) .addVersion( { diff --git a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts index 4c9294248e1e76..851fa865566f79 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/compliance_dashboard/compliance_dashboard.ts @@ -65,6 +65,9 @@ export const defineGetComplianceDashboardRoute = (router: CspRouter) => .get({ access: 'internal', path: STATS_ROUTE_PATH, + options: { + tags: ['access:cloud-security-posture-read'], + }, }) .addVersion( { diff --git a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts index d4645631550231..026cf68819ab2b 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/detection_engine/get_detection_engine_alerts_count_by_rule_tags.ts @@ -57,6 +57,9 @@ export const defineGetDetectionEngineAlertsStatus = (router: CspRouter) => .get({ access: 'internal', path: GET_DETECTION_RULE_ALERTS_STATUS_PATH, + options: { + tags: ['access:cloud-security-posture-read'], + }, }) .addVersion( { diff --git a/x-pack/test/cloud_security_posture_api/config.ts b/x-pack/test/cloud_security_posture_api/config.ts index 62a976da706695..08dfc89bf18006 100644 --- a/x-pack/test/cloud_security_posture_api/config.ts +++ b/x-pack/test/cloud_security_posture_api/config.ts @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import { resolve } from 'path'; import type { FtrConfigProviderContext } from '@kbn/test'; import { CLOUD_SECURITY_PLUGIN_VERSION } from '@kbn/cloud-security-posture-plugin/common/constants'; @@ -15,14 +15,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...xpackFunctionalConfig.getAll(), - testFiles: [ - require.resolve('./telemetry/telemetry.ts'), - require.resolve('./routes/vulnerabilities_dashboard.ts'), - require.resolve('./routes/stats.ts'), - require.resolve('./routes/csp_benchmark_rules_bulk_update.ts'), - require.resolve('./routes/csp_benchmark_rules_get_states.ts'), - require.resolve('./routes/benchmarks.ts'), - ], + testFiles: [resolve(__dirname, './routes')], junit: { reportName: 'X-Pack Cloud Security Posture API Tests', }, diff --git a/x-pack/test/cloud_security_posture_api/routes/benchmarks.ts b/x-pack/test/cloud_security_posture_api/routes/benchmarks.ts index 65a3ec42fc37d9..a8b94a9f36799b 100644 --- a/x-pack/test/cloud_security_posture_api/routes/benchmarks.ts +++ b/x-pack/test/cloud_security_posture_api/routes/benchmarks.ts @@ -17,18 +17,20 @@ import expect from '@kbn/expect'; import Chance from 'chance'; import { CspBenchmarkRule } from '@kbn/cloud-security-posture-plugin/common/types/latest'; import { FtrProviderContext } from '../ftr_provider_context'; +import { CspSecurityCommonProvider } from './helper/user_roles_utilites'; const chance = new Chance(); // eslint-disable-next-line import/no-default-export export default function (providerContext: FtrProviderContext) { const { getService } = providerContext; - const retry = getService('retry'); const es = getService('es'); const kibanaServer = getService('kibanaServer'); const supertest = getService('supertest'); const log = getService('log'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const cspSecurity = CspSecurityCommonProvider(providerContext); const getCspBenchmarkRules = async (benchmarkId: string): Promise => { let cspBenchmarkRules: CspBenchmarkRule[] = []; @@ -263,5 +265,57 @@ export default function (providerContext: FtrProviderContext) { expect(scoreAfterMute.score.postureScore).to.equal(0); }); }); + + describe('Get Benchmark API', async () => { + beforeEach(async () => { + await index.removeFindings(); + await kibanaServer.savedObjects.clean({ + types: ['cloud-security-posture-settings'], + }); + await waitForPluginInitialized(); + }); + + it('Calling Benchmark API as User with no read access to Security', async () => { + const benchmark = 'cis_aws'; + const benchmarkRules = await getCspBenchmarkRules(benchmark); + + const cspmFinding1 = getMockFinding(benchmarkRules[0], 'passed'); + + await index.addFindings([cspmFinding1]); + + const { body: benchmarksResult } = await supertestWithoutAuth + .get('/internal/cloud_security_posture/benchmarks') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_no_read_user', + cspSecurity.getPasswordForUser('role_security_no_read_user') + ); + + expect(benchmarksResult.statusCode).to.equal(403); + }); + + // Blocked by https://github.com/elastic/kibana/issues/188059 + it.skip('Calling Benchmark API as User with read access to Security', async () => { + const benchmark = 'cis_aws'; + const benchmarkRules = await getCspBenchmarkRules(benchmark); + + const cspmFinding1 = getMockFinding(benchmarkRules[0], 'passed'); + + await index.addFindings([cspmFinding1]); + + const { status } = await supertestWithoutAuth + .get('/internal/cloud_security_posture/benchmarks') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_read_user', + cspSecurity.getPasswordForUser('role_security_read_user') + ); + expect(status).to.equal(200); + }); + }); }); } diff --git a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts index 17ce5675e13192..54ae003de8698a 100644 --- a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts +++ b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_bulk_update.ts @@ -21,13 +21,17 @@ import type { CspBenchmarkRule } from '@kbn/cloud-security-posture-plugin/common // eslint-disable @kbn/imports/no_boundary_crossing import { generateBenchmarkRuleTags } from '@kbn/cloud-security-posture-plugin/common/utils/detection_rules'; import type { FtrProviderContext } from '../ftr_provider_context'; +import { CspSecurityCommonProvider } from './helper/user_roles_utilites'; // eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; const retry = getService('retry'); const supertest = getService('supertest'); const log = getService('log'); const kibanaServer = getService('kibanaServer'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const cspSecurity = CspSecurityCommonProvider(providerContext); const generateRuleKey = (rule: CspBenchmarkRule): string => { return `${rule.metadata.benchmark.id};${rule.metadata.benchmark.version};${rule.metadata.benchmark.rule_number}`; @@ -447,5 +451,68 @@ export default function ({ getService }: FtrProviderContext) { expect(body.error).to.eql('Bad Request'); expect(body.statusCode).to.eql(400); }); + + it('users without read privileges on cloud security should not be able to mute', async () => { + const rule1 = await getRandomCspBenchmarkRule(); + const rule2 = await getRandomCspBenchmarkRule(); + + const { status } = await supertestWithoutAuth + .post(`/internal/cloud_security_posture/rules/_bulk_action`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_no_read_user', + cspSecurity.getPasswordForUser('role_security_no_read_user') + ) + .send({ + action: 'mute', + rules: [ + { + benchmark_id: rule1.metadata.benchmark.id, + benchmark_version: rule1.metadata.benchmark.version, + rule_number: rule1.metadata.benchmark.rule_number || '', + rule_id: rule1.metadata.id, + }, + { + benchmark_id: rule2.metadata.benchmark.id, + benchmark_version: rule2.metadata.benchmark.version, + rule_number: rule2.metadata.benchmark.rule_number || '', + rule_id: rule2.metadata.id, + }, + ], + }); + expect(status).to.be(403); + }); + // Blocked by https://github.com/elastic/kibana/issues/188059 + it.skip('users with read privileges on cloud security should be able to mute', async () => { + const rule1 = await getRandomCspBenchmarkRule(); + const rule2 = await getRandomCspBenchmarkRule(); + + const { status } = await supertestWithoutAuth + .post(`/internal/cloud_security_posture/rules/_bulk_action`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .set('kbn-xsrf', 'xxxx') + .auth('role_security_read_user', cspSecurity.getPasswordForUser('role_security_read_user')) + .send({ + action: 'mute', + rules: [ + { + benchmark_id: rule1.metadata.benchmark.id, + benchmark_version: rule1.metadata.benchmark.version, + rule_number: rule1.metadata.benchmark.rule_number || '', + rule_id: rule1.metadata.id, + }, + { + benchmark_id: rule2.metadata.benchmark.id, + benchmark_version: rule2.metadata.benchmark.version, + rule_number: rule2.metadata.benchmark.rule_number || '', + rule_id: rule2.metadata.id, + }, + ], + }); + expect(status).to.be(200); + }); }); } diff --git a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_get_states.ts b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_get_states.ts index 737013950d6119..3b023ca4d352bf 100644 --- a/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_get_states.ts +++ b/x-pack/test/cloud_security_posture_api/routes/csp_benchmark_rules_get_states.ts @@ -15,13 +15,17 @@ import { import { CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE } from '@kbn/cloud-security-posture-plugin/common/constants'; import type { CspBenchmarkRule } from '@kbn/cloud-security-posture-plugin/common/types/latest'; import type { FtrProviderContext } from '../ftr_provider_context'; +import { CspSecurityCommonProvider } from './helper/user_roles_utilites'; // eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; const retry = getService('retry'); const supertest = getService('supertest'); const log = getService('log'); const kibanaServer = getService('kibanaServer'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const cspSecurity = CspSecurityCommonProvider(providerContext); const generateRuleKey = (rule: CspBenchmarkRule): string => { return `${rule.metadata.benchmark.id};${rule.metadata.benchmark.version};${rule.metadata.benchmark.rule_number}`; @@ -160,5 +164,28 @@ export default function ({ getService }: FtrProviderContext) { expectExpect(body).toEqual({}); }); + // Blocked by https://github.com/elastic/kibana/issues/188059 + it.skip('GET rules states API with user with read access', async () => { + const { status } = await supertestWithoutAuth + .get(`/internal/cloud_security_posture/rules/_get_states?tags=CIS`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .set('kbn-xsrf', 'xxxx') + .auth('role_security_read_user', cspSecurity.getPasswordForUser('role_security_read_use')); + expect(status).to.be(200); + }); + + it('GET rules states API API with user without read access', async () => { + const { status } = await supertestWithoutAuth + .get(`/internal/cloud_security_posture/rules/_get_states`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_no_read_user', + cspSecurity.getPasswordForUser('role_security_no_read_user') + ); + expect(status).to.be(403); + }); }); } diff --git a/x-pack/test/cloud_security_posture_api/routes/get_detection_engine_alerts_count_by_rule_tags.ts b/x-pack/test/cloud_security_posture_api/routes/get_detection_engine_alerts_count_by_rule_tags.ts new file mode 100644 index 00000000000000..0b2ceb882ba23e --- /dev/null +++ b/x-pack/test/cloud_security_posture_api/routes/get_detection_engine_alerts_count_by_rule_tags.ts @@ -0,0 +1,70 @@ +/* + * 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 { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { CspSecurityCommonProvider } from './helper/user_roles_utilites'; + +// eslint-disable-next-line import/no-default-export +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + + const retry = getService('retry'); + const supertest = getService('supertest'); + const log = getService('log'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const cspSecurity = CspSecurityCommonProvider(providerContext); + + /** + * required before indexing findings + */ + const waitForPluginInitialized = (): Promise => + 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'); + }); + + describe('/internal/cloud_security_posture/detection_engine_rules/alerts/_status', () => { + describe('GET detection_engine_rules API with user that has specific access', async () => { + before(async () => { + await waitForPluginInitialized(); + }); + it('GET detection_engine_rules API with user with read access', async () => { + const { status } = await supertestWithoutAuth + .get( + '/internal/cloud_security_posture/detection_engine_rules/alerts/_status?tags=["CIS"]' + ) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_read_user_alerts', + cspSecurity.getPasswordForUser('role_security_read_user_alerts') + ); + expect(status).to.be(200); + }); + + it('GET detection_engine_rules API with user without read access', async () => { + const { status } = await supertestWithoutAuth + .get( + '/internal/cloud_security_posture/detection_engine_rules/alerts/_status?tags=["CIS"]' + ) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_no_read_user_alerts', + cspSecurity.getPasswordForUser('role_security_no_read_user_alerts') + ); + expect(status).to.be(403); + }); + }); + }); +} diff --git a/x-pack/test/cloud_security_posture_api/routes/helper/index.ts b/x-pack/test/cloud_security_posture_api/routes/helper/index.ts new file mode 100644 index 00000000000000..e26a8f21c9ea80 --- /dev/null +++ b/x-pack/test/cloud_security_posture_api/routes/helper/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { CspSecurityCommonProvider } from './user_roles_utilites'; + +export const cloudSecurityPosturePageObjects = { + cspSecurity: CspSecurityCommonProvider, +}; +export const pageObjects = { + ...cloudSecurityPosturePageObjects, +}; diff --git a/x-pack/test/cloud_security_posture_api/routes/helper/user_roles_utilites.ts b/x-pack/test/cloud_security_posture_api/routes/helper/user_roles_utilites.ts new file mode 100644 index 00000000000000..ef66b58d283112 --- /dev/null +++ b/x-pack/test/cloud_security_posture_api/routes/helper/user_roles_utilites.ts @@ -0,0 +1,201 @@ +/* + * 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 { + LATEST_FINDINGS_INDEX_PATTERN, + BENCHMARK_SCORE_INDEX_PATTERN, + LATEST_VULNERABILITIES_INDEX_PATTERN, + ALERTS_INDEX_PATTERN, + FINDINGS_INDEX_PATTERN, +} from '@kbn/cloud-security-posture-plugin/common/constants'; +import type { FtrProviderContext } from '../../ftr_provider_context'; + +const alertsSecurityUserIndices = [ + { + names: [FINDINGS_INDEX_PATTERN], + privileges: ['read'], + }, + { + names: [LATEST_FINDINGS_INDEX_PATTERN], + privileges: ['read'], + }, + { + names: [BENCHMARK_SCORE_INDEX_PATTERN], + privileges: ['read'], + }, + { + names: [LATEST_VULNERABILITIES_INDEX_PATTERN], + privileges: ['all'], + }, + { + names: [ALERTS_INDEX_PATTERN], + privileges: ['all'], + }, +]; + +const securityUserIndinces = [ + { + names: [FINDINGS_INDEX_PATTERN], + privileges: ['read'], + }, + { + names: [LATEST_FINDINGS_INDEX_PATTERN], + privileges: ['read'], + }, + { + names: [BENCHMARK_SCORE_INDEX_PATTERN], + privileges: ['read'], + }, + { + names: [LATEST_VULNERABILITIES_INDEX_PATTERN], + privileges: ['all'], + }, +]; + +export function CspSecurityCommonProvider(providerContext: FtrProviderContext) { + const { getService } = providerContext; + const security = getService('security'); + + const roles = [ + { + name: 'role_security_no_read', + elasticsearch: { + indices: securityUserIndinces, + }, + kibana: [ + { + base: [], + feature: { + fleet: ['all'], + fleetv2: ['all'], + savedObjectsManagement: ['all'], + }, + spaces: ['*'], + }, + ], + }, + { + name: 'role_security_read', + elasticsearch: { + indices: securityUserIndinces, + }, + kibana: [ + { + base: [], + feature: { + siem: ['read'], + fleet: ['all'], + fleetv2: ['all'], + savedObjectsManagement: ['all'], + }, + spaces: ['*'], + }, + ], + }, + { + name: 'role_security_read_alerts', + elasticsearch: { + indices: alertsSecurityUserIndices, + }, + kibana: [ + { + base: [], + feature: { + siem: ['read'], + fleet: ['all'], + fleetv2: ['all'], + }, + spaces: ['*'], + }, + ], + }, + { + name: 'role_security_no_read_alerts', + elasticsearch: { + indices: alertsSecurityUserIndices, + }, + kibana: [ + { + base: [], + feature: { + fleet: ['all'], + fleetv2: ['all'], + }, + spaces: ['*'], + }, + ], + }, + ]; + + const users = [ + { + name: 'role_security_read_user', + full_name: 'security read privilege user', + password: 'test123', + roles: ['role_security_read'], + }, + { + name: 'role_security_read_user_alerts', + full_name: 'user with 0 security privilege for', + password: 'csp123', + roles: ['role_security_read_alerts'], + }, + { + name: 'role_security_no_read_user', + full_name: 'user with 0 security privilege', + password: 'csp123', + roles: ['role_security_no_read'], + }, + { + name: 'role_security_no_read_user_alerts', + full_name: 'user with 0 security privilege for', + password: 'csp123', + roles: ['role_security_no_read_alerts'], + }, + ]; + + return { + async createRoles() { + for (const role of roles) { + await security.role.create(role.name, { + elasticsearch: role.elasticsearch, + kibana: role.kibana, + }); + } + }, + + async createUsers() { + for (const user of users) { + await security.user.create(user.name, { + password: user.password, + roles: user.roles, + full_name: user.full_name, + }); + } + }, + + async cleanRoles() { + for (const role of roles) { + await security.role.delete(role.name); + } + }, + + async cleanUsers() { + for (const user of users) { + await security.user.delete(user.name); + } + }, + + getPasswordForUser(user: string): string { + const userConfig = users.find((u) => u.name === user); + if (userConfig === undefined) { + throw new Error(`Can't log in user ${user} - not defined`); + } + return userConfig.password; + }, + }; +} diff --git a/x-pack/test/cloud_security_posture_api/routes/index.ts b/x-pack/test/cloud_security_posture_api/routes/index.ts new file mode 100644 index 00000000000000..adacc80fb6f7d5 --- /dev/null +++ b/x-pack/test/cloud_security_posture_api/routes/index.ts @@ -0,0 +1,30 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; +import { CspSecurityCommonProvider } from './helper/user_roles_utilites'; + +// eslint-disable-next-line import/no-default-export +export default function (providerContext: FtrProviderContext) { + const { loadTestFile } = providerContext; + const cspSecurity = CspSecurityCommonProvider(providerContext); + describe('Cloud Security Posture', function () { + before(async () => { + await cspSecurity.createRoles(); + await cspSecurity.createUsers(); + }); + + loadTestFile(require.resolve('../telemetry/telemetry.ts')); + loadTestFile(require.resolve('./vulnerabilities_dashboard.ts')); + loadTestFile(require.resolve('./stats.ts')); + loadTestFile(require.resolve('./csp_benchmark_rules_bulk_update.ts')); + loadTestFile(require.resolve('./csp_benchmark_rules_get_states.ts')); + loadTestFile(require.resolve('./benchmarks.ts')); + loadTestFile(require.resolve('./status.ts')); + loadTestFile(require.resolve('./get_detection_engine_alerts_count_by_rule_tags')); + }); +} diff --git a/x-pack/test/cloud_security_posture_api/routes/stats.ts b/x-pack/test/cloud_security_posture_api/routes/stats.ts index f290c24a645fc1..adb2952d5388e6 100644 --- a/x-pack/test/cloud_security_posture_api/routes/stats.ts +++ b/x-pack/test/cloud_security_posture_api/routes/stats.ts @@ -26,6 +26,7 @@ import { cspmComplianceDashboardDataMockV2, } from './mocks/benchmark_score_mock'; import { findingsMockData } from './mocks/findings_mock'; +import { CspSecurityCommonProvider } from './helper/user_roles_utilites'; const removeRealtimeCalculatedFields = (trends: PostureTrend[]) => { return trends.map((trend: PostureTrend) => { @@ -61,6 +62,8 @@ export default function (providerContext: FtrProviderContext) { const es = getService('es'); const supertest = getService('supertest'); const log = getService('log'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const cspSecurity = CspSecurityCommonProvider(providerContext); /** * required before indexing findings @@ -295,5 +298,75 @@ export default function (providerContext: FtrProviderContext) { }).to.eql(kspmComplianceDashboardDataMockV2); }); }); + + describe('GET stats API with user that has specific access', async () => { + beforeEach(async () => { + await index.removeFindings(); + await index.removeScores(); + + await waitForPluginInitialized(); + }); + it('GET stats API V1 with user with read access', async () => { + await index.addScores(getBenchmarkScoreMockData('cspm', true)); + await index.addScores(getBenchmarkScoreMockData('cspm', false)); + await index.addFindings([findingsMockData[1]]); + + const { status } = await supertestWithoutAuth + .get(`/internal/cloud_security_posture/stats/cspm`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_read_user', + cspSecurity.getPasswordForUser('role_security_read_user') + ); + expect(status).to.be(200); + }); + it('GET stats API V1 with user with read access', async () => { + await index.addScores(getBenchmarkScoreMockData('cspm', true)); + await index.addScores(getBenchmarkScoreMockData('cspm', false)); + await index.addFindings([findingsMockData[1]]); + + const { status } = await supertestWithoutAuth + .get(`/internal/cloud_security_posture/stats/cspm`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_read_user', + cspSecurity.getPasswordForUser('role_security_read_user') + ); + expect(status).to.be(200); + }); + it('GET stats API V2 with user with read access', async () => { + await index.addScores(getBenchmarkScoreMockData('cspm', true)); + await index.addScores(getBenchmarkScoreMockData('cspm', false)); + await index.addFindings([findingsMockData[1]]); + + const { status } = await supertestWithoutAuth + .get(`/internal/cloud_security_posture/stats/cspm`) + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_read_user', + cspSecurity.getPasswordForUser('role_security_read_user') + ); + expect(status).to.be(200); + }); + + it('GET stats API V2 with user without read access', async () => { + await index.addScores(getBenchmarkScoreMockData('kspm', true)); + await index.addScores(getBenchmarkScoreMockData('kspm', false)); + await index.addFindings([findingsMockData[0]]); + + const { status } = await supertestWithoutAuth + .get(`/internal/cloud_security_posture/stats/kspm`) + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_no_read_user', + cspSecurity.getPasswordForUser('role_security_no_read_user') + ); + expect(status).to.be(403); + }); + }); }); } diff --git a/x-pack/test/cloud_security_posture_api/routes/status.ts b/x-pack/test/cloud_security_posture_api/routes/status.ts new file mode 100644 index 00000000000000..0004144575956e --- /dev/null +++ b/x-pack/test/cloud_security_posture_api/routes/status.ts @@ -0,0 +1,66 @@ +/* + * 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 { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; +import { CspSecurityCommonProvider } from './helper/user_roles_utilites'; + +// eslint-disable-next-line import/no-default-export +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + + const retry = getService('retry'); + const supertest = getService('supertest'); + const log = getService('log'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const cspSecurity = CspSecurityCommonProvider(providerContext); + + /** + * required before indexing findings + */ + const waitForPluginInitialized = (): Promise => + 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'); + }); + + describe('GET /internal/cloud_security_posture/status', () => { + describe('GET status API with user that has specific access', async () => { + before(async () => { + await waitForPluginInitialized(); + }); + it('GET stats API with user with read access', async () => { + const { status } = await supertestWithoutAuth + .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_read_user', + cspSecurity.getPasswordForUser('role_security_read_user') + ); + expect(status).to.be(200); + }); + + it('GET status API with user without read access', async () => { + const { status } = await supertestWithoutAuth + .get(`/internal/cloud_security_posture/status`) + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_no_read_user', + cspSecurity.getPasswordForUser('role_security_no_read_user') + ); + expect(status).to.be(403); + }); + }); + }); +} diff --git a/x-pack/test/cloud_security_posture_api/routes/vulnerabilities_dashboard.ts b/x-pack/test/cloud_security_posture_api/routes/vulnerabilities_dashboard.ts index 8f2a2fa2693d47..8ed4f91a61fead 100644 --- a/x-pack/test/cloud_security_posture_api/routes/vulnerabilities_dashboard.ts +++ b/x-pack/test/cloud_security_posture_api/routes/vulnerabilities_dashboard.ts @@ -13,6 +13,7 @@ import { vulnerabilitiesLatestMock, scoresVulnerabilitiesMock, } from './mocks/vulnerabilities_latest_mock'; +import { CspSecurityCommonProvider } from './helper/user_roles_utilites'; export interface CnvmStatistics { criticalCount?: number; @@ -106,11 +107,14 @@ const removeRealtimeCalculatedFields = ( }; // eslint-disable-next-line import/no-default-export -export default function ({ getService }: FtrProviderContext) { +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; const retry = getService('retry'); const es = getService('es'); const supertest = getService('supertest'); const log = getService('log'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const cspSecurity = CspSecurityCommonProvider(providerContext); /** * required before indexing findings @@ -304,5 +308,28 @@ export default function ({ getService }: FtrProviderContext) { .set(ELASTIC_HTTP_VERSION_HEADER, '1') .expect(500); }); + + it('GET vulnerabilities dashboard API with users with read access to cloud security posture', async () => { + const { status } = await supertestWithoutAuth + .get('/internal/cloud_security_posture/vulnerabilities_dashboard') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .auth('role_security_read_user', cspSecurity.getPasswordForUser('role_security_read_user')); + + expect(status).to.be(200); + }); + + it('GET vulnerabilities dashboard API with users without read access to cloud security posture', async () => { + const { status } = await supertestWithoutAuth + .get('/internal/cloud_security_posture/vulnerabilities_dashboard') + .set(ELASTIC_HTTP_VERSION_HEADER, '1') + .set('kbn-xsrf', 'xxxx') + .auth( + 'role_security_no_read_user', + cspSecurity.getPasswordForUser('role_security_no_read_user') + ); + + expect(status).to.be(403); + }); }); }