[Security Solution] add api validation for event filters (#123776)

This commit is contained in:
Joey F. Poon 2022-01-28 13:31:49 -06:00 committed by GitHub
parent 258039a919
commit fa38d1ae0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 742 additions and 340 deletions

View file

@ -10,7 +10,10 @@ import type {
CreateExceptionListItemSchema,
UpdateExceptionListItemSchema,
} from '@kbn/securitysolution-io-ts-list-types';
import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '@kbn/securitysolution-list-constants';
import {
ENDPOINT_EVENT_FILTERS_LIST_ID,
ENDPOINT_TRUSTED_APPS_LIST_ID,
} from '@kbn/securitysolution-list-constants';
import { BaseDataGenerator } from './base_data_generator';
import { ConditionEntryField } from '../types';
import { BY_POLICY_ARTIFACT_TAG_PREFIX } from '../service/artifacts/constants';
@ -198,4 +201,84 @@ export class ExceptionsListItemGenerator extends BaseDataGenerator<ExceptionList
...overrides,
};
}
generateEventFilter(overrides: Partial<ExceptionListItemSchema> = {}): ExceptionListItemSchema {
const eventFilter = this.generate(overrides);
return {
...eventFilter,
name: `Event filter (${this.randomString(5)})`,
list_id: ENDPOINT_EVENT_FILTERS_LIST_ID,
};
}
generateEventFilterForCreate(
overrides: Partial<CreateExceptionListItemSchema> = {}
): CreateExceptionListItemSchemaWithNonNullProps {
const {
/* eslint-disable @typescript-eslint/naming-convention */
description,
entries,
list_id,
name,
type,
comments,
item_id,
meta,
namespace_type,
os_types,
tags,
/* eslint-enable @typescript-eslint/naming-convention */
} = this.generateEventFilter();
return {
description,
entries,
list_id,
name,
type,
comments,
item_id,
meta,
namespace_type,
os_types,
tags,
...overrides,
};
}
generateEventFilterForUpdate(
overrides: Partial<UpdateExceptionListItemSchema> = {}
): UpdateExceptionListItemSchemaWithNonNullProps {
const {
/* eslint-disable @typescript-eslint/naming-convention */
description,
entries,
name,
type,
comments,
id,
item_id,
meta,
namespace_type,
os_types,
tags,
_version,
/* eslint-enable @typescript-eslint/naming-convention */
} = this.generateEventFilter();
return {
description,
entries,
name,
type,
comments,
id,
item_id,
meta,
namespace_type,
os_types,
tags,
_version: _version ?? 'some value',
...overrides,
};
}
}

View file

@ -0,0 +1,339 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const EXCEPTIONABLE_ENDPOINT_EVENT_FIELDS = [
'@timestamp',
'agent.id',
'agent.name',
'agent.type',
'agent.version',
'data_stream.dataset',
'data_stream.namespace',
'data_stream.type',
'destination.address',
'destination.bytes',
'destination.domain',
'destination.geo.city_name',
'destination.geo.continent_name',
'destination.geo.country_iso_code',
'destination.geo.country_name',
'destination.geo.location',
'destination.geo.name',
'destination.geo.region_iso_code',
'destination.geo.region_name',
'destination.ip',
'destination.packets',
'destination.port',
'destination.registered_domain',
'destination.top_level_domain',
'dll.code_signature.exists',
'dll.code_signature.status',
'dll.code_signature.subject_name',
'dll.code_signature.trusted',
'dll.code_signature.valid',
'dll.Ext',
'dll.Ext.code_signature',
'dll.Ext.code_signature.exists',
'dll.Ext.code_signature.status',
'dll.Ext.code_signature.subject_name',
'dll.Ext.code_signature.trusted',
'dll.Ext.code_signature.valid',
'dll.Ext.load_index',
'dll.hash.md5',
'dll.hash.sha1',
'dll.hash.sha256',
'dll.hash.sha512',
'dll.name',
'dll.path',
'dll.pe.company',
'dll.pe.description',
'dll.pe.file_version',
'dll.pe.imphash',
'dll.pe.original_file_name',
'dll.pe.product',
'dns.Ext',
'dns.Ext.options',
'dns.Ext.status',
'dns.question.name',
'dns.question.registered_domain',
'dns.question.subdomain',
'dns.question.top_level_domain',
'dns.question.type',
'dns.resolved_ip',
'ecs.version',
'elastic.agent',
'elastic.agent.id',
'Endpoint.policy',
'Endpoint.policy.applied',
'Endpoint.policy.applied.id',
'Endpoint.policy.applied.name',
'Endpoint.policy.applied.status',
'Endpoint.status',
'event.action',
'event.category',
'event.code',
'event.created',
'event.dataset',
'event.Ext',
'event.Ext.correlation',
'event.Ext.correlation.id',
'event.hash',
'event.id',
'event.ingested',
'event.module',
'event.outcome',
'event.provider',
'event.sequence',
'event.severity',
'event.type',
'file.accessed',
'file.attributes',
'file.created',
'file.ctime',
'file.device',
'file.directory',
'file.drive_letter',
'file.Ext',
'file.Ext.code_signature',
'file.Ext.code_signature.exists',
'file.Ext.code_signature.status',
'file.Ext.code_signature.subject_name',
'file.Ext.code_signature.trusted',
'file.Ext.code_signature.valid',
'file.Ext.entropy',
'file.Ext.header_data',
'file.Ext.monotonic_id',
'file.Ext.original',
'file.Ext.original.gid',
'file.Ext.original.group',
'file.Ext.original.mode',
'file.Ext.original.name',
'file.Ext.original.owner',
'file.Ext.original.path',
'file.Ext.original.uid',
'file.Ext.windows',
'file.Ext.windows.zone_identifier',
'file.extension',
'file.gid',
'file.group',
'file.hash.md5',
'file.hash.sha1',
'file.hash.sha256',
'file.hash.sha512',
'file.inode',
'file.mime_type',
'file.mode',
'file.mtime',
'file.name',
'file.owner',
'file.path',
'file.path.caseless',
'file.path.text',
'file.pe.company',
'file.pe.description',
'file.pe.file_version',
'file.pe.imphash',
'file.pe.original_file_name',
'file.pe.product',
'file.size',
'file.target_path',
'file.target_path.caseless',
'file.target_path.text',
'file.type',
'file.uid',
'group.domain',
'group.Ext',
'group.Ext.real',
'group.Ext.real.id',
'group.Ext.real.name',
'group.id',
'group.name',
'host.architecture',
'host.domain',
'host.hostname',
'host.id',
'host.ip',
'host.mac',
'host.name',
'host.os.Ext',
'host.os.Ext.variant',
'host.os.family',
'host.os.full',
'host.os.full.caseless',
'host.os.full.text',
'host.os.kernel',
'host.os.name',
'host.os.name.caseless',
'host.os.name.text',
'host.os.platform',
'host.os.version',
'host.type',
'host.uptime',
'http.request.body.bytes',
'http.request.body.content',
'http.request.body.content.text',
'http.request.bytes',
'http.response.body.bytes',
'http.response.body.content',
'http.response.body.content.text',
'http.response.bytes',
'http.response.Ext',
'http.response.Ext.version',
'http.response.status_code',
'message',
'network.bytes',
'network.community_id',
'network.direction',
'network.iana_number',
'network.packets',
'network.protocol',
'network.transport',
'network.type',
'package.name',
'process.args',
'process.args_count',
'process.code_signature.exists',
'process.code_signature.status',
'process.code_signature.subject_name',
'process.code_signature.trusted',
'process.code_signature.valid',
'process.command_line',
'process.command_line.caseless',
'process.command_line.text',
'process.entity_id',
'process.executable',
'process.executable.caseless',
'process.executable.text',
'process.exit_code',
'process.Ext',
'process.Ext.ancestry',
'process.Ext.authentication_id',
'process.Ext.code_signature',
'process.Ext.code_signature.exists',
'process.Ext.code_signature.status',
'process.Ext.code_signature.subject_name',
'process.Ext.code_signature.trusted',
'process.Ext.code_signature.valid',
'process.Ext.defense_evasions',
'process.Ext.session',
'process.Ext.token.elevation',
'process.Ext.token.elevation_type',
'process.Ext.token.integrity_level_name',
'process.hash.md5',
'process.hash.sha1',
'process.hash.sha256',
'process.hash.sha512',
'process.name',
'process.name.caseless',
'process.name.text',
'process.parent.args',
'process.parent.args_count',
'process.parent.code_signature.exists',
'process.parent.code_signature.status',
'process.parent.code_signature.subject_name',
'process.parent.code_signature.trusted',
'process.parent.code_signature.valid',
'process.parent.command_line',
'process.parent.command_line.caseless',
'process.parent.command_line.text',
'process.parent.entity_id',
'process.parent.executable',
'process.parent.executable.caseless',
'process.parent.executable.text',
'process.parent.exit_code',
'process.parent.Ext',
'process.parent.Ext.code_signature',
'process.parent.Ext.code_signature.exists',
'process.parent.Ext.code_signature.status',
'process.parent.Ext.code_signature.subject_name',
'process.parent.Ext.code_signature.trusted',
'process.parent.Ext.code_signature.valid',
'process.parent.Ext.real',
'process.parent.Ext.real.pid',
'process.parent.hash.md5',
'process.parent.hash.sha1',
'process.parent.hash.sha256',
'process.parent.hash.sha512',
'process.parent.name',
'process.parent.name.caseless',
'process.parent.name.text',
'process.parent.pe.company',
'process.parent.pe.description',
'process.parent.pe.file_version',
'process.parent.pe.imphash',
'process.parent.pe.original_file_name',
'process.parent.pe.product',
'process.parent.pgid',
'process.parent.pid',
'process.parent.ppid',
'process.parent.thread.id',
'process.parent.thread.name',
'process.parent.title',
'process.parent.title.text',
'process.parent.uptime',
'process.parent.working_directory',
'process.parent.working_directory.caseless',
'process.parent.working_directory.text',
'process.pe.company',
'process.pe.description',
'process.pe.file_version',
'process.pe.imphash',
'process.pe.original_file_name',
'process.pe.product',
'process.pgid',
'process.pid',
'process.ppid',
'process.thread.id',
'process.thread.name',
'process.title',
'process.title.text',
'process.uptime',
'process.working_directory',
'process.working_directory.caseless',
'process.working_directory.text',
'registry.data.bytes',
'registry.data.strings',
'registry.hive',
'registry.key',
'registry.path',
'registry.value',
'source.address',
'source.bytes',
'source.domain',
'source.geo.city_name',
'source.geo.continent_name',
'source.geo.country_iso_code',
'source.geo.country_name',
'source.geo.location',
'source.geo.name',
'source.geo.region_iso_code',
'source.geo.region_name',
'source.ip',
'source.packets',
'source.port',
'source.registered_domain',
'source.top_level_domain',
'user.domain',
'user.email',
'user.Ext',
'user.Ext.real',
'user.Ext.real.id',
'user.Ext.real.name',
'user.full_name',
'user.full_name.text',
'user.group.domain',
'user.group.Ext',
'user.group.Ext.real',
'user.group.Ext.real.id',
'user.group.Ext.real.name',
'user.group.id',
'user.group.name',
'user.hash',
'user.id',
'user.name',
'user.name.text',
];

View file

@ -1,332 +0,0 @@
[
"@timestamp",
"agent.id",
"agent.name",
"agent.type",
"agent.version",
"data_stream.dataset",
"data_stream.namespace",
"data_stream.type",
"destination.address",
"destination.bytes",
"destination.domain",
"destination.geo.city_name",
"destination.geo.continent_name",
"destination.geo.country_iso_code",
"destination.geo.country_name",
"destination.geo.location",
"destination.geo.name",
"destination.geo.region_iso_code",
"destination.geo.region_name",
"destination.ip",
"destination.packets",
"destination.port",
"destination.registered_domain",
"destination.top_level_domain",
"dll.code_signature.exists",
"dll.code_signature.status",
"dll.code_signature.subject_name",
"dll.code_signature.trusted",
"dll.code_signature.valid",
"dll.Ext",
"dll.Ext.code_signature",
"dll.Ext.code_signature.exists",
"dll.Ext.code_signature.status",
"dll.Ext.code_signature.subject_name",
"dll.Ext.code_signature.trusted",
"dll.Ext.code_signature.valid",
"dll.Ext.load_index",
"dll.hash.md5",
"dll.hash.sha1",
"dll.hash.sha256",
"dll.hash.sha512",
"dll.name",
"dll.path",
"dll.pe.company",
"dll.pe.description",
"dll.pe.file_version",
"dll.pe.imphash",
"dll.pe.original_file_name",
"dll.pe.product",
"dns.Ext",
"dns.Ext.options",
"dns.Ext.status",
"dns.question.name",
"dns.question.registered_domain",
"dns.question.subdomain",
"dns.question.top_level_domain",
"dns.question.type",
"dns.resolved_ip",
"ecs.version",
"elastic.agent",
"elastic.agent.id",
"Endpoint.policy",
"Endpoint.policy.applied",
"Endpoint.policy.applied.id",
"Endpoint.policy.applied.name",
"Endpoint.policy.applied.status",
"Endpoint.status",
"event.action",
"event.category",
"event.code",
"event.created",
"event.dataset",
"event.Ext",
"event.Ext.correlation",
"event.Ext.correlation.id",
"event.hash",
"event.id",
"event.ingested",
"event.module",
"event.outcome",
"event.provider",
"event.sequence",
"event.severity",
"event.type",
"file.accessed",
"file.attributes",
"file.created",
"file.ctime",
"file.device",
"file.directory",
"file.drive_letter",
"file.Ext",
"file.Ext.code_signature",
"file.Ext.code_signature.exists",
"file.Ext.code_signature.status",
"file.Ext.code_signature.subject_name",
"file.Ext.code_signature.trusted",
"file.Ext.code_signature.valid",
"file.Ext.entropy",
"file.Ext.header_data",
"file.Ext.monotonic_id",
"file.Ext.original",
"file.Ext.original.gid",
"file.Ext.original.group",
"file.Ext.original.mode",
"file.Ext.original.name",
"file.Ext.original.owner",
"file.Ext.original.path",
"file.Ext.original.uid",
"file.Ext.windows",
"file.Ext.windows.zone_identifier",
"file.extension",
"file.gid",
"file.group",
"file.hash.md5",
"file.hash.sha1",
"file.hash.sha256",
"file.hash.sha512",
"file.inode",
"file.mime_type",
"file.mode",
"file.mtime",
"file.name",
"file.owner",
"file.path",
"file.path.caseless",
"file.path.text",
"file.pe.company",
"file.pe.description",
"file.pe.file_version",
"file.pe.imphash",
"file.pe.original_file_name",
"file.pe.product",
"file.size",
"file.target_path",
"file.target_path.caseless",
"file.target_path.text",
"file.type",
"file.uid",
"group.domain",
"group.Ext",
"group.Ext.real",
"group.Ext.real.id",
"group.Ext.real.name",
"group.id",
"group.name",
"host.architecture",
"host.domain",
"host.hostname",
"host.id",
"host.ip",
"host.mac",
"host.name",
"host.os.Ext",
"host.os.Ext.variant",
"host.os.family",
"host.os.full",
"host.os.full.caseless",
"host.os.full.text",
"host.os.kernel",
"host.os.name",
"host.os.name.caseless",
"host.os.name.text",
"host.os.platform",
"host.os.version",
"host.type",
"host.uptime",
"http.request.body.bytes",
"http.request.body.content",
"http.request.body.content.text",
"http.request.bytes",
"http.response.body.bytes",
"http.response.body.content",
"http.response.body.content.text",
"http.response.bytes",
"http.response.Ext",
"http.response.Ext.version",
"http.response.status_code",
"message",
"network.bytes",
"network.community_id",
"network.direction",
"network.iana_number",
"network.packets",
"network.protocol",
"network.transport",
"network.type",
"package.name",
"process.args",
"process.args_count",
"process.code_signature.exists",
"process.code_signature.status",
"process.code_signature.subject_name",
"process.code_signature.trusted",
"process.code_signature.valid",
"process.command_line",
"process.command_line.caseless",
"process.command_line.text",
"process.entity_id",
"process.executable",
"process.executable.caseless",
"process.executable.text",
"process.exit_code",
"process.Ext",
"process.Ext.ancestry",
"process.Ext.authentication_id",
"process.Ext.code_signature",
"process.Ext.code_signature.exists",
"process.Ext.code_signature.status",
"process.Ext.code_signature.subject_name",
"process.Ext.code_signature.trusted",
"process.Ext.code_signature.valid",
"process.Ext.defense_evasions",
"process.Ext.session",
"process.Ext.token.elevation",
"process.Ext.token.elevation_type",
"process.Ext.token.integrity_level_name",
"process.hash.md5",
"process.hash.sha1",
"process.hash.sha256",
"process.hash.sha512",
"process.name",
"process.name.caseless",
"process.name.text",
"process.parent.args",
"process.parent.args_count",
"process.parent.code_signature.exists",
"process.parent.code_signature.status",
"process.parent.code_signature.subject_name",
"process.parent.code_signature.trusted",
"process.parent.code_signature.valid",
"process.parent.command_line",
"process.parent.command_line.caseless",
"process.parent.command_line.text",
"process.parent.entity_id",
"process.parent.executable",
"process.parent.executable.caseless",
"process.parent.executable.text",
"process.parent.exit_code",
"process.parent.Ext",
"process.parent.Ext.code_signature",
"process.parent.Ext.code_signature.exists",
"process.parent.Ext.code_signature.status",
"process.parent.Ext.code_signature.subject_name",
"process.parent.Ext.code_signature.trusted",
"process.parent.Ext.code_signature.valid",
"process.parent.Ext.real",
"process.parent.Ext.real.pid",
"process.parent.hash.md5",
"process.parent.hash.sha1",
"process.parent.hash.sha256",
"process.parent.hash.sha512",
"process.parent.name",
"process.parent.name.caseless",
"process.parent.name.text",
"process.parent.pe.company",
"process.parent.pe.description",
"process.parent.pe.file_version",
"process.parent.pe.imphash",
"process.parent.pe.original_file_name",
"process.parent.pe.product",
"process.parent.pgid",
"process.parent.pid",
"process.parent.ppid",
"process.parent.thread.id",
"process.parent.thread.name",
"process.parent.title",
"process.parent.title.text",
"process.parent.uptime",
"process.parent.working_directory",
"process.parent.working_directory.caseless",
"process.parent.working_directory.text",
"process.pe.company",
"process.pe.description",
"process.pe.file_version",
"process.pe.imphash",
"process.pe.original_file_name",
"process.pe.product",
"process.pgid",
"process.pid",
"process.ppid",
"process.thread.id",
"process.thread.name",
"process.title",
"process.title.text",
"process.uptime",
"process.working_directory",
"process.working_directory.caseless",
"process.working_directory.text",
"registry.data.bytes",
"registry.data.strings",
"registry.hive",
"registry.key",
"registry.path",
"registry.value",
"source.address",
"source.bytes",
"source.domain",
"source.geo.city_name",
"source.geo.continent_name",
"source.geo.country_iso_code",
"source.geo.country_name",
"source.geo.location",
"source.geo.name",
"source.geo.region_iso_code",
"source.geo.region_name",
"source.ip",
"source.packets",
"source.port",
"source.registered_domain",
"source.top_level_domain",
"user.domain",
"user.email",
"user.Ext",
"user.Ext.real",
"user.Ext.real.id",
"user.Ext.real.name",
"user.full_name",
"user.full_name.text",
"user.group.domain",
"user.group.Ext",
"user.group.Ext.real",
"user.group.Ext.real.id",
"user.group.Ext.real.name",
"user.group.id",
"user.group.name",
"user.hash",
"user.id",
"user.name",
"user.name.text"
]

View file

@ -43,7 +43,7 @@ import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard'
import exceptionableLinuxFields from './exceptionable_linux_fields.json';
import exceptionableWindowsMacFields from './exceptionable_windows_mac_fields.json';
import exceptionableEndpointFields from './exceptionable_endpoint_fields.json';
import exceptionableEndpointEventFields from './exceptionable_endpoint_event_fields.json';
import { EXCEPTIONABLE_ENDPOINT_EVENT_FIELDS } from '../../../../common/endpoint/exceptions/exceptionable_endpoint_event_fields';
import { ALERT_ORIGINAL_EVENT } from '../../../../common/field_maps/field_names';
export const filterIndexPatterns = (
@ -68,7 +68,7 @@ export const filterIndexPatterns = (
return {
...patterns,
fields: patterns.fields.filter(({ name }) =>
exceptionableEndpointEventFields.includes(name)
EXCEPTIONABLE_ENDPOINT_EVENT_FIELDS.includes(name)
),
};
default:

View file

@ -5,7 +5,11 @@
* 2.0.
*/
import { ExceptionListType, ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import {
CreateExceptionListSchema,
ExceptionListType,
ExceptionListTypeEnum,
} from '@kbn/securitysolution-io-ts-list-types';
import {
EXCEPTION_LIST_URL,
EXCEPTION_LIST_ITEM_URL,
@ -15,7 +19,7 @@ import {
} from '@kbn/securitysolution-list-constants';
export const EVENT_FILTER_LIST_TYPE: ExceptionListType = ExceptionListTypeEnum.ENDPOINT_EVENTS;
export const EVENT_FILTER_LIST = {
export const EVENT_FILTER_LIST: CreateExceptionListSchema = {
name: ENDPOINT_EVENT_FILTERS_LIST_NAME,
namespace_type: 'agnostic',
description: ENDPOINT_EVENT_FILTERS_LIST_DESCRIPTION,

View file

@ -10,7 +10,7 @@ import {
ExceptionsListPreCreateItemServerExtension,
} from '../../../../../lists/server';
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { TrustedAppValidator } from '../validators';
import { EventFilterValidator, TrustedAppValidator } from '../validators';
export const getExceptionsPreCreateItemHandler = (
endpointAppContext: EndpointAppContextService
@ -21,6 +21,11 @@ export const getExceptionsPreCreateItemHandler = (
return new TrustedAppValidator(endpointAppContext, request).validatePreCreateItem(data);
}
// Validate event filter
if (EventFilterValidator.isEventFilter(data)) {
return new EventFilterValidator(endpointAppContext, request).validatePreCreateItem(data);
}
return data;
};
};

View file

@ -10,7 +10,7 @@ import {
UpdateExceptionListItemOptions,
} from '../../../../../lists/server';
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { TrustedAppValidator } from '../validators';
import { EventFilterValidator, TrustedAppValidator } from '../validators';
export const getExceptionsPreUpdateItemHandler = (
endpointAppContextService: EndpointAppContextService
@ -30,14 +30,24 @@ export const getExceptionsPreUpdateItemHandler = (
return data;
}
const listId = currentSavedItem.list_id;
// Validate trusted apps
if (TrustedAppValidator.isTrustedApp({ listId: currentSavedItem.list_id })) {
if (TrustedAppValidator.isTrustedApp({ listId })) {
return new TrustedAppValidator(endpointAppContextService, request).validatePreUpdateItem(
data,
currentSavedItem
);
}
// Validate event filter
if (EventFilterValidator.isEventFilter({ listId })) {
return new EventFilterValidator(endpointAppContextService, request).validatePreUpdateItem(
data,
currentSavedItem
);
}
return data;
};
};

View file

@ -0,0 +1,104 @@
/*
* 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 { schema } from '@kbn/config-schema';
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants';
import {
CreateExceptionListItemOptions,
UpdateExceptionListItemOptions,
} from '../../../../../lists/server';
import { EXCEPTIONABLE_ENDPOINT_EVENT_FIELDS } from '../../../../common/endpoint/exceptions/exceptionable_endpoint_event_fields';
import { ExceptionItemLikeOptions } from '../types';
import { BaseValidator } from './base_validator';
import { EndpointArtifactExceptionValidationError } from './errors';
function validateField(field: string) {
if (!EXCEPTIONABLE_ENDPOINT_EVENT_FIELDS.includes(field)) {
return `invalid field: ${field}`;
}
}
const EntrySchema = schema.object({
field: schema.string({ validate: validateField }),
operator: schema.oneOf([schema.literal('included'), schema.literal('excluded')]),
type: schema.oneOf([schema.literal('match'), schema.literal('match_any')]),
value: schema.oneOf([schema.arrayOf(schema.string()), schema.string()]),
});
const NestedEntrySchema = schema.object({
field: schema.string({ validate: validateField }),
type: schema.literal('nested'),
entries: schema.arrayOf(EntrySchema),
});
const EntriesSchema = schema.oneOf([EntrySchema, NestedEntrySchema]);
const EventFilterDataSchema = schema.object(
{
entries: schema.arrayOf(EntriesSchema, { minSize: 1 }),
},
{
unknowns: 'ignore',
}
);
export class EventFilterValidator extends BaseValidator {
static isEventFilter(item: { listId: string }): boolean {
return item.listId === ENDPOINT_EVENT_FILTERS_LIST_ID;
}
async validatePreCreateItem(item: CreateExceptionListItemOptions) {
await this.validateCanManageEndpointArtifacts();
await this.validateEventFilterData(item);
// user can always create a global entry so additional checks not needed
if (this.isItemByPolicy(item)) {
await this.validateCanCreateByPolicyArtifacts(item);
await this.validateByPolicyItem(item);
}
return item;
}
async validatePreUpdateItem(
_updatedItem: UpdateExceptionListItemOptions,
currentItem: ExceptionListItemSchema
): Promise<UpdateExceptionListItemOptions> {
const updatedItem = _updatedItem as ExceptionItemLikeOptions;
await this.validateCanManageEndpointArtifacts();
await this.validateEventFilterData(updatedItem);
try {
await this.validateCanCreateByPolicyArtifacts(updatedItem);
} catch (noByPolicyAuthzError) {
// Not allowed to create/update by policy data. Validate that the effective scope of the item
// remained unchanged with this update or was set to `global` (only allowed update). If not,
// then throw the validation error that was catch'ed
if (this.wasByPolicyEffectScopeChanged(updatedItem, currentItem)) {
throw noByPolicyAuthzError;
}
}
await this.validateByPolicyItem(updatedItem);
return _updatedItem;
}
private async validateEventFilterData(item: ExceptionItemLikeOptions): Promise<void> {
await this.validateBasicData(item);
try {
EventFilterDataSchema.validate(item);
} catch (error) {
throw new EndpointArtifactExceptionValidationError(error.message);
}
}
}

View file

@ -6,3 +6,4 @@
*/
export { TrustedAppValidator } from './trusted_app_validator';
export { EventFilterValidator } from './event_filter_validator';

View file

@ -16,6 +16,7 @@ import { FtrService } from '../../functional/ftr_provider_context';
import { ExceptionsListItemGenerator } from '../../../plugins/security_solution/common/endpoint/data_generators/exceptions_list_item_generator';
import { TRUSTED_APPS_EXCEPTION_LIST_DEFINITION } from '../../../plugins/security_solution/public/management/pages/trusted_apps/constants';
import { EndpointError } from '../../../plugins/security_solution/common/endpoint/errors';
import { EVENT_FILTER_LIST } from '../../../plugins/security_solution/public/management/pages/event_filters/constants';
export interface ArtifactTestData {
artifact: ExceptionListItemSchema;
@ -86,4 +87,13 @@ export class EndpointArtifactsTestResources extends FtrService {
return this.createExceptionItem(trustedApp);
}
async createEventFilter(
overrides: Partial<CreateExceptionListItemSchema> = {}
): Promise<ArtifactTestData> {
await this.ensureListExists(EVENT_FILTER_LIST);
const eventFilter = this.exceptionsGenerator.generateEventFilterForCreate(overrides);
return this.createExceptionItem(eventFilter);
}
}

View file

@ -0,0 +1,177 @@
/*
* 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 { EXCEPTION_LIST_ITEM_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';
import { PolicyTestResourceInfo } from '../../../security_solution_endpoint/services/endpoint_policy';
import { ArtifactTestData } from '../../../security_solution_endpoint/services/endpoint_artifacts';
import { BY_POLICY_ARTIFACT_TAG_PREFIX } from '../../../../plugins/security_solution/common/endpoint/service/artifacts';
import { ExceptionsListItemGenerator } from '../../../../plugins/security_solution/common/endpoint/data_generators/exceptions_list_item_generator';
import {
createUserAndRole,
deleteUserAndRole,
ROLES,
} from '../../../common/services/security_solution';
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const endpointPolicyTestResources = getService('endpointPolicyTestResources');
const endpointArtifactTestResources = getService('endpointArtifactTestResources');
describe('Endpoint artifacts (via lists plugin) event filter', () => {
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 } }) => {
expect(res.body.message).to.match(/EndpointArtifactError/);
};
const anErrorMessageWith = (
value: string | RegExp
): ((res: { body: { message: string } }) => void) => {
return (res) => {
if (value instanceof RegExp) {
expect(res.body.message).to.match(value);
} else {
expect(res.body.message).to.be(value);
}
};
};
const exceptionsGenerator = new ExceptionsListItemGenerator();
let eventFilterData: ArtifactTestData;
type EventFilterApiCallsInterface = Array<{
method: keyof Pick<typeof supertest, 'post' | 'put'>;
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'>;
}>;
const eventFilterCalls: EventFilterApiCallsInterface = [
{
method: 'post',
path: EXCEPTION_LIST_ITEM_URL,
getBody: (overrides) =>
exceptionsGenerator.generateEventFilterForCreate({
tags: eventFilterData.artifact.tags,
...overrides,
}),
},
{
method: 'put',
path: EXCEPTION_LIST_ITEM_URL,
getBody: (overrides) =>
exceptionsGenerator.generateEventFilterForUpdate({
id: eventFilterData.artifact.id,
item_id: eventFilterData.artifact.item_id,
tags: eventFilterData.artifact.tags,
_version: eventFilterData.artifact._version,
...overrides,
}),
},
];
beforeEach(async () => {
eventFilterData = await endpointArtifactTestResources.createEventFilter({
tags: [`${BY_POLICY_ARTIFACT_TAG_PREFIX}${fleetEndpointPolicy.packagePolicy.id}`],
});
});
afterEach(async () => {
if (eventFilterData) {
await eventFilterData.cleanup();
}
});
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({});
body.entries[0].field = 'some.invalid.field';
await supertest[eventFilterCall.method](eventFilterCall.path)
.set('kbn-xsrf', 'true')
.send(body)
.expect(400)
.expect(anEndpointArtifactError)
.expect(anErrorMessageWith(/types that failed validation:/));
});
it(`should error on [${eventFilterCall.method}] if more than one OS is set`, async () => {
const body = eventFilterCall.getBody({ os_types: ['linux', 'windows'] });
await supertest[eventFilterCall.method](eventFilterCall.path)
.set('kbn-xsrf', 'true')
.send(body)
.expect(400)
.expect(anEndpointArtifactError)
.expect(anErrorMessageWith(/\[osTypes\]: array size is \[2\]/));
});
it(`should error on [${eventFilterCall.method}] if policy id is invalid`, async () => {
const body = eventFilterCall.getBody({
tags: [`${BY_POLICY_ARTIFACT_TAG_PREFIX}123`],
});
await supertest[eventFilterCall.method](eventFilterCall.path)
.set('kbn-xsrf', 'true')
.send(body)
.expect(400)
.expect(anEndpointArtifactError)
.expect(anErrorMessageWith(/invalid policy ids/));
});
it(`should work on [${eventFilterCall.method}] with valid entry`, async () => {
const body = eventFilterCall.getBody({});
await supertest[eventFilterCall.method](eventFilterCall.path)
.set('kbn-xsrf', 'true')
.send(body)
.expect(200);
});
}
});
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)
.auth(ROLES.detections_admin, 'changeme')
.set('kbn-xsrf', 'true')
.send(eventFilterCall.getBody({}))
.expect(403, {
status_code: 403,
message: 'EndpointArtifactError: Endpoint authorization failure',
});
});
}
});
});
}

View file

@ -33,5 +33,6 @@ export default function endpointAPIIntegrationTests(providerContext: FtrProvider
loadTestFile(require.resolve('./package'));
loadTestFile(require.resolve('./endpoint_authz'));
loadTestFile(require.resolve('./endpoint_artifacts'));
loadTestFile(require.resolve('./endpoint_artifacts/event_filter'));
});
}