[Security Solution] add blocklist list (#126390)

This commit is contained in:
Joey F. Poon 2022-03-01 09:46:13 -06:00 committed by GitHub
parent e72dafc038
commit 4e3d2f62fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 285 additions and 9 deletions

View file

@ -14,6 +14,7 @@ export const exceptionListType = t.keyof({
endpoint_trusted_apps: null,
endpoint_events: null,
endpoint_host_isolation_exceptions: null,
endpoint_blocklists: null,
});
export const exceptionListTypeOrUndefined = t.union([exceptionListType, t.undefined]);
export type ExceptionListType = t.TypeOf<typeof exceptionListType>;
@ -24,4 +25,5 @@ export enum ExceptionListTypeEnum {
ENDPOINT_TRUSTED_APPS = 'endpoint',
ENDPOINT_EVENTS = 'endpoint_events',
ENDPOINT_HOST_ISOLATION_EXCEPTIONS = 'endpoint_host_isolation_exceptions',
ENDPOINT_BLOCKLISTS = 'endpoint_blocklists',
}

View file

@ -86,7 +86,7 @@ describe('Lists', () => {
const message = pipe(decoded, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "1" supplied to "Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}>"',
'Invalid value "1" supplied to "Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}>"',
]);
expect(message.schema).toEqual({});
});
@ -117,8 +117,8 @@ describe('Lists', () => {
const message = pipe(decoded, foldLeftRight);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "1" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}> | undefined)"',
'Invalid value "[1]" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions", namespace_type: "agnostic" | "single" |}> | undefined)"',
'Invalid value "1" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}> | undefined)"',
'Invalid value "[1]" supplied to "(Array<{| id: NonEmptyString, list_id: NonEmptyString, type: "detection" | "endpoint" | "endpoint_trusted_apps" | "endpoint_events" | "endpoint_host_isolation_exceptions" | "endpoint_blocklists", namespace_type: "agnostic" | "single" |}> | undefined)"',
]);
expect(message.schema).toEqual({});
});

View file

@ -76,3 +76,7 @@ export const ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_NAME =
'Endpoint Security Host isolation exceptions List';
export const ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_DESCRIPTION =
'Endpoint Security Host isolation exceptions List';
export const ENDPOINT_BLOCKLISTS_LIST_ID = 'endpoint_blocklists';
export const ENDPOINT_BLOCKLISTS_LIST_NAME = 'Endpoint Security Blocklists List';
export const ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION = 'Endpoint Security Blocklists List';

View file

@ -14,6 +14,7 @@ import {
ENDPOINT_EVENT_FILTERS_LIST_ID,
ENDPOINT_TRUSTED_APPS_LIST_ID,
ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
ENDPOINT_BLOCKLISTS_LIST_ID,
} from '@kbn/securitysolution-list-constants';
import { BaseDataGenerator } from './base_data_generator';
import { ConditionEntryField } from '../types';
@ -250,4 +251,70 @@ export class ExceptionsListItemGenerator extends BaseDataGenerator<ExceptionList
...overrides,
};
}
generateBlocklist(overrides: Partial<ExceptionListItemSchema> = {}): ExceptionListItemSchema {
return this.generate({
name: `Blocklist ${this.randomString(5)}`,
list_id: ENDPOINT_BLOCKLISTS_LIST_ID,
item_id: `generator_endpoint_blocklist_${this.randomUUID()}`,
os_types: ['windows'],
entries: [
this.randomChoice([
{
field: 'process.executable.caseless',
value: ['/some/path', 'some/other/path', 'yet/another/path'],
type: 'match_any',
operator: 'included',
},
{
field: 'process.hash.sha256',
value: [
'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3',
'2C26B46B68FFC68FF99B453C1D30413413422D706483BFA0F98A5E886266E7AE',
'FCDE2B2EDBA56BF408601FB721FE9B5C338D10EE429EA04FAE5511B68FBF8FB9',
],
type: 'match_any',
operator: 'included',
},
{
field: 'process.Ext.code_signature',
entries: [
{
field: 'trusted',
value: 'true',
type: 'match',
operator: 'included',
},
{
field: 'subject_name',
value: ['notsus.exe', 'verynotsus.exe', 'superlegit.exe'],
type: 'match_any',
operator: 'included',
},
],
type: 'nested',
},
]),
],
...overrides,
});
}
generateBlocklistForCreate(
overrides: Partial<CreateExceptionListItemSchema> = {}
): CreateExceptionListItemSchemaWithNonNullProps {
return {
...exceptionItemToCreateExceptionItem(this.generateBlocklist()),
...overrides,
};
}
generateBlocklistForUpdate(
overrides: Partial<UpdateExceptionListItemSchema> = {}
): UpdateExceptionListItemSchemaWithNonNullProps {
return {
...exceptionItemToUpdateExceptionItem(this.generateBlocklist()),
...overrides,
};
}
}

View file

@ -0,0 +1,26 @@
/*
* 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 {
ExceptionListType,
ExceptionListTypeEnum,
CreateExceptionListSchema,
} from '@kbn/securitysolution-io-ts-list-types';
import {
ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION,
ENDPOINT_BLOCKLISTS_LIST_ID,
ENDPOINT_BLOCKLISTS_LIST_NAME,
} from '@kbn/securitysolution-list-constants';
export const BLOCKLISTS_LIST_TYPE: ExceptionListType = ExceptionListTypeEnum.ENDPOINT_BLOCKLISTS;
export const BLOCKLISTS_LIST_DEFINITION: CreateExceptionListSchema = {
name: ENDPOINT_BLOCKLISTS_LIST_NAME,
namespace_type: 'agnostic',
description: ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION,
list_id: ENDPOINT_BLOCKLISTS_LIST_ID,
type: BLOCKLISTS_LIST_TYPE,
};

View file

@ -0,0 +1,26 @@
/*
* 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 { ENDPOINT_BLOCKLISTS_LIST_ID } from '@kbn/securitysolution-list-constants';
import { HttpStart } from 'kibana/public';
import { ExceptionsListApiClient } from '../../../services/exceptions_list/exceptions_list_api_client';
import { BLOCKLISTS_LIST_DEFINITION } from '../constants';
/**
* Blocklist exceptions Api client class using ExceptionsListApiClient as base class
* It follow the Singleton pattern.
* Please, use the getInstance method instead of creating a new instance when using this implementation.
*/
export class BlocklistsApiClient extends ExceptionsListApiClient {
constructor(http: HttpStart) {
super(http, ENDPOINT_BLOCKLISTS_LIST_ID, BLOCKLISTS_LIST_DEFINITION);
}
public static getInstance(http: HttpStart): ExceptionsListApiClient {
return super.getInstance(http, ENDPOINT_BLOCKLISTS_LIST_ID, BLOCKLISTS_LIST_DEFINITION);
}
}

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './blocklists_api_client';

View file

