mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[EDR Workflows] Add matches capabilities to Endpoint Exception creation (#166002)
## Summary
This PR adds `matches` (`wildcard include`) and `does not match`
(`wildcard exclude`) to fields which support them when creating an
Endpoint exception.
For backwards compatibility with Endpoints < 8.2.0, Manifest Manager
adds the following entry to Endpoint Exceptions containing _only_
wildcards:
```json
{
"field": "event.module",
"operator": "included",
"type": "exact_cased",
"value": "endpoint"
}
```
> [!Note]
> Warnings for wrongly formatted wildcards don't seem to work correctly
at the moment. #170495 will bring some changes in the related functions,
so this PR is waiting on that to be merged.
<img width="1465" alt="image"
src="db04fe0b
-4cb3-4cba-a6d7-622a2239f059">
## Sample manifests
### Linux
⚠️ On Linux, the type is always `wildcard_cased`, see the following
comment for details:
https://github.com/elastic/kibana/pull/120349#issuecomment-989963682
```json
{
"entries": [
{
"type": "simple",
"entries": [
{
"field": "file.path",
"operator": "included",
"type": "wildcard_cased",
"value": "*/test/*"
},
{
"field": "event.module",
"operator": "included",
"type": "exact_cased",
"value": "endpoint"
}
]
}
]
}
```
### Windows
```json
{
"entries": [
{
"type": "simple",
"entries": [
{
"field": "file.path",
"operator": "included",
"type": "wildcard_caseless",
"value": "*/test/*"
},
{
"field": "event.module",
"operator": "included",
"type": "exact_cased",
"value": "endpoint"
}
]
}
]
}
```
### Checklist
Delete any items that are not applicable to this PR.
- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
This commit is contained in:
parent
cc524d1d6d
commit
44d7c0ae95
12 changed files with 507 additions and 121 deletions
|
@ -23,16 +23,14 @@ describe('endpointEntryMatchWildcard', () => {
|
|||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate when "operator" is "excluded"', () => {
|
||||
test('it should validate when "operator" is "excluded"', () => {
|
||||
const payload = getEntryMatchWildcardMock();
|
||||
payload.operator = 'excluded';
|
||||
const decoded = endpointEntryMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "excluded" supplied to "operator"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "field" is empty string', () => {
|
||||
|
|
|
@ -7,12 +7,16 @@
|
|||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { NonEmptyString, operatorIncluded } from '@kbn/securitysolution-io-ts-types';
|
||||
import {
|
||||
NonEmptyString,
|
||||
operatorExcluded,
|
||||
operatorIncluded,
|
||||
} from '@kbn/securitysolution-io-ts-types';
|
||||
|
||||
export const endpointEntryMatchWildcard = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator: operatorIncluded,
|
||||
operator: t.union([operatorIncluded, operatorExcluded]),
|
||||
type: t.keyof({ wildcard: null }),
|
||||
value: NonEmptyString,
|
||||
})
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import * as t from 'io-ts';
|
||||
|
||||
export const operatorIncluded = t.keyof({ included: null });
|
||||
export const operatorExcluded = t.keyof({ excluded: null });
|
||||
|
||||
export const operator = t.keyof({
|
||||
equals: null,
|
||||
|
|
|
@ -48,6 +48,8 @@ import {
|
|||
isNotOneOfOperator,
|
||||
isInListOperator,
|
||||
isNotInListOperator,
|
||||
matchesOperator,
|
||||
doesNotMatchOperator,
|
||||
} from '../autocomplete_operators';
|
||||
|
||||
import {
|
||||
|
@ -696,8 +698,14 @@ export const getOperatorOptions = (
|
|||
): OperatorOption[] => {
|
||||
if (item.nested === 'parent' || item.field == null) {
|
||||
return [isOperator];
|
||||
} else if ((item.nested != null && listType === 'endpoint') || listType === 'endpoint') {
|
||||
return isBoolean ? [isOperator] : [isOperator, isOneOfOperator];
|
||||
} else if (listType === 'endpoint') {
|
||||
if (isBoolean) {
|
||||
return [isOperator];
|
||||
} else {
|
||||
return fieldSupportsMatches(item.field)
|
||||
? [isOperator, isOneOfOperator, matchesOperator, doesNotMatchOperator]
|
||||
: [isOperator, isOneOfOperator];
|
||||
}
|
||||
} else if (item.nested != null && listType === 'detection') {
|
||||
return isBoolean ? [isOperator, existsOperator] : [isOperator, isOneOfOperator, existsOperator];
|
||||
} else if (isBoolean) {
|
||||
|
|
|
@ -360,7 +360,7 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
|
|||
};
|
||||
|
||||
// show this when wildcard with matches operator
|
||||
const getWildcardWarningInfo = (precedingWarning: string): React.ReactNode => {
|
||||
const getEventFilterWildcardWarningInfo = (precedingWarning: string): React.ReactNode => {
|
||||
return (
|
||||
<p>
|
||||
{precedingWarning}{' '}
|
||||
|
@ -437,7 +437,9 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
|
|||
value: wildcardValue,
|
||||
});
|
||||
actualWarning =
|
||||
warning === WILDCARD_WARNING ? warning && getWildcardWarningInfo(warning) : warning;
|
||||
warning === WILDCARD_WARNING && listType === 'endpoint_events'
|
||||
? getEventFilterWildcardWarningInfo(warning)
|
||||
: warning;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
FormattedBuilderEntry,
|
||||
OperatorOption,
|
||||
doesNotExistOperator,
|
||||
doesNotMatchOperator,
|
||||
existsOperator,
|
||||
filterExceptionItems,
|
||||
getCorrespondingKeywordField,
|
||||
|
@ -50,6 +51,7 @@ import {
|
|||
isNotOperator,
|
||||
isOneOfOperator,
|
||||
isOperator,
|
||||
matchesOperator,
|
||||
} from '@kbn/securitysolution-list-utils';
|
||||
import { DataViewBase, DataViewFieldBase } from '@kbn/es-query';
|
||||
import { fields, getField } from '@kbn/data-plugin/common/mocks';
|
||||
|
@ -509,25 +511,57 @@ describe('Exception builder helpers', () => {
|
|||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns "isOperator" and "isOneOfOperator" if item is nested and "listType" is "endpoint"', () => {
|
||||
const payloadItem: FormattedBuilderEntry = getMockNestedBuilderEntry();
|
||||
const output = getOperatorOptions(payloadItem, 'endpoint', false);
|
||||
const expected: OperatorOption[] = [isOperator, isOneOfOperator];
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
describe('"endpoint" list type', () => {
|
||||
test('it returns operators "is", "isOneOf", "matches" and "doesNotMatch" if item is nested and field supports "matches"', () => {
|
||||
const payloadItem: FormattedBuilderEntry = getMockNestedBuilderEntry();
|
||||
const output = getOperatorOptions(payloadItem, 'endpoint', false);
|
||||
const expected: OperatorOption[] = [
|
||||
isOperator,
|
||||
isOneOfOperator,
|
||||
matchesOperator,
|
||||
doesNotMatchOperator,
|
||||
];
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns "isOperator" and "isOneOfOperator" if "listType" is "endpoint"', () => {
|
||||
const payloadItem: FormattedBuilderEntry = getMockBuilderEntry();
|
||||
const output = getOperatorOptions(payloadItem, 'endpoint', false);
|
||||
const expected: OperatorOption[] = [isOperator, isOneOfOperator];
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
test('it returns operators "is" and "isOneOf" if item is nested and field does not support "matches"', () => {
|
||||
const payloadItem: FormattedBuilderEntry = {
|
||||
...getMockNestedBuilderEntry(),
|
||||
field: getField('ip'),
|
||||
};
|
||||
const output = getOperatorOptions(payloadItem, 'endpoint', false);
|
||||
const expected: OperatorOption[] = [isOperator, isOneOfOperator];
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns "isOperator" if "listType" is "endpoint" and field type is boolean', () => {
|
||||
const payloadItem: FormattedBuilderEntry = getMockBuilderEntry();
|
||||
const output = getOperatorOptions(payloadItem, 'endpoint', true);
|
||||
const expected: OperatorOption[] = [isOperator];
|
||||
expect(output).toEqual(expected);
|
||||
test('it returns operators "is", "isOneOf", "matches" and "doesNotMatch" if field supports "matches"', () => {
|
||||
const payloadItem: FormattedBuilderEntry = {
|
||||
...getMockBuilderEntry(),
|
||||
field: getField('@tags'),
|
||||
};
|
||||
const output = getOperatorOptions(payloadItem, 'endpoint', false);
|
||||
const expected: OperatorOption[] = [
|
||||
isOperator,
|
||||
isOneOfOperator,
|
||||
matchesOperator,
|
||||
doesNotMatchOperator,
|
||||
];
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns "isOperator" and "isOneOfOperator" if field does not support "matches"', () => {
|
||||
const payloadItem: FormattedBuilderEntry = getMockBuilderEntry();
|
||||
const output = getOperatorOptions(payloadItem, 'endpoint', false);
|
||||
const expected: OperatorOption[] = [isOperator, isOneOfOperator];
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
|
||||
test('it returns "isOperator" and field type is boolean', () => {
|
||||
const payloadItem: FormattedBuilderEntry = getMockBuilderEntry();
|
||||
const output = getOperatorOptions(payloadItem, 'endpoint', true);
|
||||
const expected: OperatorOption[] = [isOperator];
|
||||
expect(output).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
test('it returns "isOperator", "isOneOfOperator", and "existsOperator" if item is nested and "listType" is "detection"', () => {
|
||||
|
|
|
@ -6,13 +6,7 @@
|
|||
*/
|
||||
|
||||
import { savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import {
|
||||
ENDPOINT_BLOCKLISTS_LIST_ID,
|
||||
ENDPOINT_EVENT_FILTERS_LIST_ID,
|
||||
ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
|
||||
ENDPOINT_LIST_ID,
|
||||
ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
} from '@kbn/securitysolution-list-constants';
|
||||
import { ENDPOINT_LIST_ID, ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
|
||||
import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock';
|
||||
import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models';
|
||||
import { getEmptyInternalArtifactMock } from '../../../schemas/artifacts/saved_objects.mock';
|
||||
|
@ -82,6 +76,14 @@ describe('ManifestManager', () => {
|
|||
const ARTIFACT_NAME_BLOCKLISTS_WINDOWS = 'endpoint-blocklist-windows-v1';
|
||||
const ARTIFACT_NAME_BLOCKLISTS_LINUX = 'endpoint-blocklist-linux-v1';
|
||||
|
||||
const mockPolicyListIdsResponse = (items: string[]) =>
|
||||
jest.fn().mockResolvedValue({
|
||||
items,
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
total: items.length,
|
||||
});
|
||||
|
||||
let ARTIFACTS: InternalArtifactCompleteSchema[] = [];
|
||||
let ARTIFACTS_BY_ID: { [K: string]: InternalArtifactCompleteSchema } = {};
|
||||
let ARTIFACT_EXCEPTIONS_MACOS: InternalArtifactCompleteSchema;
|
||||
|
@ -311,14 +313,6 @@ describe('ManifestManager', () => {
|
|||
...new Set(artifacts.map((artifact) => artifact.identifier)).values(),
|
||||
];
|
||||
|
||||
const mockPolicyListIdsResponse = (items: string[]) =>
|
||||
jest.fn().mockResolvedValue({
|
||||
items,
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
total: items.length,
|
||||
});
|
||||
|
||||
test('Fails when exception list client fails', async () => {
|
||||
const context = buildManifestManagerContextMock({});
|
||||
const manifestManager = new ManifestManager(context);
|
||||
|
@ -383,10 +377,12 @@ describe('ManifestManager', () => {
|
|||
|
||||
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
|
||||
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
|
||||
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_EVENT_FILTERS_LIST_ID]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
|
||||
[ENDPOINT_BLOCKLISTS_LIST_ID]: { linux: [blocklistsListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.eventFilters.id]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id]: {
|
||||
linux: [hostIsolationExceptionsItem],
|
||||
},
|
||||
[ENDPOINT_ARTIFACT_LISTS.blocklists.id]: { linux: [blocklistsListItem] },
|
||||
});
|
||||
context.savedObjectsClient.create = jest
|
||||
.fn()
|
||||
|
@ -474,10 +470,12 @@ describe('ManifestManager', () => {
|
|||
|
||||
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
|
||||
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
|
||||
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_EVENT_FILTERS_LIST_ID]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
|
||||
[ENDPOINT_BLOCKLISTS_LIST_ID]: { linux: [blocklistsListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.eventFilters.id]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id]: {
|
||||
linux: [hostIsolationExceptionsItem],
|
||||
},
|
||||
[ENDPOINT_ARTIFACT_LISTS.blocklists.id]: { linux: [blocklistsListItem] },
|
||||
});
|
||||
|
||||
const manifest = await manifestManager.buildNewManifest(oldManifest);
|
||||
|
@ -557,17 +555,21 @@ describe('ManifestManager', () => {
|
|||
macos: [exceptionListItem, exceptionListItem],
|
||||
windows: [duplicatedEndpointExceptionInDifferentOS],
|
||||
},
|
||||
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem, trustedAppListItem] },
|
||||
[ENDPOINT_EVENT_FILTERS_LIST_ID]: {
|
||||
[ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: {
|
||||
linux: [trustedAppListItem, trustedAppListItem],
|
||||
},
|
||||
[ENDPOINT_ARTIFACT_LISTS.eventFilters.id]: {
|
||||
windows: [eventFiltersListItem, duplicatedEventFilterInDifferentPolicy],
|
||||
},
|
||||
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: {
|
||||
[ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id]: {
|
||||
linux: [
|
||||
hostIsolationExceptionsItem,
|
||||
{ ...hostIsolationExceptionsItem, tags: [`policy:${TEST_POLICY_ID_2}`] },
|
||||
],
|
||||
},
|
||||
[ENDPOINT_BLOCKLISTS_LIST_ID]: { macos: [blocklistsListItem, blocklistsListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.blocklists.id]: {
|
||||
macos: [blocklistsListItem, blocklistsListItem],
|
||||
},
|
||||
});
|
||||
context.savedObjectsClient.create = jest
|
||||
.fn()
|
||||
|
@ -673,7 +675,7 @@ describe('ManifestManager', () => {
|
|||
|
||||
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
|
||||
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
|
||||
[ENDPOINT_TRUSTED_APPS_LIST_ID]: {
|
||||
[ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: {
|
||||
linux: [trustedAppListItem, trustedAppListItemPolicy2],
|
||||
},
|
||||
});
|
||||
|
@ -756,14 +758,6 @@ describe('ManifestManager', () => {
|
|||
...new Set(artifacts.map((artifact) => artifact.identifier)).values(),
|
||||
];
|
||||
|
||||
const mockPolicyListIdsResponse = (items: string[]) =>
|
||||
jest.fn().mockResolvedValue({
|
||||
items,
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
total: items.length,
|
||||
});
|
||||
|
||||
test('when it has endpoint artifact management app feature it should not generate host isolation exceptions', async () => {
|
||||
const exceptionListItem = getExceptionListItemSchemaMock({ os_types: ['macos'] });
|
||||
const trustedAppListItem = getExceptionListItemSchemaMock({
|
||||
|
@ -789,10 +783,12 @@ describe('ManifestManager', () => {
|
|||
|
||||
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
|
||||
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
|
||||
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_EVENT_FILTERS_LIST_ID]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
|
||||
[ENDPOINT_BLOCKLISTS_LIST_ID]: { linux: [blocklistsListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.eventFilters.id]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id]: {
|
||||
linux: [hostIsolationExceptionsItem],
|
||||
},
|
||||
[ENDPOINT_ARTIFACT_LISTS.blocklists.id]: { linux: [blocklistsListItem] },
|
||||
});
|
||||
context.savedObjectsClient.create = jest
|
||||
.fn()
|
||||
|
@ -870,10 +866,12 @@ describe('ManifestManager', () => {
|
|||
|
||||
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
|
||||
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
|
||||
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_EVENT_FILTERS_LIST_ID]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
|
||||
[ENDPOINT_BLOCKLISTS_LIST_ID]: { linux: [blocklistsListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.eventFilters.id]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id]: {
|
||||
linux: [hostIsolationExceptionsItem],
|
||||
},
|
||||
[ENDPOINT_ARTIFACT_LISTS.blocklists.id]: { linux: [blocklistsListItem] },
|
||||
});
|
||||
context.savedObjectsClient.create = jest
|
||||
.fn()
|
||||
|
@ -950,10 +948,12 @@ describe('ManifestManager', () => {
|
|||
|
||||
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
|
||||
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
|
||||
[ENDPOINT_TRUSTED_APPS_LIST_ID]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_EVENT_FILTERS_LIST_ID]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID]: { linux: [hostIsolationExceptionsItem] },
|
||||
[ENDPOINT_BLOCKLISTS_LIST_ID]: { linux: [blocklistsListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.trustedApps.id]: { linux: [trustedAppListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.eventFilters.id]: { linux: [eventFiltersListItem] },
|
||||
[ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id]: {
|
||||
linux: [hostIsolationExceptionsItem],
|
||||
},
|
||||
[ENDPOINT_ARTIFACT_LISTS.blocklists.id]: { linux: [blocklistsListItem] },
|
||||
});
|
||||
context.savedObjectsClient.create = jest
|
||||
.fn()
|
||||
|
@ -998,6 +998,94 @@ describe('ManifestManager', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('buildNewManifest when Endpoint Exceptions contain `matches`', () => {
|
||||
test('when contains only `wildcard`, `event.module=endpoint` is added', async () => {
|
||||
const exceptionListItem = getExceptionListItemSchemaMock({
|
||||
os_types: ['macos'],
|
||||
entries: [
|
||||
{ type: 'wildcard', operator: 'included', field: 'path', value: '*match_me*' },
|
||||
{ type: 'wildcard', operator: 'excluded', field: 'not_path', value: '*dont_match_me*' },
|
||||
],
|
||||
});
|
||||
const expectedExceptionListItem = getExceptionListItemSchemaMock({
|
||||
os_types: ['macos'],
|
||||
entries: [
|
||||
...exceptionListItem.entries,
|
||||
{ type: 'match', operator: 'included', field: 'event.module', value: 'endpoint' },
|
||||
],
|
||||
});
|
||||
|
||||
const context = buildManifestManagerContextMock({});
|
||||
const manifestManager = new ManifestManager(context);
|
||||
|
||||
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
|
||||
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
|
||||
});
|
||||
context.savedObjectsClient.create = jest
|
||||
.fn()
|
||||
.mockImplementation((_type: string, object: InternalManifestSchema) => ({
|
||||
attributes: object,
|
||||
}));
|
||||
context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]);
|
||||
|
||||
const manifest = await manifestManager.buildNewManifest();
|
||||
|
||||
expect(manifest?.getSchemaVersion()).toStrictEqual('v1');
|
||||
expect(manifest?.getSemanticVersion()).toStrictEqual('1.0.0');
|
||||
expect(manifest?.getSavedObjectVersion()).toBeUndefined();
|
||||
|
||||
const artifacts = manifest.getAllArtifacts();
|
||||
|
||||
expect(artifacts.length).toBe(15);
|
||||
|
||||
expect(getArtifactObject(artifacts[0])).toStrictEqual({
|
||||
entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1'),
|
||||
});
|
||||
});
|
||||
|
||||
test('when contains anything next to `wildcard`, nothing is added', async () => {
|
||||
const exceptionListItem = getExceptionListItemSchemaMock({
|
||||
os_types: ['macos'],
|
||||
entries: [
|
||||
{ type: 'wildcard', operator: 'included', field: 'path', value: '*match_me*' },
|
||||
{ type: 'wildcard', operator: 'excluded', field: 'path', value: '*dont_match_me*' },
|
||||
{ type: 'match', operator: 'included', field: 'path', value: 'something' },
|
||||
],
|
||||
});
|
||||
const expectedExceptionListItem = getExceptionListItemSchemaMock({
|
||||
os_types: ['macos'],
|
||||
entries: [...exceptionListItem.entries],
|
||||
});
|
||||
|
||||
const context = buildManifestManagerContextMock({});
|
||||
const manifestManager = new ManifestManager(context);
|
||||
|
||||
context.exceptionListClient.findExceptionListItem = mockFindExceptionListItemResponses({
|
||||
[ENDPOINT_LIST_ID]: { macos: [exceptionListItem] },
|
||||
});
|
||||
context.savedObjectsClient.create = jest
|
||||
.fn()
|
||||
.mockImplementation((_type: string, object: InternalManifestSchema) => ({
|
||||
attributes: object,
|
||||
}));
|
||||
context.packagePolicyService.listIds = mockPolicyListIdsResponse([TEST_POLICY_ID_1]);
|
||||
|
||||
const manifest = await manifestManager.buildNewManifest();
|
||||
|
||||
expect(manifest?.getSchemaVersion()).toStrictEqual('v1');
|
||||
expect(manifest?.getSemanticVersion()).toStrictEqual('1.0.0');
|
||||
expect(manifest?.getSavedObjectVersion()).toBeUndefined();
|
||||
|
||||
const artifacts = manifest.getAllArtifacts();
|
||||
|
||||
expect(artifacts.length).toBe(15);
|
||||
|
||||
expect(getArtifactObject(artifacts[0])).toStrictEqual({
|
||||
entries: translateToEndpointExceptions([expectedExceptionListItem], 'v1'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteArtifacts', () => {
|
||||
test('Successfully invokes saved objects client', async () => {
|
||||
const context = buildManifestManagerContextMock({});
|
||||
|
@ -1465,14 +1553,6 @@ describe('ManifestManager', () => {
|
|||
});
|
||||
|
||||
describe('cleanup artifacts', () => {
|
||||
const mockPolicyListIdsResponse = (items: string[]) =>
|
||||
jest.fn().mockResolvedValue({
|
||||
items,
|
||||
page: 1,
|
||||
per_page: 100,
|
||||
total: items.length,
|
||||
});
|
||||
|
||||
test('Successfully removes orphan artifacts', async () => {
|
||||
const context = buildManifestManagerContextMock({});
|
||||
const manifestManager = new ManifestManager(context);
|
||||
|
|
|
@ -9,13 +9,7 @@ import semver from 'semver';
|
|||
import { chunk, isEmpty, isEqual, keyBy } from 'lodash';
|
||||
import type { ElasticsearchClient } from '@kbn/core/server';
|
||||
import { type Logger, type SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import {
|
||||
ENDPOINT_BLOCKLISTS_LIST_ID,
|
||||
ENDPOINT_EVENT_FILTERS_LIST_ID,
|
||||
ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
|
||||
ENDPOINT_LIST_ID,
|
||||
ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
} from '@kbn/securitysolution-list-constants';
|
||||
import { ENDPOINT_LIST_ID, ENDPOINT_ARTIFACT_LISTS } from '@kbn/securitysolution-list-constants';
|
||||
import type { ListResult, PackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
import type { Artifact, PackagePolicyClient } from '@kbn/fleet-plugin/server';
|
||||
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
|
||||
|
@ -58,6 +52,7 @@ interface ArtifactsBuildResult {
|
|||
interface BuildArtifactsForOsOptions {
|
||||
listId: ArtifactListId;
|
||||
name: string;
|
||||
exceptionItemDecorator?: (item: ExceptionListItemSchema) => ExceptionListItemSchema;
|
||||
}
|
||||
|
||||
const iterateArtifactsBuildResult = (
|
||||
|
@ -159,19 +154,21 @@ export class ManifestManager {
|
|||
os,
|
||||
policyId,
|
||||
schemaVersion,
|
||||
exceptionItemDecorator,
|
||||
}: {
|
||||
elClient: ExceptionListClient;
|
||||
listId: ArtifactListId;
|
||||
os: string;
|
||||
policyId?: string;
|
||||
schemaVersion: string;
|
||||
exceptionItemDecorator?: (item: ExceptionListItemSchema) => ExceptionListItemSchema;
|
||||
}): Promise<WrappedTranslatedExceptionList> {
|
||||
if (!this.cachedExceptionsListsByOs.has(`${listId}-${os}`)) {
|
||||
let itemsByListId: ExceptionListItemSchema[] = [];
|
||||
if (
|
||||
(listId === ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID &&
|
||||
(listId === ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id &&
|
||||
this.appFeaturesService.isEnabled(AppFeatureKey.endpointResponseActions)) ||
|
||||
(listId !== ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID &&
|
||||
(listId !== ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id &&
|
||||
this.appFeaturesService.isEnabled(AppFeatureKey.endpointArtifactManagement))
|
||||
) {
|
||||
itemsByListId = await getAllItemsFromEndpointExceptionList({
|
||||
|
@ -179,6 +176,10 @@ export class ManifestManager {
|
|||
os,
|
||||
listId,
|
||||
});
|
||||
|
||||
if (exceptionItemDecorator) {
|
||||
itemsByListId = itemsByListId.map(exceptionItemDecorator);
|
||||
}
|
||||
}
|
||||
this.cachedExceptionsListsByOs.set(`${listId}-${os}`, itemsByListId);
|
||||
}
|
||||
|
@ -209,6 +210,7 @@ export class ManifestManager {
|
|||
name,
|
||||
os,
|
||||
policyId,
|
||||
exceptionItemDecorator,
|
||||
}: {
|
||||
os: string;
|
||||
policyId?: string;
|
||||
|
@ -220,6 +222,7 @@ export class ManifestManager {
|
|||
os,
|
||||
policyId,
|
||||
listId,
|
||||
exceptionItemDecorator,
|
||||
}),
|
||||
this.schemaVersion,
|
||||
os,
|
||||
|
@ -261,9 +264,27 @@ export class ManifestManager {
|
|||
): Promise<ArtifactsBuildResult> {
|
||||
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
|
||||
const policySpecificArtifacts: Record<string, InternalArtifactCompleteSchema[]> = {};
|
||||
|
||||
const decorateWildcardOnlyExceptionItem = (item: ExceptionListItemSchema) => {
|
||||
const isWildcardOnly = item.entries.every(({ type }) => type === 'wildcard');
|
||||
|
||||
// add `event.module=endpoint` to make endpoints older than 8.2 work when only `wildcard` is used
|
||||
if (isWildcardOnly) {
|
||||
item.entries.push({
|
||||
type: 'match',
|
||||
operator: 'included',
|
||||
field: 'event.module',
|
||||
value: 'endpoint',
|
||||
});
|
||||
}
|
||||
|
||||
return item;
|
||||
};
|
||||
|
||||
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
|
||||
listId: ENDPOINT_LIST_ID,
|
||||
name: ArtifactConstants.GLOBAL_ALLOWLIST_NAME,
|
||||
exceptionItemDecorator: decorateWildcardOnlyExceptionItem,
|
||||
};
|
||||
|
||||
for (const os of ArtifactConstants.SUPPORTED_OPERATING_SYSTEMS) {
|
||||
|
@ -284,7 +305,7 @@ export class ManifestManager {
|
|||
protected async buildTrustedAppsArtifacts(allPolicyIds: string[]): Promise<ArtifactsBuildResult> {
|
||||
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
|
||||
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
|
||||
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
|
||||
listId: ENDPOINT_ARTIFACT_LISTS.trustedApps.id,
|
||||
name: ArtifactConstants.GLOBAL_TRUSTED_APPS_NAME,
|
||||
};
|
||||
|
||||
|
@ -312,7 +333,7 @@ export class ManifestManager {
|
|||
): Promise<ArtifactsBuildResult> {
|
||||
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
|
||||
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
|
||||
listId: ENDPOINT_EVENT_FILTERS_LIST_ID,
|
||||
listId: ENDPOINT_ARTIFACT_LISTS.eventFilters.id,
|
||||
name: ArtifactConstants.GLOBAL_EVENT_FILTERS_NAME,
|
||||
};
|
||||
|
||||
|
@ -338,7 +359,7 @@ export class ManifestManager {
|
|||
protected async buildBlocklistArtifacts(allPolicyIds: string[]): Promise<ArtifactsBuildResult> {
|
||||
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
|
||||
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
|
||||
listId: ENDPOINT_BLOCKLISTS_LIST_ID,
|
||||
listId: ENDPOINT_ARTIFACT_LISTS.blocklists.id,
|
||||
name: ArtifactConstants.GLOBAL_BLOCKLISTS_NAME,
|
||||
};
|
||||
|
||||
|
@ -367,7 +388,7 @@ export class ManifestManager {
|
|||
): Promise<ArtifactsBuildResult> {
|
||||
const defaultArtifacts: InternalArtifactCompleteSchema[] = [];
|
||||
const buildArtifactsForOsOptions: BuildArtifactsForOsOptions = {
|
||||
listId: ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID,
|
||||
listId: ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id,
|
||||
name: ArtifactConstants.GLOBAL_HOST_ISOLATION_EXCEPTIONS_NAME,
|
||||
};
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
ENDPOINT_ARTIFACT_LIST_IDS,
|
||||
EXCEPTION_LIST_URL,
|
||||
} from '@kbn/securitysolution-list-constants';
|
||||
import { ManifestConstants } from '@kbn/security-solution-plugin/server/endpoint/lib/artifacts';
|
||||
import { ArtifactElasticsearchProperties } from '@kbn/fleet-plugin/server/services';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import {
|
||||
|
@ -21,7 +20,6 @@ import {
|
|||
getArtifactsListTestsData,
|
||||
ArtifactActionsType,
|
||||
AgentPolicyResponseType,
|
||||
InternalManifestSchemaResponseType,
|
||||
getCreateMultipleData,
|
||||
MultipleArtifactActionsType,
|
||||
} from './mocks';
|
||||
|
@ -32,6 +30,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
const pageObjects = getPageObjects(['common', 'artifactEntriesList']);
|
||||
const testSubjects = getService('testSubjects');
|
||||
const browser = getService('browser');
|
||||
const endpointArtifactsTestResources = getService('endpointArtifactTestResources');
|
||||
const endpointTestResources = getService('endpointTestResources');
|
||||
const retry = getService('retry');
|
||||
const esClient = getService('es');
|
||||
|
@ -86,29 +85,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
// Check edited artifact is in the list with new values (wait for list to be updated)
|
||||
let updatedArtifact: ArtifactElasticsearchProperties | undefined;
|
||||
await retry.waitForWithTimeout('fleet artifact is updated', 120_000, async () => {
|
||||
// Get endpoint manifest
|
||||
const {
|
||||
hits: { hits: manifestResults },
|
||||
} = await esClient.search({
|
||||
index: '.kibana*',
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
type: ManifestConstants.SAVED_OBJECT_TYPE,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
size: 1,
|
||||
});
|
||||
const artifacts = await endpointArtifactsTestResources.getArtifacts();
|
||||
|
||||
const manifestResult = manifestResults[0] as InternalManifestSchemaResponseType;
|
||||
const manifestArtifact = manifestResult._source[
|
||||
'endpoint:user-artifact-manifest'
|
||||
].artifacts.find((artifact) => {
|
||||
const manifestArtifact = artifacts.find((artifact) => {
|
||||
return (
|
||||
artifact.artifactId ===
|
||||
`${expectedArtifact.identifier}-${expectedArtifact.decoded_sha256}` &&
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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 { unzip } from 'zlib';
|
||||
import { promisify } from 'util';
|
||||
import expect from '@kbn/expect';
|
||||
import { IndexedHostsAndAlertsResponse } from '@kbn/security-solution-plugin/common/endpoint/index_data';
|
||||
import { EXCEPTION_LIST_ITEM_URL } from '@kbn/securitysolution-list-constants';
|
||||
import { ArtifactElasticsearchProperties } from '@kbn/fleet-plugin/server/services';
|
||||
import { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper';
|
||||
import { FtrProviderContext } from '../../ftr_provider_context';
|
||||
import { targetTags } from '../../target_tags';
|
||||
|
||||
export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
||||
const pageObjects = getPageObjects(['common', 'header']);
|
||||
const queryBar = getService('queryBar');
|
||||
const testSubjects = getService('testSubjects');
|
||||
const endpointTestResources = getService('endpointTestResources');
|
||||
const endpointArtifactTestResources = getService('endpointArtifactTestResources');
|
||||
const retry = getService('retry');
|
||||
const esClient = getService('es');
|
||||
const supertest = getService('supertest');
|
||||
const find = getService('find');
|
||||
const unzipPromisify = promisify(unzip);
|
||||
|
||||
describe('Endpoint Exceptions', function () {
|
||||
targetTags(this, ['@ess', '@serverless']);
|
||||
|
||||
this.timeout(10 * 60_000);
|
||||
|
||||
const clearPrefilledEntries = async () => {
|
||||
const entriesContainer = await testSubjects.find('exceptionEntriesContainer');
|
||||
|
||||
let deleteButtons: WebElementWrapper[];
|
||||
do {
|
||||
deleteButtons = await testSubjects.findAllDescendant(
|
||||
'builderItemEntryDeleteButton',
|
||||
entriesContainer
|
||||
);
|
||||
|
||||
await deleteButtons[0].click();
|
||||
} while (deleteButtons.length > 1);
|
||||
};
|
||||
|
||||
const openNewEndpointExceptionFlyout = async () => {
|
||||
await testSubjects.click('timeline-context-menu-button');
|
||||
await testSubjects.click('add-endpoint-exception-menu-item');
|
||||
await testSubjects.existOrFail('addExceptionFlyout');
|
||||
|
||||
await retry.waitFor('entries should be loaded', () =>
|
||||
testSubjects.exists('exceptionItemEntryContainer')
|
||||
);
|
||||
};
|
||||
|
||||
const setLastFieldsValue = async ({
|
||||
testSubj,
|
||||
value,
|
||||
optionSelector = `button[title="${value}"]`,
|
||||
}: {
|
||||
testSubj: string;
|
||||
value: string;
|
||||
optionSelector?: string;
|
||||
}) => {
|
||||
const fields = await find.allByCssSelector(`[data-test-subj="${testSubj}"]`);
|
||||
|
||||
const lastField = fields[fields.length - 1];
|
||||
await lastField.click();
|
||||
|
||||
const inputField = await lastField.findByTagName('input');
|
||||
await inputField.type(value);
|
||||
|
||||
const dropdownOptionSelector = `[data-test-subj="comboBoxOptionsList ${testSubj}-optionsList"] ${optionSelector}`;
|
||||
await find.clickByCssSelector(dropdownOptionSelector);
|
||||
};
|
||||
|
||||
const setLastEntry = async ({
|
||||
field,
|
||||
operator,
|
||||
value,
|
||||
}: {
|
||||
field: string;
|
||||
operator: 'matches' | 'is';
|
||||
value: string;
|
||||
}) => {
|
||||
await setLastFieldsValue({ testSubj: 'fieldAutocompleteComboBox', value: field });
|
||||
await setLastFieldsValue({ testSubj: 'operatorAutocompleteComboBox', value: operator });
|
||||
await setLastFieldsValue({
|
||||
testSubj: operator === 'matches' ? 'valuesAutocompleteWildcard' : 'valuesAutocompleteMatch',
|
||||
value,
|
||||
optionSelector: 'p',
|
||||
});
|
||||
};
|
||||
|
||||
const checkArtifact = (expectedArtifact: object) => {
|
||||
return retry.tryForTime(120_000, async () => {
|
||||
const artifacts = await endpointArtifactTestResources.getArtifacts();
|
||||
|
||||
const manifestArtifact = artifacts.find((artifact) =>
|
||||
artifact.artifactId.startsWith('endpoint-exceptionlist-macos-v1')
|
||||
);
|
||||
|
||||
expect(manifestArtifact).to.not.be(undefined);
|
||||
|
||||
// Get fleet artifact
|
||||
const artifactResult = await esClient.get({
|
||||
index: '.fleet-artifacts-7',
|
||||
id: `endpoint:${manifestArtifact!.artifactId}`,
|
||||
});
|
||||
|
||||
const artifact = artifactResult._source as ArtifactElasticsearchProperties;
|
||||
|
||||
const zippedBody = Buffer.from(artifact.body, 'base64');
|
||||
const artifactBody = await unzipPromisify(zippedBody);
|
||||
|
||||
expect(JSON.parse(artifactBody.toString())).to.eql(expectedArtifact);
|
||||
});
|
||||
};
|
||||
|
||||
let indexedData: IndexedHostsAndAlertsResponse;
|
||||
before(async () => {
|
||||
indexedData = await endpointTestResources.loadEndpointData();
|
||||
|
||||
const waitForAlertsToAppear = async () => {
|
||||
await pageObjects.common.navigateToUrlWithBrowserHistory('security', `/alerts`);
|
||||
await pageObjects.header.waitUntilLoadingHasFinished();
|
||||
await retry.waitForWithTimeout('alerts to appear', 10 * 60_000, async () => {
|
||||
await queryBar.clickQuerySubmitButton();
|
||||
return testSubjects.exists('timeline-context-menu-button');
|
||||
});
|
||||
};
|
||||
|
||||
await waitForAlertsToAppear();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await endpointTestResources.unloadEndpointData(indexedData);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const deleteEndpointExceptions = async () => {
|
||||
const { body } = await supertest
|
||||
.get(`${EXCEPTION_LIST_ITEM_URL}/_find?list_id=endpoint_list&namespace_type=agnostic`)
|
||||
.set('kbn-xsrf', 'true');
|
||||
|
||||
for (const exceptionListItem of (body as FoundExceptionListItemSchema).data) {
|
||||
await supertest
|
||||
.delete(`${EXCEPTION_LIST_ITEM_URL}?id=${exceptionListItem.id}&namespace_type=agnostic`)
|
||||
.set('kbn-xsrf', 'true');
|
||||
}
|
||||
};
|
||||
|
||||
await deleteEndpointExceptions();
|
||||
});
|
||||
|
||||
it('should add `event.module=endpoint` to entry if only wildcard operator is present', async () => {
|
||||
await pageObjects.common.navigateToUrlWithBrowserHistory('security', `/alerts`);
|
||||
|
||||
await openNewEndpointExceptionFlyout();
|
||||
await clearPrefilledEntries();
|
||||
|
||||
await testSubjects.setValue('exceptionFlyoutNameInput', 'test exception');
|
||||
await setLastEntry({ field: 'file.path', operator: 'matches', value: '*/cheese/*' });
|
||||
await testSubjects.click('exceptionsAndButton');
|
||||
await setLastEntry({ field: 'process.executable', operator: 'matches', value: 'ex*' });
|
||||
|
||||
await testSubjects.click('addExceptionConfirmButton');
|
||||
await pageObjects.common.closeToast();
|
||||
|
||||
await checkArtifact({
|
||||
entries: [
|
||||
{
|
||||
type: 'simple',
|
||||
entries: [
|
||||
{
|
||||
field: 'file.path',
|
||||
operator: 'included',
|
||||
type: 'wildcard_cased',
|
||||
value: '*/cheese/*',
|
||||
},
|
||||
{
|
||||
field: 'process.executable',
|
||||
operator: 'included',
|
||||
type: 'wildcard_cased',
|
||||
value: 'ex*',
|
||||
},
|
||||
{
|
||||
// this additional entry should be added
|
||||
field: 'event.module',
|
||||
operator: 'included',
|
||||
type: 'exact_cased',
|
||||
value: 'endpoint',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT add `event.module=endpoint` to entry if there is another operator', async () => {
|
||||
await pageObjects.common.navigateToUrlWithBrowserHistory('security', `/alerts`);
|
||||
|
||||
await openNewEndpointExceptionFlyout();
|
||||
await clearPrefilledEntries();
|
||||
|
||||
await testSubjects.setValue('exceptionFlyoutNameInput', 'test exception');
|
||||
await setLastEntry({ field: 'file.path', operator: 'matches', value: '*/cheese/*' });
|
||||
await testSubjects.click('exceptionsAndButton');
|
||||
await setLastEntry({ field: 'process.executable', operator: 'is', value: 'something' });
|
||||
|
||||
await testSubjects.click('addExceptionConfirmButton');
|
||||
await pageObjects.common.closeToast();
|
||||
|
||||
await checkArtifact({
|
||||
entries: [
|
||||
{
|
||||
type: 'simple',
|
||||
entries: [
|
||||
{
|
||||
field: 'file.path',
|
||||
operator: 'included',
|
||||
type: 'wildcard_cased',
|
||||
value: '*/cheese/*',
|
||||
},
|
||||
{
|
||||
field: 'process.executable',
|
||||
operator: 'included',
|
||||
type: 'exact_cased',
|
||||
value: 'something',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -47,5 +47,6 @@ export default function (providerContext: FtrProviderContext) {
|
|||
loadTestFile(require.resolve('./trusted_apps_list'));
|
||||
loadTestFile(require.resolve('./fleet_integrations'));
|
||||
loadTestFile(require.resolve('./artifact_entries_list'));
|
||||
loadTestFile(require.resolve('./endpoint_exceptions'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ import { EndpointError } from '@kbn/security-solution-plugin/common/endpoint/err
|
|||
import { EVENT_FILTER_LIST_DEFINITION } from '@kbn/security-solution-plugin/public/management/pages/event_filters/constants';
|
||||
import { HOST_ISOLATION_EXCEPTIONS_LIST_DEFINITION } from '@kbn/security-solution-plugin/public/management/pages/host_isolation_exceptions/constants';
|
||||
import { BLOCKLISTS_LIST_DEFINITION } from '@kbn/security-solution-plugin/public/management/pages/blocklist/constants';
|
||||
import { ManifestConstants } from '@kbn/security-solution-plugin/server/endpoint/lib/artifacts';
|
||||
import { FtrService } from '../../functional/ftr_provider_context';
|
||||
import { InternalManifestSchemaResponseType } from '../apps/integrations/mocks';
|
||||
|
||||
export interface ArtifactTestData {
|
||||
artifact: ExceptionListItemSchema;
|
||||
|
@ -29,6 +31,7 @@ export class EndpointArtifactsTestResources extends FtrService {
|
|||
private readonly exceptionsGenerator = new ExceptionsListItemGenerator();
|
||||
private readonly supertest = this.ctx.getService('supertest');
|
||||
private readonly log = this.ctx.getService('log');
|
||||
private readonly esClient = this.ctx.getService('es');
|
||||
|
||||
private getHttpResponseFailureHandler(
|
||||
ignoredStatusCodes: number[] = []
|
||||
|
@ -118,4 +121,19 @@ export class EndpointArtifactsTestResources extends FtrService {
|
|||
|
||||
return this.createExceptionItem(blocklist);
|
||||
}
|
||||
|
||||
async getArtifacts() {
|
||||
const {
|
||||
hits: { hits: manifestResults },
|
||||
} = await this.esClient.search({
|
||||
index: '.kibana*',
|
||||
query: { bool: { filter: [{ term: { type: ManifestConstants.SAVED_OBJECT_TYPE } }] } },
|
||||
size: 1,
|
||||
});
|
||||
|
||||
const manifestResult = manifestResults[0] as InternalManifestSchemaResponseType;
|
||||
const artifacts = manifestResult._source['endpoint:user-artifact-manifest'].artifacts;
|
||||
|
||||
return artifacts;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue