diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/hunter.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/hunter.ts index 0183a64788619..808df4307dcac 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/hunter.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/hunter.ts @@ -22,6 +22,10 @@ export const getHunter: () => Omit = () => { 'actions_log_management_all', 'host_isolation_all', 'process_operations_all', + 'trusted_applications_all', + 'event_filters_all', + 'host_isolation_exceptions_all', + 'blocklist_all', ], }, }, diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/t2_analyst.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/t2_analyst.ts index 32d3fac8ac680..a80ea19516ca5 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/t2_analyst.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/roles_users/t2_analyst.ts @@ -17,7 +17,14 @@ export const getT2Analyst: () => Omit = () => { ...noResponseActionsRole.kibana[0], feature: { ...noResponseActionsRole.kibana[0].feature, - siem: ['minimal_all', 'actions_log_management_read'], + siem: [ + 'minimal_all', + 'actions_log_management_read', + 'trusted_applications_read', + 'event_filters_read', + 'host_isolation_exceptions_read', + 'blocklist_read', + ], }, }, ], diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/blocklists.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/blocklists.ts index 68b2cef5b1319..cb43aa5db89a4 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/blocklists.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/blocklists.ts @@ -16,11 +16,7 @@ import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/commo import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts'; -import { - createUserAndRole, - deleteUserAndRole, - ROLES, -} from '../../../common/services/security_solution'; +import { ROLE } from '../../services/roles_users'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -34,18 +30,12 @@ export default function ({ getService }: FtrProviderContext) { before(async () => { // Create an endpoint policy in fleet we can work with fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy(); - - // create role/user - await createUserAndRole(getService, ROLES.detections_admin); }); after(async () => { if (fleetEndpointPolicy) { await fleetEndpointPolicy.cleanup(); } - - // delete role/user - await deleteUserAndRole(getService, ROLES.detections_admin); }); const anEndpointArtifactError = (res: { body: { message: string } }) => { @@ -93,6 +83,7 @@ export default function ({ getService }: FtrProviderContext) { > = [ { method: 'post', + info: 'create single item', path: EXCEPTION_LIST_ITEM_URL, getBody: () => { return exceptionsGenerator.generateBlocklistForCreate({ tags: [GLOBAL_ARTIFACT_TAG] }); @@ -100,6 +91,7 @@ export default function ({ getService }: FtrProviderContext) { }, { method: 'put', + info: 'update single item', path: EXCEPTION_LIST_ITEM_URL, getBody: () => exceptionsGenerator.generateBlocklistForUpdate({ @@ -110,13 +102,60 @@ export default function ({ getService }: FtrProviderContext) { }, ]; + const needsWritePrivilege: BlocklistApiCallsInterface = [ + { + method: 'delete', + info: 'delete single item', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}?item_id=${blocklistData.artifact.item_id}&namespace_type=${blocklistData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + ]; + + const needsReadPrivilege: BlocklistApiCallsInterface = [ + { + method: 'get', + info: 'single item', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}?item_id=${blocklistData.artifact.item_id}&namespace_type=${blocklistData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + { + method: 'get', + info: 'list summary', + get path() { + return `${EXCEPTION_LIST_URL}/summary?list_id=${blocklistData.artifact.list_id}&namespace_type=${blocklistData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + { + method: 'get', + info: 'find items', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${blocklistData.artifact.list_id}&namespace_type=${blocklistData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`; + }, + getBody: () => undefined, + }, + { + method: 'post', + info: 'list export', + get path() { + return `${EXCEPTION_LIST_URL}/_export?list_id=${blocklistData.artifact.list_id}&namespace_type=${blocklistData.artifact.namespace_type}&id=${blocklistData.artifact.id}`; + }, + getBody: () => undefined, + }, + ]; + describe('and has authorization to manage endpoint security', () => { for (const blocklistApiCall of blocklistApiCalls) { it(`should error on [${blocklistApiCall.method}] if invalid condition entry fields are used`, async () => { const body = blocklistApiCall.getBody(); body.entries[0].field = 'some.invalid.field'; - await supertest[blocklistApiCall.method](blocklistApiCall.path) + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -136,7 +175,8 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertest[blocklistApiCall.method](blocklistApiCall.path) + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -156,7 +196,8 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertest[blocklistApiCall.method](blocklistApiCall.path) + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -182,7 +223,8 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertest[blocklistApiCall.method](blocklistApiCall.path) + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -215,7 +257,8 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertest[blocklistApiCall.method](blocklistApiCall.path) + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -228,7 +271,8 @@ export default function ({ getService }: FtrProviderContext) { body.os_types = ['linux', 'windows']; - await supertest[blocklistApiCall.method](blocklistApiCall.path) + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -241,6 +285,7 @@ export default function ({ getService }: FtrProviderContext) { body.tags = [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`]; + // Using superuser here as we need custom license for this action await supertest[blocklistApiCall.method](blocklistApiCall.path) .set('kbn-xsrf', 'true') .send(body) @@ -249,57 +294,51 @@ export default function ({ getService }: FtrProviderContext) { .expect(anErrorMessageWith(/invalid policy ids/)); }); } + for (const blocklistApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { + it(`should not error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') + .set('kbn-xsrf', 'true') + .send(blocklistApiCall.getBody()) + .expect(200); + }); + } }); - describe('and user DOES NOT have authorization to manage endpoint security', () => { - const allblocklistApiCalls: BlocklistApiCallsInterface = [ - ...blocklistApiCalls, - { - method: 'get', - info: 'single item', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}?item_id=${blocklistData.artifact.item_id}&namespace_type=${blocklistData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'get', - info: 'list summary', - get path() { - return `${EXCEPTION_LIST_URL}/summary?list_id=${blocklistData.artifact.list_id}&namespace_type=${blocklistData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'delete', - info: 'single item', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}?item_id=${blocklistData.artifact.item_id}&namespace_type=${blocklistData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'post', - info: 'list export', - get path() { - return `${EXCEPTION_LIST_URL}/_export?list_id=${blocklistData.artifact.list_id}&namespace_type=${blocklistData.artifact.namespace_type}&id=1`; - }, - getBody: () => undefined, - }, - { - method: 'get', - info: 'single items', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${blocklistData.artifact.list_id}&namespace_type=${blocklistData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`; - }, - getBody: () => undefined, - }, - ]; + describe('and user has authorization to read blocklist', () => { + for (const blocklistApiCall of [...blocklistApiCalls, ...needsWritePrivilege]) { + it(`should error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.t2_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send(blocklistApiCall.getBody()) + .expect(403, { + status_code: 403, + message: 'EndpointArtifactError: Endpoint authorization failure', + }); + }); + } - for (const blocklistApiCall of allblocklistApiCalls) { - it(`should error on [${blocklistApiCall.method}]`, async () => { + for (const blocklistApiCall of needsReadPrivilege) { + it(`should not error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { + await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) + .auth(ROLE.t2_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send(blocklistApiCall.getBody()) + .expect(200); + }); + } + }); + + describe('and user has no authorization to blocklist', () => { + for (const blocklistApiCall of [ + ...blocklistApiCalls, + ...needsWritePrivilege, + ...needsReadPrivilege, + ]) { + it(`should error on [${blocklistApiCall.method}] - [${blocklistApiCall.info}]`, async () => { await supertestWithoutAuth[blocklistApiCall.method](blocklistApiCall.path) - .auth(ROLES.detections_admin, 'changeme') + .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') .send(blocklistApiCall.getBody()) .expect(403, { diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/event_filters.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/event_filters.ts index 517c090605a85..2bcfdb5baa934 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/event_filters.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/event_filters.ts @@ -17,11 +17,7 @@ import { import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts'; -import { - createUserAndRole, - deleteUserAndRole, - ROLES, -} from '../../../common/services/security_solution'; +import { ROLE } from '../../services/roles_users'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -30,24 +26,17 @@ export default function ({ getService }: FtrProviderContext) { const endpointArtifactTestResources = getService('endpointArtifactTestResources'); describe('Endpoint artifacts (via lists plugin): Event Filters', () => { - const USER = ROLES.detections_admin; let fleetEndpointPolicy: PolicyTestResourceInfo; before(async () => { // Create an endpoint policy in fleet we can work with fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy(); - - // create role/user - await createUserAndRole(getService, USER); }); after(async () => { if (fleetEndpointPolicy) { await fleetEndpointPolicy.cleanup(); } - - // delete role/user - await deleteUserAndRole(getService, USER); }); const anEndpointArtifactError = (res: { body: { message: string } }) => { @@ -88,6 +77,7 @@ export default function ({ getService }: FtrProviderContext) { const eventFilterCalls: EventFilterApiCallsInterface = [ { method: 'post', + info: 'create single item', path: EXCEPTION_LIST_ITEM_URL, getBody: (overrides = {}) => exceptionsGenerator.generateEventFilterForCreate({ @@ -97,6 +87,7 @@ export default function ({ getService }: FtrProviderContext) { }, { method: 'put', + info: 'update single item', path: EXCEPTION_LIST_ITEM_URL, getBody: (overrides = {}) => exceptionsGenerator.generateEventFilterForUpdate({ @@ -109,6 +100,52 @@ export default function ({ getService }: FtrProviderContext) { }, ]; + const needsWritePrivilege: EventFilterApiCallsInterface = [ + { + method: 'delete', + info: 'delete single item', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}?item_id=${eventFilterData.artifact.item_id}&namespace_type=${eventFilterData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + ]; + + const needsReadPrivilege: EventFilterApiCallsInterface = [ + { + method: 'get', + info: 'single item', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}?item_id=${eventFilterData.artifact.item_id}&namespace_type=${eventFilterData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + { + method: 'get', + info: 'list summary', + get path() { + return `${EXCEPTION_LIST_URL}/summary?list_id=${eventFilterData.artifact.list_id}&namespace_type=${eventFilterData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + { + method: 'get', + info: 'find items', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${eventFilterData.artifact.list_id}&namespace_type=${eventFilterData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`; + }, + getBody: () => undefined, + }, + { + method: 'post', + info: 'list export', + get path() { + return `${EXCEPTION_LIST_URL}/_export?list_id=${eventFilterData.artifact.list_id}&namespace_type=${eventFilterData.artifact.namespace_type}&id=${eventFilterData.artifact.id}`; + }, + getBody: () => undefined, + }, + ]; + beforeEach(async () => { eventFilterData = await endpointArtifactTestResources.createEventFilter({ tags: [`${BY_POLICY_ARTIFACT_TAG_PREFIX}${fleetEndpointPolicy.packagePolicy.id}`], @@ -140,13 +177,14 @@ export default function ({ getService }: FtrProviderContext) { }); describe('and has authorization to manage endpoint security', () => { - for (const eventFilterCall of eventFilterCalls) { - it(`should error on [${eventFilterCall.method}] if invalid field`, async () => { - const body = eventFilterCall.getBody({}); + for (const eventFilterApiCall of eventFilterCalls) { + it(`should error on [${eventFilterApiCall.method}] if invalid field`, async () => { + const body = eventFilterApiCall.getBody({}); body.entries[0].field = 'some.invalid.field'; - await supertest[eventFilterCall.method](eventFilterCall.path) + await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -154,10 +192,11 @@ export default function ({ getService }: FtrProviderContext) { .expect(anErrorMessageWith(/invalid field: some\.invalid\.field/)); }); - it(`should error on [${eventFilterCall.method}] if more than one OS is set`, async () => { - const body = eventFilterCall.getBody({ os_types: ['linux', 'windows'] }); + it(`should error on [${eventFilterApiCall.method}] if more than one OS is set`, async () => { + const body = eventFilterApiCall.getBody({ os_types: ['linux', 'windows'] }); - await supertest[eventFilterCall.method](eventFilterCall.path) + await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -165,12 +204,14 @@ export default function ({ getService }: FtrProviderContext) { .expect(anErrorMessageWith(/\[osTypes\]: array size is \[2\]/)); }); - it(`should error on [${eventFilterCall.method}] if policy id is invalid`, async () => { - const body = eventFilterCall.getBody({ + it(`should error on [${eventFilterApiCall.method}] if policy id is invalid`, async () => { + const body = eventFilterApiCall.getBody({ tags: [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`], }); - await supertest[eventFilterCall.method](eventFilterCall.path) + // Using superuser there as we need custom license for this action + await supertest[eventFilterApiCall.method](eventFilterApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -178,10 +219,12 @@ export default function ({ getService }: FtrProviderContext) { .expect(anErrorMessageWith(/invalid policy ids/)); }); - it(`should work on [${eventFilterCall.method}] with valid entry`, async () => { - const body = eventFilterCall.getBody({}); + it(`should work on [${eventFilterApiCall.method}] with valid entry`, async () => { + const body = eventFilterApiCall.getBody({}); - await supertest[eventFilterCall.method](eventFilterCall.path) + // Using superuser here as we need custom license for this action + await supertest[eventFilterApiCall.method](eventFilterApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(200); @@ -190,63 +233,53 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(deleteUrl).set('kbn-xsrf', 'true'); }); } + for (const eventFilterApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { + it(`should not error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { + await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') + .set('kbn-xsrf', 'true') + .send(eventFilterApiCall.getBody()) + .expect(200); + }); + } }); - describe(`and user (${USER}) DOES NOT have authorization to manage endpoint security`, () => { - // Define a new array that includes the prior set from above, plus additional API calls that - // only have Authz validations setup - const allApiCalls: EventFilterApiCallsInterface = [ - ...eventFilterCalls, - { - method: 'get', - info: 'single item', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}?item_id=${eventFilterData.artifact.item_id}&namespace_type=${eventFilterData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'get', - info: 'list summary', - get path() { - return `${EXCEPTION_LIST_URL}/summary?list_id=${eventFilterData.artifact.list_id}&namespace_type=${eventFilterData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'delete', - info: 'single item', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}?item_id=${eventFilterData.artifact.item_id}&namespace_type=${eventFilterData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'post', - info: 'list export', - get path() { - return `${EXCEPTION_LIST_URL}/_export?list_id=${eventFilterData.artifact.list_id}&namespace_type=${eventFilterData.artifact.namespace_type}&id=1`; - }, - getBody: () => undefined, - }, - { - method: 'get', - info: 'find items', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${eventFilterData.artifact.list_id}&namespace_type=${eventFilterData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`; - }, - getBody: () => undefined, - }, - ]; + describe('and user has authorization to read event filters', () => { + for (const eventFilterApiCall of [...eventFilterCalls, ...needsWritePrivilege]) { + it(`should error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { + await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) + .auth(ROLE.t2_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send(eventFilterApiCall.getBody()) + .expect(403, { + status_code: 403, + message: 'EndpointArtifactError: Endpoint authorization failure', + }); + }); + } - for (const apiCall of allApiCalls) { - it(`should error on [${apiCall.method}]${ - apiCall.info ? ` ${apiCall.info}` : '' - }`, async () => { - await supertestWithoutAuth[apiCall.method](apiCall.path) - .auth(ROLES.detections_admin, 'changeme') + for (const eventFilterApiCall of needsReadPrivilege) { + it(`should not error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { + await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) + .auth(ROLE.t2_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send(eventFilterApiCall.getBody()) + .expect(200); + }); + } + }); + + describe('and user has no authorization to event filters', () => { + for (const eventFilterApiCall of [ + ...eventFilterCalls, + ...needsWritePrivilege, + ...needsReadPrivilege, + ]) { + it(`should error on [${eventFilterApiCall.method}] - [${eventFilterApiCall.info}]`, async () => { + await supertestWithoutAuth[eventFilterApiCall.method](eventFilterApiCall.path) + .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') - .send(apiCall.getBody()) + .send(eventFilterApiCall.getBody()) .expect(403, { status_code: 403, message: 'EndpointArtifactError: Endpoint authorization failure', diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/host_isolation_exceptions.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/host_isolation_exceptions.ts index acbc72b479544..08f0d33faca4c 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/host_isolation_exceptions.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/host_isolation_exceptions.ts @@ -20,32 +20,17 @@ import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/commo import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts'; -import { - createUserAndRole, - deleteUserAndRole, - ROLES, -} from '../../../common/services/security_solution'; +import { ROLE } from '../../services/roles_users'; export default function ({ getService }: FtrProviderContext) { - const USER = ROLES.detections_admin; const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const endpointPolicyTestResources = getService('endpointPolicyTestResources'); const endpointArtifactTestResources = getService('endpointArtifactTestResources'); - const log = getService('log'); - - type ApiCallsInterface = Array<{ - method: keyof Pick; - info?: string; - path: string; - // The body just needs to have the properties we care about in the tests. This should cover most - // mocks used for testing that support different interfaces - getBody: () => BodyReturnType; - }>; describe('Endpoint Host Isolation Exceptions artifacts (via lists plugin)', () => { let fleetEndpointPolicy: PolicyTestResourceInfo; - let existingExceptionData: ArtifactTestData; + let hostIsolationExceptionData: ArtifactTestData; const exceptionsGenerator = new ExceptionsListItemGenerator(); @@ -64,57 +49,115 @@ export default function ({ getService }: FtrProviderContext) { } }; }; + type UnknownBodyGetter = () => unknown; + type PutPostBodyGetter = ( + overrides?: Partial + ) => Pick< + ExceptionListItemSchema, + 'item_id' | 'namespace_type' | 'os_types' | 'tags' | 'entries' + >; + type HostIsolationExceptionApiCallsInterface = Array<{ + method: keyof Pick; + info?: string; + path: string; + // The body just needs to have the properties we care about in the tests. This should cover most + // mocks used for testing that support different interfaces + getBody: BodyGetter; + }>; + + const hostIsolationExceptionCalls: HostIsolationExceptionApiCallsInterface = + [ + { + method: 'post', + info: 'create single item', + path: EXCEPTION_LIST_ITEM_URL, + getBody: () => + exceptionsGenerator.generateHostIsolationExceptionForCreate({ + tags: [GLOBAL_ARTIFACT_TAG], + }), + }, + { + method: 'put', + info: 'update single item', + path: EXCEPTION_LIST_ITEM_URL, + getBody: () => + exceptionsGenerator.generateHostIsolationExceptionForUpdate({ + id: hostIsolationExceptionData.artifact.id, + item_id: hostIsolationExceptionData.artifact.item_id, + _version: hostIsolationExceptionData.artifact._version, + tags: [GLOBAL_ARTIFACT_TAG], + }), + }, + ]; - const apiCalls: ApiCallsInterface< - Pick - > = [ + const needsWritePrivilege: HostIsolationExceptionApiCallsInterface = [ { - method: 'post', - path: EXCEPTION_LIST_ITEM_URL, - getBody: () => - exceptionsGenerator.generateHostIsolationExceptionForCreate({ - tags: [GLOBAL_ARTIFACT_TAG], - }), + method: 'delete', + info: 'delete single item', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}?item_id=${hostIsolationExceptionData.artifact.item_id}&namespace_type=${hostIsolationExceptionData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + ]; + + const needsReadPrivilege: HostIsolationExceptionApiCallsInterface = [ + { + method: 'get', + info: 'single item', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}?item_id=${hostIsolationExceptionData.artifact.item_id}&namespace_type=${hostIsolationExceptionData.artifact.namespace_type}`; + }, + getBody: () => undefined, }, { - method: 'put', - path: EXCEPTION_LIST_ITEM_URL, - getBody: () => - exceptionsGenerator.generateHostIsolationExceptionForUpdate({ - id: existingExceptionData.artifact.id, - item_id: existingExceptionData.artifact.item_id, - _version: existingExceptionData.artifact._version, - tags: [GLOBAL_ARTIFACT_TAG], - }), + method: 'get', + info: 'list summary', + get path() { + return `${EXCEPTION_LIST_URL}/summary?list_id=${hostIsolationExceptionData.artifact.list_id}&namespace_type=${hostIsolationExceptionData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + { + method: 'get', + info: 'find items', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${hostIsolationExceptionData.artifact.list_id}&namespace_type=${hostIsolationExceptionData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`; + }, + getBody: () => undefined, + }, + { + method: 'post', + info: 'list export', + get path() { + return `${EXCEPTION_LIST_URL}/_export?list_id=${hostIsolationExceptionData.artifact.list_id}&namespace_type=${hostIsolationExceptionData.artifact.namespace_type}&id=${hostIsolationExceptionData.artifact.id}`; + }, + getBody: () => undefined, }, ]; before(async () => { // Create an endpoint policy in fleet we can work with fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy(); - - // create role/user - await createUserAndRole(getService, USER); }); after(async () => { if (fleetEndpointPolicy) { await fleetEndpointPolicy.cleanup(); } - - // delete role/user - await deleteUserAndRole(getService, USER); }); beforeEach(async () => { - existingExceptionData = await endpointArtifactTestResources.createHostIsolationException({ - tags: [`${BY_POLICY_ARTIFACT_TAG_PREFIX}${fleetEndpointPolicy.packagePolicy.id}`], - }); + hostIsolationExceptionData = await endpointArtifactTestResources.createHostIsolationException( + { + tags: [`${BY_POLICY_ARTIFACT_TAG_PREFIX}${fleetEndpointPolicy.packagePolicy.id}`], + } + ); }); afterEach(async () => { - if (existingExceptionData) { - await existingExceptionData.cleanup(); + if (hostIsolationExceptionData) { + await hostIsolationExceptionData.cleanup(); } }); @@ -126,7 +169,7 @@ export default function ({ getService }: FtrProviderContext) { 'file', Buffer.from( toNdJsonString([ - getImportExceptionsListSchemaMock(existingExceptionData.artifact.list_id), + getImportExceptionsListSchemaMock(hostIsolationExceptionData.artifact.list_id), ]) ), 'exceptions.ndjson' @@ -139,160 +182,150 @@ export default function ({ getService }: FtrProviderContext) { }); describe('and has authorization to manage endpoint security', () => { - describe('should error on ', () => { - for (const apiCall of apiCalls) { - it(`[${apiCall.method}] if invalid condition entry fields are used`, async () => { - const body = apiCall.getBody(); - - body.entries[0].field = 'some.invalid.field'; - - await supertest[apiCall.method](apiCall.path) - .set('kbn-xsrf', 'true') - .send(body) - .expect(400) - .expect(anEndpointArtifactError) - .expect(anErrorMessageWith(/expected value to equal \[destination.ip\]/)); - }); - - it(`[${apiCall.method}] if more than one entry`, async () => { - const body = apiCall.getBody(); - - body.entries.push({ ...body.entries[0] }); - - await supertest[apiCall.method](apiCall.path) - .set('kbn-xsrf', 'true') - .send(body) - .expect(400) - .expect(anEndpointArtifactError) - .expect(anErrorMessageWith(/\[entries\]: array size is \[2\]/)); - }); - - it(`[${apiCall.method}] if an invalid ip is used`, async () => { - const body = apiCall.getBody(); - - body.entries = [ - { - field: 'destination.ip', - operator: 'included', - type: 'match', - value: 'not.an.ip', - }, - ]; - - await supertest[apiCall.method](apiCall.path) - .set('kbn-xsrf', 'true') - .send(body) - .expect(400) - .expect(anEndpointArtifactError) - .expect(anErrorMessageWith(/invalid ip/)); - }); - - it(`[${apiCall.method}] if all OSs for os_types are not included`, async () => { - const body = apiCall.getBody(); - - body.os_types = ['linux', 'windows']; - - await supertest[apiCall.method](apiCall.path) - .set('kbn-xsrf', 'true') - .send(body) - .expect(400) - .expect(anEndpointArtifactError) - .expect(anErrorMessageWith(/\[osTypes\]: array size is \[2\]/)); - }); - - it(`[${apiCall.method}] if policy id is invalid`, async () => { - const body = apiCall.getBody(); - - body.tags = [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`]; - - await supertest[apiCall.method](apiCall.path) - .set('kbn-xsrf', 'true') - .send(body) - .expect(400) - .expect(anEndpointArtifactError) - .expect(anErrorMessageWith(/invalid policy ids/)); - }); - } - }); + for (const hostIsolationExceptionApiCall of hostIsolationExceptionCalls) { + it(`[${hostIsolationExceptionApiCall.method}] if invalid condition entry fields are used`, async () => { + const body = hostIsolationExceptionApiCall.getBody(); - describe('should work on ', () => { - for (const apiCall of apiCalls) { - it(`[${apiCall.method}] if entry is valid`, async () => { - const body = apiCall.getBody(); + body.entries[0].field = 'some.invalid.field'; - await supertest[apiCall.method](apiCall.path) - .set('kbn-xsrf', 'true') - .on('error', (error) => { - log.error(JSON.stringify(error?.response?.body ?? error, null, 2)); - }) - .send(body) - .expect(200); + await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) + .auth(ROLE.analyst_hunter, 'changeme') + .set('kbn-xsrf', 'true') + .send(body) + .expect(400) + .expect(anEndpointArtifactError) + .expect(anErrorMessageWith(/expected value to equal \[destination.ip\]/)); + }); - const deleteUrl = `${EXCEPTION_LIST_ITEM_URL}?item_id=${body.item_id}&namespace_type=${body.namespace_type}`; + it(`[${hostIsolationExceptionApiCall.method}] if more than one entry`, async () => { + const body = hostIsolationExceptionApiCall.getBody(); - log.info(`cleaning up: ${deleteUrl}`); + body.entries.push({ ...body.entries[0] }); - await supertest.delete(deleteUrl).set('kbn-xsrf', 'true').expect(200); - }); - } - }); + await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) + .auth(ROLE.analyst_hunter, 'changeme') + .set('kbn-xsrf', 'true') + .send(body) + .expect(400) + .expect(anEndpointArtifactError) + .expect(anErrorMessageWith(/\[entries\]: array size is \[2\]/)); + }); + + it(`[${hostIsolationExceptionApiCall.method}] if an invalid ip is used`, async () => { + const body = hostIsolationExceptionApiCall.getBody(); + + body.entries = [ + { + field: 'destination.ip', + operator: 'included', + type: 'match', + value: 'not.an.ip', + }, + ]; + + await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) + .auth(ROLE.analyst_hunter, 'changeme') + .set('kbn-xsrf', 'true') + .send(body) + .expect(400) + .expect(anEndpointArtifactError) + .expect(anErrorMessageWith(/invalid ip/)); + }); + + it(`[${hostIsolationExceptionApiCall.method}] if all OSs for os_types are not included`, async () => { + const body = hostIsolationExceptionApiCall.getBody(); + + body.os_types = ['linux', 'windows']; + + await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) + .auth(ROLE.analyst_hunter, 'changeme') + .set('kbn-xsrf', 'true') + .send(body) + .expect(400) + .expect(anEndpointArtifactError) + .expect(anErrorMessageWith(/\[osTypes\]: array size is \[2\]/)); + }); + + it(`[${hostIsolationExceptionApiCall.method}] if policy id is invalid`, async () => { + const body = hostIsolationExceptionApiCall.getBody(); + + body.tags = [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`]; + + // Using superuser here as we need custom license for this action + await supertest[hostIsolationExceptionApiCall.method](hostIsolationExceptionApiCall.path) + .set('kbn-xsrf', 'true') + .send(body) + .expect(400) + .expect(anEndpointArtifactError) + .expect(anErrorMessageWith(/invalid policy ids/)); + }); + } + for (const hostIsolationExceptionApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { + it(`should not error on [${hostIsolationExceptionApiCall.method}] - [${hostIsolationExceptionApiCall.info}]`, async () => { + await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) + .auth(ROLE.analyst_hunter, 'changeme') + .set('kbn-xsrf', 'true') + .send(hostIsolationExceptionApiCall.getBody()) + .expect(200); + }); + } }); - describe(`and user (${USER}) DOES NOT have authorization to manage endpoint security`, () => { - // Define a new array that includes the prior set from above, plus additional API calls that - // only have Authz validations setup - const allApiCalls: ApiCallsInterface = [ - ...apiCalls, - { - method: 'get', - info: 'single item', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}?item_id=${existingExceptionData.artifact.item_id}&namespace_type=${existingExceptionData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'get', - info: 'list summary', - get path() { - return `${EXCEPTION_LIST_URL}/summary?list_id=${existingExceptionData.artifact.list_id}&namespace_type=${existingExceptionData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'delete', - info: 'single item', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}?item_id=${existingExceptionData.artifact.item_id}&namespace_type=${existingExceptionData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'post', - info: 'list export', - get path() { - return `${EXCEPTION_LIST_URL}/_export?list_id=${existingExceptionData.artifact.list_id}&namespace_type=${existingExceptionData.artifact.namespace_type}&id=1`; - }, - getBody: () => undefined, - }, - { - method: 'get', - info: 'find items', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${existingExceptionData.artifact.list_id}&namespace_type=${existingExceptionData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`; - }, - getBody: () => undefined, - }, - ]; + describe('and user has authorization to read host isolation exceptions', () => { + for (const hostIsolationExceptionApiCall of [ + ...hostIsolationExceptionCalls, + ...needsWritePrivilege, + ]) { + it(`should error on [${hostIsolationExceptionApiCall.method}] - [${hostIsolationExceptionApiCall.info}]`, async () => { + await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) + .auth(ROLE.t2_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send(hostIsolationExceptionApiCall.getBody()) + .expect(403, { + status_code: 403, + message: 'EndpointArtifactError: Endpoint authorization failure', + }); + }); + } + + for (const hostIsolationExceptionApiCall of needsReadPrivilege) { + it(`should not error on [${hostIsolationExceptionApiCall.method}] - [${hostIsolationExceptionApiCall.info}]`, async () => { + await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) + .auth(ROLE.t2_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send(hostIsolationExceptionApiCall.getBody()) + .expect(200); + }); + } + }); - for (const apiCall of allApiCalls) { - it(`should error on [${apiCall.method}]${ - apiCall.info ? ` ${apiCall.info}` : '' - }`, async () => { - await supertestWithoutAuth[apiCall.method](apiCall.path) - .auth(ROLES.detections_admin, 'changeme') + describe('and user has no authorization to host isolation exceptions', () => { + for (const hostIsolationExceptionApiCall of [ + ...hostIsolationExceptionCalls, + ...needsWritePrivilege, + ...needsReadPrivilege, + ]) { + it(`should error on [${hostIsolationExceptionApiCall.method}] - [${hostIsolationExceptionApiCall.info}]`, async () => { + await supertestWithoutAuth[hostIsolationExceptionApiCall.method]( + hostIsolationExceptionApiCall.path + ) + .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') - .send(apiCall.getBody()) + .send(hostIsolationExceptionApiCall.getBody()) .expect(403, { status_code: 403, message: 'EndpointArtifactError: Endpoint authorization failure', diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/trusted_apps.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/trusted_apps.ts index 041b0ea5794fb..bd1f4c1b519af 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/trusted_apps.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_artifacts/trusted_apps.ts @@ -16,11 +16,7 @@ import { ExceptionsListItemGenerator } from '@kbn/security-solution-plugin/commo import { FtrProviderContext } from '../../ftr_provider_context'; import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy'; import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts'; -import { - createUserAndRole, - deleteUserAndRole, - ROLES, -} from '../../../common/services/security_solution'; +import { ROLE } from '../../services/roles_users'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -32,20 +28,13 @@ export default function ({ getService }: FtrProviderContext) { let fleetEndpointPolicy: PolicyTestResourceInfo; before(async () => { - // Create an endpoint policy in fleet we can work with fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy(); - - // create role/user - await createUserAndRole(getService, ROLES.detections_admin); }); after(async () => { if (fleetEndpointPolicy) { await fleetEndpointPolicy.cleanup(); } - - // delete role/user - await deleteUserAndRole(getService, ROLES.detections_admin); }); const anEndpointArtifactError = (res: { body: { message: string } }) => { @@ -93,6 +82,7 @@ export default function ({ getService }: FtrProviderContext) { > = [ { method: 'post', + info: 'create single item', path: EXCEPTION_LIST_ITEM_URL, getBody: () => { return exceptionsGenerator.generateTrustedAppForCreate({ tags: [GLOBAL_ARTIFACT_TAG] }); @@ -100,6 +90,7 @@ export default function ({ getService }: FtrProviderContext) { }, { method: 'put', + info: 'update single item', path: EXCEPTION_LIST_ITEM_URL, getBody: () => exceptionsGenerator.generateTrustedAppForUpdate({ @@ -110,14 +101,61 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - describe('and has authorization to manage endpoint security', () => { + const needsWritePrivilege: TrustedAppApiCallsInterface = [ + { + method: 'delete', + info: 'delete single item', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}?item_id=${trustedAppData.artifact.item_id}&namespace_type=${trustedAppData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + ]; + + const needsReadPrivilege: TrustedAppApiCallsInterface = [ + { + method: 'get', + info: 'single item', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}?item_id=${trustedAppData.artifact.item_id}&namespace_type=${trustedAppData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + { + method: 'get', + info: 'list summary', + get path() { + return `${EXCEPTION_LIST_URL}/summary?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}`; + }, + getBody: () => undefined, + }, + { + method: 'get', + info: 'find items', + get path() { + return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`; + }, + getBody: () => undefined, + }, + { + method: 'post', + info: 'list export', + get path() { + return `${EXCEPTION_LIST_URL}/_export?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}&id=${trustedAppData.artifact.id}`; + }, + getBody: () => undefined, + }, + ]; + + describe('and has authorization to write trusted apps', () => { for (const trustedAppApiCall of trustedAppApiCalls) { it(`should error on [${trustedAppApiCall.method}] if invalid condition entry fields are used`, async () => { const body = trustedAppApiCall.getBody(); body.entries[0].field = 'some.invalid.field'; - await supertest[trustedAppApiCall.method](trustedAppApiCall.path) + await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -130,7 +168,8 @@ export default function ({ getService }: FtrProviderContext) { body.entries.push({ ...body.entries[0] }); - await supertest[trustedAppApiCall.method](trustedAppApiCall.path) + await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -150,7 +189,8 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertest[trustedAppApiCall.method](trustedAppApiCall.path) + await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -183,7 +223,8 @@ export default function ({ getService }: FtrProviderContext) { }, ]; - await supertest[trustedAppApiCall.method](trustedAppApiCall.path) + await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -196,7 +237,8 @@ export default function ({ getService }: FtrProviderContext) { body.os_types = ['linux', 'windows']; - await supertest[trustedAppApiCall.method](trustedAppApiCall.path) + await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -209,7 +251,9 @@ export default function ({ getService }: FtrProviderContext) { body.tags = [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`]; + // Using superuser here as we need custom license for this action await supertest[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') .set('kbn-xsrf', 'true') .send(body) .expect(400) @@ -217,57 +261,51 @@ export default function ({ getService }: FtrProviderContext) { .expect(anErrorMessageWith(/invalid policy ids/)); }); } + for (const trustedAppApiCall of [...needsWritePrivilege, ...needsReadPrivilege]) { + it(`should not error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { + await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.analyst_hunter, 'changeme') + .set('kbn-xsrf', 'true') + .send(trustedAppApiCall.getBody()) + .expect(200); + }); + } }); - describe('and user DOES NOT have authorization to manage endpoint security', () => { - const allTrustedAppApiCalls: TrustedAppApiCallsInterface = [ - ...trustedAppApiCalls, - { - method: 'get', - info: 'single item', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}?item_id=${trustedAppData.artifact.item_id}&namespace_type=${trustedAppData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'get', - info: 'list summary', - get path() { - return `${EXCEPTION_LIST_URL}/summary?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'delete', - info: 'single item', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}?item_id=${trustedAppData.artifact.item_id}&namespace_type=${trustedAppData.artifact.namespace_type}`; - }, - getBody: () => undefined, - }, - { - method: 'post', - info: 'list export', - get path() { - return `${EXCEPTION_LIST_URL}/_export?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}&id=1`; - }, - getBody: () => undefined, - }, - { - method: 'get', - info: 'single items', - get path() { - return `${EXCEPTION_LIST_ITEM_URL}/_find?list_id=${trustedAppData.artifact.list_id}&namespace_type=${trustedAppData.artifact.namespace_type}&page=1&per_page=1&sort_field=name&sort_order=asc`; - }, - getBody: () => undefined, - }, - ]; + describe('and user has authorization to read trusted apps', () => { + for (const trustedAppApiCall of [...trustedAppApiCalls, ...needsWritePrivilege]) { + it(`should error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { + await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.t2_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send(trustedAppApiCall.getBody()) + .expect(403, { + status_code: 403, + message: 'EndpointArtifactError: Endpoint authorization failure', + }); + }); + } - for (const trustedAppApiCall of allTrustedAppApiCalls) { - it(`should error on [${trustedAppApiCall.method}]`, async () => { + for (const trustedAppApiCall of needsReadPrivilege) { + it(`should not error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { + await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) + .auth(ROLE.t2_analyst, 'changeme') + .set('kbn-xsrf', 'true') + .send(trustedAppApiCall.getBody()) + .expect(200); + }); + } + }); + + describe('and user has no authorization to trusted apps', () => { + for (const trustedAppApiCall of [ + ...trustedAppApiCalls, + ...needsWritePrivilege, + ...needsReadPrivilege, + ]) { + it(`should error on [${trustedAppApiCall.method}] - [${trustedAppApiCall.info}]`, async () => { await supertestWithoutAuth[trustedAppApiCall.method](trustedAppApiCall.path) - .auth(ROLES.detections_admin, 'changeme') + .auth(ROLE.t1_analyst, 'changeme') .set('kbn-xsrf', 'true') .send(trustedAppApiCall.getBody()) .expect(403, { diff --git a/x-pack/test/security_solution_endpoint_api_int/config.ts b/x-pack/test/security_solution_endpoint_api_int/config.ts index 505d9734593b9..6e0d777c8d3ac 100644 --- a/x-pack/test/security_solution_endpoint_api_int/config.ts +++ b/x-pack/test/security_solution_endpoint_api_int/config.ts @@ -31,6 +31,8 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { `--xpack.fleet.packages.0.version=latest`, // this will be removed in 8.7 when the file upload feature is released `--xpack.fleet.enableExperimental.0=diagnosticFileUploadEnabled`, + // this will be removed in 8.7 when the artifacts RBAC is released + `--xpack.securitySolution.enableExperimental=${JSON.stringify(['endpointRbacEnabled'])}`, ], }, }; diff --git a/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts b/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts index c10b6a64658eb..365a00f804289 100644 --- a/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts +++ b/x-pack/test/security_solution_endpoint_api_int/services/roles_users.ts @@ -82,7 +82,7 @@ export function RolesUsersProvider({ getService }: FtrProviderContext) { ]; } - await security.role.create(predefinedRole, rolesMapping[predefinedRole]); + await security.role.create(predefinedRole, roleConfig); } if (customRole) { await security.role.create(customRole.roleName, {