@ -9,7 +9,7 @@ import React, { memo } from 'react';
import { i18n } from '@kbn/i18n';
import { useHttp } from '../../../../common/lib/kibana';
import { ArtifactListPage, ArtifactListPageProps } from '../../../components/artifact_list_page';
import { HostIsolationExceptionsApiClient } from '../../host_isolation_exceptions/host_isolation_exceptions_api_client';
import { BlocklistsApiClient } from '../services';
// FIXME:PT delete this when real component is implemented
const TempDevFormComponent: ArtifactListPageProps['ArtifactFormComponent'] = (props) => {
@ -39,7 +39,7 @@ const BLOCKLIST_PAGE_LABELS: ArtifactListPageProps['labels'] = {
defaultMessage: 'Blocklist',
}),
pageAboutInfo: i18n.translate('xpack.securitySolution.blocklist.pageAboutInfo', {
defaultMessage: '(DEV: temporarily using isolation exception api)', // FIXME: need wording from PM
defaultMessage: 'Add a blocklist to block applications or files from running.',
}),
pageAddButtonTitle: i18n.translate('xpack.securitySolution.blocklist.pageAddButtonTitle', {
defaultMessage: 'Add blocklist entry',
@ -118,13 +118,11 @@ const BLOCKLIST_PAGE_LABELS: ArtifactListPageProps['labels'] = {
export const Blocklist = memo(() => {
const http = useHttp();
// FIXME: Implement Blocklist API client and define list
// for now, just using Event Filters
const eventFiltersApiClient = HostIsolationExceptionsApiClient.getInstance(http);
const blocklistsApiClient = BlocklistsApiClient.getInstance(http);
return (
<ArtifactListPage
apiClient={eventFiltersApiClient}
apiClient={blocklistsApiClient}
ArtifactFormComponent={TempDevFormComponent} // FIXME: Implement create/edit form
labels={BLOCKLIST_PAGE_LABELS}
data-test-subj="blocklistPage"

View file

@ -0,0 +1,124 @@
/*
* 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 { run, RunFn, createFailError } from '@kbn/dev-utils';
import { KbnClient } from '@kbn/test';
import { AxiosError } from 'axios';
import pMap from 'p-map';
import type { CreateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types';
import {
ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION,
ENDPOINT_BLOCKLISTS_LIST_ID,
ENDPOINT_BLOCKLISTS_LIST_NAME,
EXCEPTION_LIST_ITEM_URL,
EXCEPTION_LIST_URL,
} from '@kbn/securitysolution-list-constants';
import { randomPolicyIdGenerator } from '../common/random_policy_id_generator';
import { ExceptionsListItemGenerator } from '../../../common/endpoint/data_generators/exceptions_list_item_generator';
import { isArtifactByPolicy } from '../../../common/endpoint/service/artifacts';
export const cli = () => {
run(
async (options) => {
try {
await createBlocklists(options);
options.log.success(`${options.flags.count} endpoint blocklists created`);
} catch (e) {
options.log.error(e);
throw createFailError(e.message);
}
},
{
description: 'Load Endpoint Blocklists',
flags: {
string: ['kibana'],
default: {
count: 10,
kibana: 'http://elastic:changeme@localhost:5601',
},
help: `
--count Number of blocklists to create. Default: 10
--kibana The URL to kibana including credentials. Default: http://elastic:changeme@localhost:5601
`,
},
}
);
};
class BlocklistDataLoaderError extends Error {
constructor(message: string, public readonly meta: unknown) {
super(message);
}
}
const handleThrowAxiosHttpError = (err: AxiosError): never => {
let message = err.message;
if (err.response) {
message = `[${err.response.status}] ${err.response.data.message ?? err.message} [ ${String(
err.response.config.method
).toUpperCase()} ${err.response.config.url} ]`;
}
throw new BlocklistDataLoaderError(message, err.toJSON());
};
const createBlocklists: RunFn = async ({ flags, log }) => {
const eventGenerator = new ExceptionsListItemGenerator();
const kbn = new KbnClient({ log, url: flags.kibana as string });
await ensureCreateEndpointBlocklistsList(kbn);
const randomPolicyId = await randomPolicyIdGenerator(kbn, log);
await pMap(
Array.from({ length: flags.count as unknown as number }),
() => {
const body = eventGenerator.generateBlocklistForCreate();
if (isArtifactByPolicy(body)) {
const nmExceptions = Math.floor(Math.random() * 3) || 1;
body.tags = Array.from({ length: nmExceptions }, () => {
return `policy:${randomPolicyId()}`;
});
}
return kbn
.request({
method: 'POST',
path: EXCEPTION_LIST_ITEM_URL,
body,
})
.catch((e) => handleThrowAxiosHttpError(e));
},
{ concurrency: 10 }
);
};
const ensureCreateEndpointBlocklistsList = async (kbn: KbnClient) => {
const newListDefinition: CreateExceptionListSchema = {
description: ENDPOINT_BLOCKLISTS_LIST_DESCRIPTION,
list_id: ENDPOINT_BLOCKLISTS_LIST_ID,
meta: undefined,
name: ENDPOINT_BLOCKLISTS_LIST_NAME,
os_types: [],
tags: [],
type: 'endpoint',
namespace_type: 'agnostic',
};
await kbn
.request({
method: 'POST',
path: EXCEPTION_LIST_URL,
body: newListDefinition,
})
.catch((e) => {
// Ignore if list was already created
if (e.response.status !== 409) {
handleThrowAxiosHttpError(e);
}
});
};

View file

@ -0,0 +1,11 @@
#!/usr/bin/env node
/*
* 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.
*/
require('../../../../../src/setup_node_env');
require('./blocklists').cli();

View file

@ -18,6 +18,7 @@ import { TRUSTED_APPS_EXCEPTION_LIST_DEFINITION } from '../../../plugins/securit
import { EndpointError } from '../../../plugins/security_solution/common/endpoint/errors';
import { EVENT_FILTER_LIST_DEFINITION } from '../../../plugins/security_solution/public/management/pages/event_filters/constants';
import { HOST_ISOLATION_EXCEPTIONS_LIST_DEFINITION } from '../../../plugins/security_solution/public/management/pages/host_isolation_exceptions/constants';
import { BLOCKLISTS_LIST_DEFINITION } from '../../../plugins/security_solution/public/management/pages/blocklist/constants';
export interface ArtifactTestData {
artifact: ExceptionListItemSchema;
@ -108,4 +109,13 @@ export class EndpointArtifactsTestResources extends FtrService {
return this.createExceptionItem(artifact);
}
async createBlocklist(
overrides: Partial<CreateExceptionListItemSchema> = {}
): Promise<ArtifactTestData> {
await this.ensureListExists(BLOCKLISTS_LIST_DEFINITION);
const blocklist = this.exceptionsGenerator.generateBlocklistForCreate(overrides);
return this.createExceptionItem(blocklist);
}
}