mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution][Endpoint] Add Event Filters api validations get
, find
, delete
, export
, summary
and import
(#124071)
* Add additional validator methods to the `EventFilterValidator` * Event Filters validations for Delete, export, get one, multi/single list find and summary apis * FTR tests for Event filters get, delete, import, export, summary and find
This commit is contained in:
parent
ee081010d8
commit
ddb3f4f461
9 changed files with 194 additions and 23 deletions
|
@ -17,6 +17,10 @@ export const getExceptionsPreCreateItemHandler = (
|
|||
endpointAppContext: EndpointAppContextService
|
||||
): ValidatorCallback => {
|
||||
return async function ({ data, context: { request } }): Promise<CreateExceptionListItemOptions> {
|
||||
if (data.namespaceType !== 'agnostic') {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Validate trusted apps
|
||||
if (TrustedAppValidator.isTrustedApp(data)) {
|
||||
return new TrustedAppValidator(endpointAppContext, request).validatePreCreateItem(data);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { EndpointAppContextService } from '../../../endpoint/endpoint_app_contex
|
|||
import { ExceptionsListPreDeleteItemServerExtension } from '../../../../../lists/server';
|
||||
import { TrustedAppValidator } from '../validators/trusted_app_validator';
|
||||
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
|
||||
import { EventFilterValidator } from '../validators';
|
||||
|
||||
type ValidatorCallback = ExceptionsListPreDeleteItemServerExtension['callback'];
|
||||
export const getExceptionsPreDeleteItemHandler = (
|
||||
|
@ -31,13 +32,16 @@ export const getExceptionsPreDeleteItemHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
const { list_id: listId } = exceptionItem;
|
||||
|
||||
// Validate Trusted Applications
|
||||
if (TrustedAppValidator.isTrustedApp({ listId: exceptionItem.list_id })) {
|
||||
if (TrustedAppValidator.isTrustedApp({ listId })) {
|
||||
await new TrustedAppValidator(endpointAppContextService, request).validatePreDeleteItem();
|
||||
return data;
|
||||
}
|
||||
|
||||
// Host Isolation Exception
|
||||
if (HostIsolationExceptionsValidator.isHostIsolationException(exceptionItem.list_id)) {
|
||||
if (HostIsolationExceptionsValidator.isHostIsolationException(listId)) {
|
||||
await new HostIsolationExceptionsValidator(
|
||||
endpointAppContextService,
|
||||
request
|
||||
|
@ -45,6 +49,12 @@ export const getExceptionsPreDeleteItemHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
// Event Filter validation
|
||||
if (EventFilterValidator.isEventFilter({ listId })) {
|
||||
await new EventFilterValidator(endpointAppContextService, request).validatePreDeleteItem();
|
||||
return data;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,12 +9,17 @@ import { EndpointAppContextService } from '../../../endpoint/endpoint_app_contex
|
|||
import { ExceptionsListPreExportServerExtension } from '../../../../../lists/server';
|
||||
import { TrustedAppValidator } from '../validators/trusted_app_validator';
|
||||
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
|
||||
import { EventFilterValidator } from '../validators';
|
||||
|
||||
type ValidatorCallback = ExceptionsListPreExportServerExtension['callback'];
|
||||
export const getExceptionsPreExportHandler = (
|
||||
endpointAppContextService: EndpointAppContextService
|
||||
): ValidatorCallback => {
|
||||
return async function ({ data, context: { request, exceptionListClient } }) {
|
||||
if (data.namespaceType !== 'agnostic') {
|
||||
return data;
|
||||
}
|
||||
|
||||
const { listId: maybeListId, id } = data;
|
||||
let listId: string | null | undefined = maybeListId;
|
||||
|
||||
|
@ -40,6 +45,12 @@ export const getExceptionsPreExportHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
// Event Filter validations
|
||||
if (EventFilterValidator.isEventFilter({ listId })) {
|
||||
await new EventFilterValidator(endpointAppContextService, request).validatePreExport();
|
||||
return data;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -10,6 +10,7 @@ import { EndpointAppContextService } from '../../../endpoint/endpoint_app_contex
|
|||
import { ExceptionsListPreGetOneItemServerExtension } from '../../../../../lists/server';
|
||||
import { TrustedAppValidator } from '../validators/trusted_app_validator';
|
||||
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
|
||||
import { EventFilterValidator } from '../validators';
|
||||
|
||||
type ValidatorCallback = ExceptionsListPreGetOneItemServerExtension['callback'];
|
||||
export const getExceptionsPreGetOneHandler = (
|
||||
|
@ -31,13 +32,16 @@ export const getExceptionsPreGetOneHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
const listId = exceptionItem.list_id;
|
||||
|
||||
// Validate Trusted Applications
|
||||
if (TrustedAppValidator.isTrustedApp({ listId: exceptionItem.list_id })) {
|
||||
if (TrustedAppValidator.isTrustedApp({ listId })) {
|
||||
await new TrustedAppValidator(endpointAppContextService, request).validatePreGetOneItem();
|
||||
return data;
|
||||
}
|
||||
|
||||
// validate Host Isolation Exception
|
||||
if (HostIsolationExceptionsValidator.isHostIsolationException(exceptionItem.list_id)) {
|
||||
if (HostIsolationExceptionsValidator.isHostIsolationException(listId)) {
|
||||
await new HostIsolationExceptionsValidator(
|
||||
endpointAppContextService,
|
||||
request
|
||||
|
@ -45,6 +49,12 @@ export const getExceptionsPreGetOneHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
// Event Filters Exception
|
||||
if (EventFilterValidator.isEventFilter({ listId })) {
|
||||
await new EventFilterValidator(endpointAppContextService, request).validatePreGetOneItem();
|
||||
return data;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { EndpointAppContextService } from '../../../endpoint/endpoint_app_contex
|
|||
import { ExceptionsListPreMultiListFindServerExtension } from '../../../../../lists/server';
|
||||
import { TrustedAppValidator } from '../validators/trusted_app_validator';
|
||||
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
|
||||
import { EventFilterValidator } from '../validators';
|
||||
|
||||
type ValidatorCallback = ExceptionsListPreMultiListFindServerExtension['callback'];
|
||||
export const getExceptionsPreMultiListFindHandler = (
|
||||
|
@ -33,6 +34,12 @@ export const getExceptionsPreMultiListFindHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
// Event Filters Exceptions
|
||||
if (data.listId.some((listId) => EventFilterValidator.isEventFilter({ listId }))) {
|
||||
await new EventFilterValidator(endpointAppContextService, request).validatePreMultiListFind();
|
||||
return data;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import { EndpointAppContextService } from '../../../endpoint/endpoint_app_contex
|
|||
import { ExceptionsListPreSingleListFindServerExtension } from '../../../../../lists/server';
|
||||
import { TrustedAppValidator } from '../validators/trusted_app_validator';
|
||||
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
|
||||
import { EventFilterValidator } from '../validators';
|
||||
|
||||
type ValidatorCallback = ExceptionsListPreSingleListFindServerExtension['callback'];
|
||||
export const getExceptionsPreSingleListFindHandler = (
|
||||
|
@ -19,12 +20,16 @@ export const getExceptionsPreSingleListFindHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
const { listId } = data;
|
||||
|
||||
// Validate Host Isolation Exceptions
|
||||
if (TrustedAppValidator.isTrustedApp({ listId: data.listId })) {
|
||||
if (TrustedAppValidator.isTrustedApp({ listId })) {
|
||||
await new TrustedAppValidator(endpointAppContextService, request).validatePreSingleListFind();
|
||||
return data;
|
||||
}
|
||||
if (HostIsolationExceptionsValidator.isHostIsolationException(data.listId)) {
|
||||
|
||||
// Host Isolation Exceptions
|
||||
if (HostIsolationExceptionsValidator.isHostIsolationException(listId)) {
|
||||
await new HostIsolationExceptionsValidator(
|
||||
endpointAppContextService,
|
||||
request
|
||||
|
@ -32,6 +37,15 @@ export const getExceptionsPreSingleListFindHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
// Event Filters Exceptions
|
||||
if (EventFilterValidator.isEventFilter({ listId })) {
|
||||
await new EventFilterValidator(
|
||||
endpointAppContextService,
|
||||
request
|
||||
).validatePreSingleListFind();
|
||||
return data;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
|
||||
import { ExceptionsListPreSummaryServerExtension } from '../../../../../lists/server';
|
||||
import { TrustedAppValidator } from '../validators';
|
||||
import { TrustedAppValidator, EventFilterValidator } from '../validators';
|
||||
import { HostIsolationExceptionsValidator } from '../validators/host_isolation_exceptions_validator';
|
||||
|
||||
type ValidatorCallback = ExceptionsListPreSummaryServerExtension['callback'];
|
||||
|
@ -15,6 +15,10 @@ export const getExceptionsPreSummaryHandler = (
|
|||
endpointAppContextService: EndpointAppContextService
|
||||
): ValidatorCallback => {
|
||||
return async function ({ data, context: { request, exceptionListClient } }) {
|
||||
if (data.namespaceType !== 'agnostic') {
|
||||
return data;
|
||||
}
|
||||
|
||||
const { listId: maybeListId, id } = data;
|
||||
let listId: string | null | undefined = maybeListId;
|
||||
|
||||
|
@ -40,6 +44,12 @@ export const getExceptionsPreSummaryHandler = (
|
|||
return data;
|
||||
}
|
||||
|
||||
// Event Filter Exceptions
|
||||
if (EventFilterValidator.isEventFilter({ listId })) {
|
||||
await new EventFilterValidator(endpointAppContextService, request).validatePreSummary();
|
||||
return data;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -101,4 +101,34 @@ export class EventFilterValidator extends BaseValidator {
|
|||
throw new EndpointArtifactExceptionValidationError(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async validatePreGetOneItem(): Promise<void> {
|
||||
await this.validateCanManageEndpointArtifacts();
|
||||
}
|
||||
|
||||
async validatePreSummary(): Promise<void> {
|
||||
await this.validateCanManageEndpointArtifacts();
|
||||
}
|
||||
|
||||
async validatePreDeleteItem(): Promise<void> {
|
||||
await this.validateCanManageEndpointArtifacts();
|
||||
}
|
||||
|
||||
async validatePreExport(): Promise<void> {
|
||||
await this.validateCanManageEndpointArtifacts();
|
||||
}
|
||||
|
||||
async validatePreSingleListFind(): Promise<void> {
|
||||
await this.validateCanManageEndpointArtifacts();
|
||||
}
|
||||
|
||||
async validatePreMultiListFind(): Promise<void> {
|
||||
await this.validateCanManageEndpointArtifacts();
|
||||
}
|
||||
|
||||
async validatePreImport(): Promise<void> {
|
||||
throw new EndpointArtifactExceptionValidationError(
|
||||
'Import is not supported for Endpoint artifact exceptions'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants';
|
||||
import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
|
||||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import expect from '@kbn/expect';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
|
@ -18,6 +18,10 @@ import {
|
|||
deleteUserAndRole,
|
||||
ROLES,
|
||||
} from '../../../common/services/security_solution';
|
||||
import {
|
||||
getImportExceptionsListSchemaMock,
|
||||
toNdJsonString,
|
||||
} from '../../../../plugins/lists/common/schemas/request/import_exceptions_schema.mock';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertest');
|
||||
|
@ -26,6 +30,7 @@ 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 () => {
|
||||
|
@ -33,7 +38,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
fleetEndpointPolicy = await endpointPolicyTestResources.createPolicy();
|
||||
|
||||
// create role/user
|
||||
await createUserAndRole(getService, ROLES.detections_admin);
|
||||
await createUserAndRole(getService, USER);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
@ -42,7 +47,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
}
|
||||
|
||||
// delete role/user
|
||||
await deleteUserAndRole(getService, ROLES.detections_admin);
|
||||
await deleteUserAndRole(getService, USER);
|
||||
});
|
||||
|
||||
const anEndpointArtifactError = (res: { body: { message: string } }) => {
|
||||
|
@ -63,21 +68,25 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
const exceptionsGenerator = new ExceptionsListItemGenerator();
|
||||
let eventFilterData: ArtifactTestData;
|
||||
|
||||
type EventFilterApiCallsInterface = Array<{
|
||||
method: keyof Pick<typeof supertest, 'post' | 'put'>;
|
||||
type UnknownBodyGetter = () => unknown;
|
||||
type PutPostBodyGetter = (
|
||||
overrides?: Partial<ExceptionListItemSchema>
|
||||
) => Pick<ExceptionListItemSchema, 'os_types' | 'tags' | 'entries'>;
|
||||
|
||||
type EventFilterApiCallsInterface<BodyGetter = UnknownBodyGetter> = Array<{
|
||||
method: keyof Pick<typeof supertest, 'post' | 'put' | 'get' | 'delete' | 'patch'>;
|
||||
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: (
|
||||
overrides: Partial<ExceptionListItemSchema>
|
||||
) => Pick<ExceptionListItemSchema, 'os_types' | 'tags' | 'entries'>;
|
||||
getBody: BodyGetter;
|
||||
}>;
|
||||
|
||||
const eventFilterCalls: EventFilterApiCallsInterface = [
|
||||
const eventFilterCalls: EventFilterApiCallsInterface<PutPostBodyGetter> = [
|
||||
{
|
||||
method: 'post',
|
||||
path: EXCEPTION_LIST_ITEM_URL,
|
||||
getBody: (overrides) =>
|
||||
getBody: (overrides = {}) =>
|
||||
exceptionsGenerator.generateEventFilterForCreate({
|
||||
tags: eventFilterData.artifact.tags,
|
||||
...overrides,
|
||||
|
@ -86,7 +95,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
{
|
||||
method: 'put',
|
||||
path: EXCEPTION_LIST_ITEM_URL,
|
||||
getBody: (overrides) =>
|
||||
getBody: (overrides = {}) =>
|
||||
exceptionsGenerator.generateEventFilterForUpdate({
|
||||
id: eventFilterData.artifact.id,
|
||||
item_id: eventFilterData.artifact.item_id,
|
||||
|
@ -109,6 +118,24 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
}
|
||||
});
|
||||
|
||||
it('should return 400 for import of endpoint exceptions', async () => {
|
||||
await supertest
|
||||
.post(`${EXCEPTION_LIST_URL}/_import?overwrite=false`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.attach(
|
||||
'file',
|
||||
Buffer.from(
|
||||
toNdJsonString([getImportExceptionsListSchemaMock(eventFilterData.artifact.list_id)])
|
||||
),
|
||||
'exceptions.ndjson'
|
||||
)
|
||||
.expect(400, {
|
||||
status_code: 400,
|
||||
message:
|
||||
'EndpointArtifactError: Import is not supported for Endpoint artifact exceptions',
|
||||
});
|
||||
});
|
||||
|
||||
describe('and has authorization to manage endpoint security', () => {
|
||||
for (const eventFilterCall of eventFilterCalls) {
|
||||
it(`should error on [${eventFilterCall.method} if invalid field`, async () => {
|
||||
|
@ -159,13 +186,61 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
}
|
||||
});
|
||||
|
||||
describe('and user DOES NOT have authorization to manage endpoint security', () => {
|
||||
for (const eventFilterCall of eventFilterCalls) {
|
||||
it(`should 403 on [${eventFilterCall.method}]`, async () => {
|
||||
await supertestWithoutAuth[eventFilterCall.method](eventFilterCall.path)
|
||||
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<PutPostBodyGetter | UnknownBodyGetter> = [
|
||||
...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,
|
||||
},
|
||||
];
|
||||
|
||||
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')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(eventFilterCall.getBody({}))
|
||||
.send(apiCall.getBody())
|
||||
.expect(403, {
|
||||
status_code: 403,
|
||||
message: 'EndpointArtifactError: Endpoint authorization failure',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue