mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security Solution][Endpoint] Allow wildcard in trusted app paths (#97623)
* show operator dropdown for path field refs elastic/security-team/issues/543 * update translation to use consistent values refs elastic/security-team/issues/543 * update schema to validate path values refs elastic/security-team/issues/543 * add tests for field and operator values refs elastic/security-team/issues/543 * review changes refs elastic/security-team/issues/543 * update schema to enforce dropdown validation for PATH field refs elastic/security-team/issues/543 * add tests for schema updates refs1deab39453
refs elastic/security-team/issues/543 * optimise dropdown list for re-renders refs elastic/security-team/issues/543 * align input fields and keep alignments when resized refs elastic/security-team/issues/543 * correctly enter operator data on trusted app CRUD refs elastic/security-team/issues/543 * update tests refs2ac56ee839
refs elastic/security-team/issues/543 * remove redundant code review changes * better type assertion review changes * move operator options out of component - these do not depend on component props and thus no need to have it within a useMemo callback. - review changes * derive keys from operator entry field review changes * update type * use custom styles for aligning input fields review changes * add a custom type for trusted_apps operator undo changes from list plugin and server/lib/detection_engine refs2ac56ee839
refs elastic/security-team/issues/543 * add wildcard entry type refs elastic/security-team/issues/543 refs https://github.com/elastic/kibana/pull/97623#pullrequestreview-642618462 * use the new entry type refs elastic/security-team/issues/543 refs https://github.com/elastic/kibana/pull/97623#pullrequestreview-642618462 * update tests refs elastic/security-team/issues/543 refs https://github.com/elastic/kibana/pull/97623#pullrequestreview-642618462 * update name for wildcard type so that it can be used also for cased inputs refs elastic/security-team/issues/543 refsf9cb7eddda
* update artifacts to support wildcard entries refs elastic/security-team/issues/543 * add tests for list schemas refsf9cb7eddda
refs elastic/security-team/issues/543 * add placeholders for path values review changes elastic/kibana/pull/97623#discussion_r620617999 * ignore type check for now * add type assertion refs284352ec9a
* remove unnecessary test refs2ac56ee839
* fix types refsf9cb7eddda
refsb3f5dc4553
* add a note to entries review changes refsdbd3532149
* remove redundant type assertions review changes refsbcf615ac98
refsb3f5dc4553
* move placeholder text logic to utils review changes elastic/kibana/pull/97623#discussion_r621673881 refs6f2d0d7810
* pass the style as prop review changes * update api doc CI check suggestion * make placeholderText a function expression review suggestion elastic/kibana/pull/97623/commits/2dc4fd390cf5ea0e4fa67b3f5fc2561cbb29555e * use semantic names for functions refs330731ebfc
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c03a8306ad
commit
c93e028e0c
30 changed files with 678 additions and 97 deletions
|
@ -207,7 +207,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/public/plugin.tsx",
|
||||
"lineNumber": 353
|
||||
"lineNumber": 346
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -221,7 +221,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/public/plugin.tsx",
|
||||
"lineNumber": 353
|
||||
"lineNumber": 346
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -229,7 +229,7 @@
|
|||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/public/plugin.tsx",
|
||||
"lineNumber": 353
|
||||
"lineNumber": 346
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -245,7 +245,7 @@
|
|||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/public/plugin.tsx",
|
||||
"lineNumber": 398
|
||||
"lineNumber": 391
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -276,7 +276,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/public/types.ts",
|
||||
"lineNumber": 69
|
||||
"lineNumber": 68
|
||||
},
|
||||
"signature": [
|
||||
"() => Promise<",
|
||||
|
@ -287,7 +287,7 @@
|
|||
],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/public/types.ts",
|
||||
"lineNumber": 68
|
||||
"lineNumber": 67
|
||||
},
|
||||
"lifecycle": "setup",
|
||||
"initialIsOpen": true
|
||||
|
@ -301,7 +301,7 @@
|
|||
"children": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/public/types.ts",
|
||||
"lineNumber": 72
|
||||
"lineNumber": 71
|
||||
},
|
||||
"lifecycle": "start",
|
||||
"initialIsOpen": true
|
||||
|
@ -453,7 +453,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 147
|
||||
"lineNumber": 145
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -461,7 +461,7 @@
|
|||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 147
|
||||
"lineNumber": 145
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -521,7 +521,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 159
|
||||
"lineNumber": 157
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -535,7 +535,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 159
|
||||
"lineNumber": 157
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -543,7 +543,7 @@
|
|||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 159
|
||||
"lineNumber": 157
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -582,7 +582,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 341
|
||||
"lineNumber": 338
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -596,7 +596,7 @@
|
|||
"description": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 341
|
||||
"lineNumber": 338
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -604,7 +604,7 @@
|
|||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 341
|
||||
"lineNumber": 338
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -620,13 +620,13 @@
|
|||
"returnComment": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 423
|
||||
"lineNumber": 412
|
||||
}
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 131
|
||||
"lineNumber": 129
|
||||
},
|
||||
"initialIsOpen": false
|
||||
}
|
||||
|
@ -1484,7 +1484,7 @@
|
|||
"children": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 107
|
||||
"lineNumber": 105
|
||||
},
|
||||
"lifecycle": "setup",
|
||||
"initialIsOpen": true
|
||||
|
@ -1498,7 +1498,7 @@
|
|||
"children": [],
|
||||
"source": {
|
||||
"path": "x-pack/plugins/security_solution/server/plugin.ts",
|
||||
"lineNumber": 110
|
||||
"lineNumber": 108
|
||||
},
|
||||
"lifecycle": "start",
|
||||
"initialIsOpen": true
|
||||
|
|
|
@ -51,6 +51,7 @@ export const OPERATOR_EXCLUDED = 'excluded';
|
|||
export const ENTRY_VALUE = 'some host name';
|
||||
export const MATCH = 'match';
|
||||
export const MATCH_ANY = 'match_any';
|
||||
export const WILDCARD = 'wildcard';
|
||||
export const MAX_IMPORT_PAYLOAD_BYTES = 9000000;
|
||||
export const IMPORT_BUFFER_SIZE = 1000;
|
||||
export const LIST = 'list';
|
||||
|
|
|
@ -287,6 +287,7 @@ export enum OperatorTypeEnum {
|
|||
NESTED = 'nested',
|
||||
MATCH = 'match',
|
||||
MATCH_ANY = 'match_any',
|
||||
WILDCARD = 'wildcard',
|
||||
EXISTS = 'exists',
|
||||
LIST = 'list',
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
import { NonEmptyString } from '../../../shared_imports';
|
||||
import { operatorIncluded } from '../../common/schemas';
|
||||
|
||||
export const endpointEntryMatchWildcard = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator: operatorIncluded,
|
||||
type: t.keyof({ wildcard: null }),
|
||||
value: NonEmptyString,
|
||||
})
|
||||
);
|
||||
export type EndpointEntryMatchWildcard = t.TypeOf<typeof endpointEntryMatchWildcard>;
|
|
@ -12,12 +12,28 @@ import { entriesMatch } from './entry_match';
|
|||
import { entriesExists } from './entry_exists';
|
||||
import { entriesList } from './entry_list';
|
||||
import { entriesNested } from './entry_nested';
|
||||
import { entriesMatchWildcard } from './entry_match_wildcard';
|
||||
|
||||
export const entry = t.union([entriesMatch, entriesMatchAny, entriesList, entriesExists]);
|
||||
// NOTE: Type nested is not included here to denote it's non-recursive nature.
|
||||
// So a nested entry is really just a collection of `Entry` types.
|
||||
export const entry = t.union([
|
||||
entriesMatch,
|
||||
entriesMatchAny,
|
||||
entriesList,
|
||||
entriesExists,
|
||||
entriesMatchWildcard,
|
||||
]);
|
||||
export type Entry = t.TypeOf<typeof entry>;
|
||||
|
||||
export const entriesArray = t.array(
|
||||
t.union([entriesMatch, entriesMatchAny, entriesList, entriesExists, entriesNested])
|
||||
t.union([
|
||||
entriesMatch,
|
||||
entriesMatchAny,
|
||||
entriesList,
|
||||
entriesExists,
|
||||
entriesNested,
|
||||
entriesMatchWildcard,
|
||||
])
|
||||
);
|
||||
export type EntriesArray = t.TypeOf<typeof entriesArray>;
|
||||
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* 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 { ENTRY_VALUE, FIELD, OPERATOR, WILDCARD } from '../../constants.mock';
|
||||
|
||||
import { EntryMatchWildcard } from './entry_match_wildcard';
|
||||
|
||||
export const getEntryMatchWildcardMock = (): EntryMatchWildcard => ({
|
||||
field: FIELD,
|
||||
operator: OPERATOR,
|
||||
type: WILDCARD,
|
||||
value: ENTRY_VALUE,
|
||||
});
|
||||
|
||||
export const getEntryMatchWildcardExcludeMock = (): EntryMatchWildcard => ({
|
||||
...getEntryMatchWildcardMock(),
|
||||
operator: 'excluded',
|
||||
});
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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 { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
|
||||
import { foldLeftRight, getPaths } from '../../shared_imports';
|
||||
|
||||
import { getEntryMatchWildcardMock } from './entry_match_wildcard.mock';
|
||||
import { EntryMatchWildcard, entriesMatchWildcard } from './entry_match_wildcard';
|
||||
|
||||
describe('entriesMatchWildcard', () => {
|
||||
test('it should validate an entry', () => {
|
||||
const payload = getEntryMatchWildcardMock();
|
||||
const decoded = entriesMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when operator is "included"', () => {
|
||||
const payload = getEntryMatchWildcardMock();
|
||||
const decoded = entriesMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate when "operator" is "excluded"', () => {
|
||||
const payload = getEntryMatchWildcardMock();
|
||||
payload.operator = 'excluded';
|
||||
const decoded = entriesMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "field" is empty string', () => {
|
||||
const payload: Omit<EntryMatchWildcard, 'field'> & { field: string } = {
|
||||
...getEntryMatchWildcardMock(),
|
||||
field: '',
|
||||
};
|
||||
const decoded = entriesMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "value" is not string', () => {
|
||||
const payload: Omit<EntryMatchWildcard, 'value'> & { value: string[] } = {
|
||||
...getEntryMatchWildcardMock(),
|
||||
value: ['some value'],
|
||||
};
|
||||
const decoded = entriesMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "["some value"]" supplied to "value"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "value" is empty string', () => {
|
||||
const payload: Omit<EntryMatchWildcard, 'value'> & { value: string } = {
|
||||
...getEntryMatchWildcardMock(),
|
||||
value: '',
|
||||
};
|
||||
const decoded = entriesMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "value"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should FAIL validation when "type" is not "wildcard"', () => {
|
||||
const payload: Omit<EntryMatchWildcard, 'type'> & { type: string } = {
|
||||
...getEntryMatchWildcardMock(),
|
||||
type: 'match',
|
||||
};
|
||||
const decoded = entriesMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "match" supplied to "type"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should strip out extra keys', () => {
|
||||
const payload: EntryMatchWildcard & {
|
||||
extraKey?: string;
|
||||
} = getEntryMatchWildcardMock();
|
||||
payload.extraKey = 'some value';
|
||||
const decoded = entriesMatchWildcard.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(getEntryMatchWildcardMock());
|
||||
});
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 * as t from 'io-ts';
|
||||
|
||||
import { NonEmptyString } from '../../shared_imports';
|
||||
import { operator } from '../common/schemas';
|
||||
|
||||
export const entriesMatchWildcard = t.exact(
|
||||
t.type({
|
||||
field: NonEmptyString,
|
||||
operator,
|
||||
type: t.keyof({ wildcard: null }),
|
||||
value: NonEmptyString,
|
||||
})
|
||||
);
|
||||
export type EntryMatchWildcard = t.TypeOf<typeof entriesMatchWildcard>;
|
|
@ -15,6 +15,7 @@ export * from './default_namespace';
|
|||
export * from './entries';
|
||||
export * from './entry_match';
|
||||
export * from './entry_match_any';
|
||||
export * from './entry_match_wildcard';
|
||||
export * from './entry_list';
|
||||
export * from './entry_exists';
|
||||
export * from './entry_nested';
|
||||
|
|
|
@ -20,6 +20,7 @@ export {
|
|||
EntryExists,
|
||||
EntryMatch,
|
||||
EntryMatchAny,
|
||||
EntryMatchWildcard,
|
||||
EntryNested,
|
||||
EntryList,
|
||||
EntriesArray,
|
||||
|
@ -39,6 +40,7 @@ export {
|
|||
nestedEntryItem,
|
||||
entriesMatch,
|
||||
entriesMatchAny,
|
||||
entriesMatchWildcard,
|
||||
entriesExists,
|
||||
entriesList,
|
||||
namespaceType,
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
EntryExists,
|
||||
EntryMatch,
|
||||
EntryMatchAny,
|
||||
EntryMatchWildcard,
|
||||
EntryNested,
|
||||
ExceptionListItemSchema,
|
||||
OperatorEnum,
|
||||
|
@ -34,7 +35,7 @@ export interface EmptyEntry {
|
|||
id: string;
|
||||
field: string | undefined;
|
||||
operator: OperatorEnum;
|
||||
type: OperatorTypeEnum.MATCH | OperatorTypeEnum.MATCH_ANY;
|
||||
type: OperatorTypeEnum.MATCH | OperatorTypeEnum.MATCH_ANY | OperatorTypeEnum.WILDCARD;
|
||||
value: string | string[] | undefined;
|
||||
}
|
||||
|
||||
|
@ -53,6 +54,7 @@ export interface EmptyNestedEntry {
|
|||
entries: Array<
|
||||
| (EntryMatch & { id?: string })
|
||||
| (EntryMatchAny & { id?: string })
|
||||
| (EntryMatchWildcard & { id?: string })
|
||||
| (EntryExists & { id?: string })
|
||||
>;
|
||||
}
|
||||
|
@ -69,6 +71,7 @@ export type BuilderEntryNested = Omit<EntryNested, 'entries'> & {
|
|||
entries: Array<
|
||||
| (EntryMatch & { id?: string })
|
||||
| (EntryMatchAny & { id?: string })
|
||||
| (EntryMatchWildcard & { id?: string })
|
||||
| (EntryExists & { id?: string })
|
||||
>;
|
||||
};
|
||||
|
|
|
@ -247,6 +247,30 @@ describe('When invoking Trusted Apps Schema', () => {
|
|||
expect(() => body.validate(bodyMsg)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate `entry.type` does not accept `wildcard` when field is NOT PATH', () => {
|
||||
const bodyMsg = createNewTrustedApp({
|
||||
entries: [
|
||||
createConditionEntry({
|
||||
field: ConditionEntryField.HASH,
|
||||
type: 'wildcard',
|
||||
}),
|
||||
],
|
||||
});
|
||||
expect(() => body.validate(bodyMsg)).toThrow();
|
||||
});
|
||||
|
||||
it('should validate `entry.type` accepts `wildcard` when field is PATH', () => {
|
||||
const bodyMsg = createNewTrustedApp({
|
||||
entries: [
|
||||
createConditionEntry({
|
||||
field: ConditionEntryField.PATH,
|
||||
type: 'wildcard',
|
||||
}),
|
||||
],
|
||||
});
|
||||
expect(() => body.validate(bodyMsg)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate `entry.value` required', () => {
|
||||
const { value, ...entry } = createConditionEntry();
|
||||
expect(() => body.validate(createNewTrustedApp({ entries: [entry] }))).toThrow();
|
||||
|
|
|
@ -29,7 +29,12 @@ export const GetTrustedAppsRequestSchema = {
|
|||
}),
|
||||
};
|
||||
|
||||
const ConditionEntryTypeSchema = schema.literal('match');
|
||||
const ConditionEntryTypeSchema = schema.conditional(
|
||||
schema.siblingRef('field'),
|
||||
ConditionEntryField.PATH,
|
||||
schema.oneOf([schema.literal('match'), schema.literal('wildcard')]),
|
||||
schema.literal('match')
|
||||
);
|
||||
const ConditionEntryOperatorSchema = schema.literal('included');
|
||||
|
||||
/*
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { ApplicationStart } from 'kibana/public';
|
||||
|
||||
import {
|
||||
DeleteTrustedAppsRequestSchema,
|
||||
GetOneTrustedAppRequestSchema,
|
||||
|
@ -69,9 +70,15 @@ export enum ConditionEntryField {
|
|||
SIGNER = 'process.Ext.code_signature',
|
||||
}
|
||||
|
||||
export enum OperatorFieldIds {
|
||||
is = 'is',
|
||||
matches = 'matches',
|
||||
}
|
||||
|
||||
export type TrustedAppEntryTypes = 'match' | 'wildcard';
|
||||
export interface ConditionEntry<T extends ConditionEntryField = ConditionEntryField> {
|
||||
field: T;
|
||||
type: 'match';
|
||||
type: TrustedAppEntryTypes;
|
||||
operator: 'included';
|
||||
value: string;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ export {
|
|||
EntryExists,
|
||||
EntryMatch,
|
||||
EntryMatchAny,
|
||||
EntryMatchWildcard,
|
||||
EntryNested,
|
||||
EntryList,
|
||||
EntriesArray,
|
||||
|
@ -38,6 +39,7 @@ export {
|
|||
nestedEntryItem,
|
||||
entriesMatch,
|
||||
entriesMatchAny,
|
||||
entriesMatchWildcard,
|
||||
entriesExists,
|
||||
entriesList,
|
||||
namespaceType,
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 { getPlaceholderTextByOSType, getPlaceholderText } from './path_placeholder';
|
||||
import { ConditionEntryField, OperatingSystem, TrustedAppEntryTypes } from '../endpoint/types';
|
||||
|
||||
const trustedAppEntry = {
|
||||
os: OperatingSystem.LINUX,
|
||||
field: ConditionEntryField.HASH,
|
||||
type: 'match' as TrustedAppEntryTypes,
|
||||
};
|
||||
|
||||
describe('Trusted Apps: Path placeholder text', () => {
|
||||
it('returns no placeholder text when field IS NOT PATH', () => {
|
||||
expect(getPlaceholderTextByOSType({ ...trustedAppEntry })).toEqual(undefined);
|
||||
});
|
||||
|
||||
it('returns a placeholder text when field IS PATH', () => {
|
||||
expect(
|
||||
getPlaceholderTextByOSType({ ...trustedAppEntry, field: ConditionEntryField.PATH })
|
||||
).toEqual(getPlaceholderText().others.exact);
|
||||
});
|
||||
|
||||
it('returns LINUX/MAC equivalent placeholder when field IS PATH', () => {
|
||||
expect(
|
||||
getPlaceholderTextByOSType({
|
||||
...trustedAppEntry,
|
||||
os: OperatingSystem.MAC,
|
||||
field: ConditionEntryField.PATH,
|
||||
})
|
||||
).toEqual(getPlaceholderText().others.exact);
|
||||
});
|
||||
|
||||
it('returns LINUX/MAC equivalent placeholder text when field IS PATH and WILDCARD operator is selected', () => {
|
||||
expect(
|
||||
getPlaceholderTextByOSType({
|
||||
...trustedAppEntry,
|
||||
os: OperatingSystem.LINUX,
|
||||
field: ConditionEntryField.PATH,
|
||||
type: 'wildcard',
|
||||
})
|
||||
).toEqual(getPlaceholderText().others.wildcard);
|
||||
});
|
||||
|
||||
it('returns WINDOWS equivalent placeholder text when field IS PATH', () => {
|
||||
expect(
|
||||
getPlaceholderTextByOSType({
|
||||
...trustedAppEntry,
|
||||
os: OperatingSystem.WINDOWS,
|
||||
field: ConditionEntryField.PATH,
|
||||
})
|
||||
).toEqual(getPlaceholderText().windows.exact);
|
||||
});
|
||||
|
||||
it('returns WINDOWS equivalent placeholder text when field IS PATH and WILDCARD operator is selected', () => {
|
||||
expect(
|
||||
getPlaceholderTextByOSType({
|
||||
...trustedAppEntry,
|
||||
os: OperatingSystem.WINDOWS,
|
||||
field: ConditionEntryField.PATH,
|
||||
type: 'wildcard',
|
||||
})
|
||||
).toEqual(getPlaceholderText().windows.wildcard);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { ConditionEntryField, OperatingSystem, TrustedAppEntryTypes } from '../endpoint/types';
|
||||
|
||||
export const getPlaceholderText = () => ({
|
||||
windows: {
|
||||
wildcard: 'C:\\sample\\**\\*',
|
||||
exact: 'C:\\sample\\path.exe',
|
||||
},
|
||||
others: {
|
||||
wildcard: '/opt/**/*',
|
||||
exact: '/opt/bin',
|
||||
},
|
||||
});
|
||||
|
||||
export const getPlaceholderTextByOSType = ({
|
||||
os,
|
||||
field,
|
||||
type,
|
||||
}: {
|
||||
os: OperatingSystem;
|
||||
field: ConditionEntryField;
|
||||
type: TrustedAppEntryTypes;
|
||||
}): string | undefined => {
|
||||
if (field === ConditionEntryField.PATH) {
|
||||
if (os === OperatingSystem.WINDOWS) {
|
||||
if (type === 'wildcard') {
|
||||
return getPlaceholderText().windows.wildcard;
|
||||
}
|
||||
return getPlaceholderText().windows.exact;
|
||||
} else {
|
||||
if (type === 'wildcard') {
|
||||
return getPlaceholderText().others.wildcard;
|
||||
}
|
||||
return getPlaceholderText().others.exact;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -15,6 +15,7 @@ import {
|
|||
Entry,
|
||||
EntryMatch,
|
||||
EntryMatchAny,
|
||||
EntryMatchWildcard,
|
||||
EntryExists,
|
||||
ExceptionListItemSchema,
|
||||
CreateExceptionListItemSchema,
|
||||
|
@ -92,6 +93,7 @@ export interface EmptyNestedEntry {
|
|||
type: OperatorTypeEnum.NESTED;
|
||||
entries: Array<
|
||||
| (EntryMatch & { id?: string })
|
||||
| (EntryMatchWildcard & { id?: string })
|
||||
| (EntryMatchAny & { id?: string })
|
||||
| (EntryExists & { id?: string })
|
||||
>;
|
||||
|
@ -108,6 +110,7 @@ export type BuilderEntryNested = Omit<EntryNested, 'entries'> & {
|
|||
id?: string;
|
||||
entries: Array<
|
||||
| (EntryMatch & { id?: string })
|
||||
| (EntryMatchWildcard & { id?: string })
|
||||
| (EntryMatchAny & { id?: string })
|
||||
| (EntryExists & { id?: string })
|
||||
>;
|
||||
|
|
|
@ -21,7 +21,7 @@ let onRemoveMock: jest.Mock;
|
|||
let onChangeMock: jest.Mock;
|
||||
let onVisitedMock: jest.Mock;
|
||||
|
||||
const entry: Readonly<ConditionEntry> = {
|
||||
const baseEntry: Readonly<ConditionEntry> = {
|
||||
field: ConditionEntryField.HASH,
|
||||
type: 'match',
|
||||
operator: 'included',
|
||||
|
@ -38,7 +38,8 @@ describe('Condition entry input', () => {
|
|||
const getElement = (
|
||||
subject: string,
|
||||
os: OperatingSystem = OperatingSystem.WINDOWS,
|
||||
isRemoveDisabled: boolean = false
|
||||
isRemoveDisabled: boolean = false,
|
||||
entry: ConditionEntry = baseEntry
|
||||
) => (
|
||||
<ConditionEntryInput
|
||||
os={os}
|
||||
|
@ -64,10 +65,10 @@ describe('Condition entry input', () => {
|
|||
expect(onChangeMock).toHaveBeenCalledTimes(1);
|
||||
expect(onChangeMock).toHaveBeenCalledWith(
|
||||
{
|
||||
...entry,
|
||||
...baseEntry,
|
||||
field: { target: { value: field } },
|
||||
},
|
||||
entry
|
||||
baseEntry
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -77,7 +78,7 @@ describe('Condition entry input', () => {
|
|||
expect(onRemoveMock).toHaveBeenCalledTimes(0);
|
||||
element.find('[data-test-subj="testOnRemove-remove"]').first().simulate('click');
|
||||
expect(onRemoveMock).toHaveBeenCalledTimes(1);
|
||||
expect(onRemoveMock).toHaveBeenCalledWith(entry);
|
||||
expect(onRemoveMock).toHaveBeenCalledWith(baseEntry);
|
||||
});
|
||||
|
||||
it('should not be able to call on remove for field input because disabled', () => {
|
||||
|
@ -92,7 +93,7 @@ describe('Condition entry input', () => {
|
|||
expect(onVisitedMock).toHaveBeenCalledTimes(0);
|
||||
element.find('[data-test-subj="testOnVisited-value"]').first().simulate('blur');
|
||||
expect(onVisitedMock).toHaveBeenCalledTimes(1);
|
||||
expect(onVisitedMock).toHaveBeenCalledWith(entry);
|
||||
expect(onVisitedMock).toHaveBeenCalledWith(baseEntry);
|
||||
});
|
||||
|
||||
it('should change value for field input', () => {
|
||||
|
@ -105,10 +106,10 @@ describe('Condition entry input', () => {
|
|||
expect(onChangeMock).toHaveBeenCalledTimes(1);
|
||||
expect(onChangeMock).toHaveBeenCalledWith(
|
||||
{
|
||||
...entry,
|
||||
...baseEntry,
|
||||
value: 'new value',
|
||||
},
|
||||
entry
|
||||
baseEntry
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -138,4 +139,24 @@ describe('Condition entry input', () => {
|
|||
.props() as EuiSuperSelectProps<string>;
|
||||
expect(superSelectProps.options.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should have operator value selected when field is HASH', () => {
|
||||
const element = shallow(getElement('testOperatorOptions'));
|
||||
const inputField = element.find('[data-test-subj="testOperatorOptions-operator"]');
|
||||
expect(inputField.contains('is'));
|
||||
});
|
||||
|
||||
it('should show operator dorpdown with two values when field is PATH', () => {
|
||||
const element = shallow(
|
||||
getElement('testOperatorOptions', undefined, undefined, {
|
||||
...baseEntry,
|
||||
field: ConditionEntryField.PATH,
|
||||
})
|
||||
);
|
||||
const superSelectProps = element
|
||||
.find('[data-test-subj="testOperatorOptions-operator"]')
|
||||
.first()
|
||||
.props() as EuiSuperSelectProps<string>;
|
||||
expect(superSelectProps.options.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,12 +6,11 @@
|
|||
*/
|
||||
|
||||
import React, { ChangeEventHandler, memo, useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiButtonIcon,
|
||||
EuiFieldText,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFormRow,
|
||||
EuiSuperSelect,
|
||||
EuiSuperSelectOption,
|
||||
|
@ -21,6 +20,7 @@ import {
|
|||
import {
|
||||
ConditionEntry,
|
||||
ConditionEntryField,
|
||||
OperatorFieldIds,
|
||||
OperatingSystem,
|
||||
} from '../../../../../../../common/endpoint/types';
|
||||
|
||||
|
@ -28,9 +28,10 @@ import {
|
|||
CONDITION_FIELD_DESCRIPTION,
|
||||
CONDITION_FIELD_TITLE,
|
||||
ENTRY_PROPERTY_TITLES,
|
||||
OPERATOR_TITLE,
|
||||
OPERATOR_TITLES,
|
||||
} from '../../translations';
|
||||
import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator';
|
||||
import { getPlaceholderTextByOSType } from '../../../../../../../common/utils/path_placeholder';
|
||||
|
||||
const ConditionEntryCell = memo<{
|
||||
showLabel: boolean;
|
||||
|
@ -66,6 +67,27 @@ export interface ConditionEntryInputProps {
|
|||
'data-test-subj'?: string;
|
||||
}
|
||||
|
||||
// adding a style prop on EuiFlexGroup works only partially
|
||||
// and for some odd reason garbles up gridTemplateAreas entry
|
||||
const InputGroup = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 25% 25% 45% 5%;
|
||||
grid-template-areas: 'field operator value remove';
|
||||
`;
|
||||
|
||||
const InputItem = styled.div<{ gridArea: string }>`
|
||||
grid-area: ${({ gridArea }) => gridArea};
|
||||
align-self: center;
|
||||
margin: 4px;
|
||||
vertical-align: baseline;
|
||||
`;
|
||||
|
||||
const operatorOptions = (Object.keys(OperatorFieldIds) as OperatorFieldIds[]).map((value) => ({
|
||||
dropdownDisplay: OPERATOR_TITLES[value],
|
||||
inputDisplay: OPERATOR_TITLES[value],
|
||||
value: value === 'matches' ? 'wildcard' : 'match',
|
||||
}));
|
||||
|
||||
export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
||||
({
|
||||
os,
|
||||
|
@ -122,6 +144,11 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
|||
[entry, onChange]
|
||||
);
|
||||
|
||||
const handleOperatorUpdate = useCallback(
|
||||
(newOperator) => onChange({ ...entry, type: newOperator }, entry),
|
||||
[entry, onChange]
|
||||
);
|
||||
|
||||
const handleRemoveClick = useCallback(() => onRemove(entry), [entry, onRemove]);
|
||||
|
||||
const handleValueOnBlur = useCallback(() => {
|
||||
|
@ -131,14 +158,8 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
|||
}, [entry, onVisited]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
gutterSize="s"
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
data-test-subj={dataTestSubj}
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItem grow={2}>
|
||||
<InputGroup data-test-subj={dataTestSubj}>
|
||||
<InputItem gridArea="field">
|
||||
<ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.field}>
|
||||
<EuiSuperSelect
|
||||
options={fieldOptions}
|
||||
|
@ -147,17 +168,36 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
|||
data-test-subj={getTestId('field')}
|
||||
/>
|
||||
</ConditionEntryCell>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
</InputItem>
|
||||
<InputItem gridArea="operator">
|
||||
<ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.operator}>
|
||||
<EuiFieldText name="operator" value={OPERATOR_TITLE.included} readOnly />
|
||||
{entry.field === ConditionEntryField.PATH ? (
|
||||
<EuiSuperSelect
|
||||
options={operatorOptions}
|
||||
onChange={handleOperatorUpdate}
|
||||
valueOfSelected={entry.type}
|
||||
data-test-subj={getTestId('operator')}
|
||||
/>
|
||||
) : (
|
||||
<EuiFieldText
|
||||
name="operator"
|
||||
value={OPERATOR_TITLES.is}
|
||||
data-test-subj={getTestId('operator')}
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
</ConditionEntryCell>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={3}>
|
||||
</InputItem>
|
||||
<InputItem gridArea="value">
|
||||
<ConditionEntryCell showLabel={showLabels} label={ENTRY_PROPERTY_TITLES.value}>
|
||||
<EuiFieldText
|
||||
name="value"
|
||||
value={entry.value}
|
||||
placeholder={getPlaceholderTextByOSType({
|
||||
os,
|
||||
field: entry.field,
|
||||
type: entry.type,
|
||||
})}
|
||||
fullWidth
|
||||
required
|
||||
onChange={handleValueUpdate}
|
||||
|
@ -165,8 +205,8 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
|||
data-test-subj={getTestId('value')}
|
||||
/>
|
||||
</ConditionEntryCell>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
</InputItem>
|
||||
<InputItem gridArea="remove">
|
||||
{/* Unicode `nbsp` is used below so that Remove button is property displayed */}
|
||||
<ConditionEntryCell showLabel={showLabels} label={'\u00A0'}>
|
||||
<EuiButtonIcon
|
||||
|
@ -181,8 +221,8 @@ export const ConditionEntryInput = memo<ConditionEntryInputProps>(
|
|||
data-test-subj={getTestId('remove')}
|
||||
/>
|
||||
</ConditionEntryCell>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</InputItem>
|
||||
</InputGroup>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
ENTRY_PROPERTY_TITLES,
|
||||
CARD_DELETE_BUTTON_LABEL,
|
||||
CONDITION_FIELD_TITLE,
|
||||
OPERATOR_TITLE,
|
||||
OPERATOR_TITLES,
|
||||
CARD_EDIT_BUTTON_LABEL,
|
||||
} from '../../translations';
|
||||
|
||||
|
@ -45,7 +45,7 @@ const getEntriesColumnDefinitions = (): Array<EuiTableFieldDataColumnType<Entry>
|
|||
truncateText: true,
|
||||
textOnly: true,
|
||||
width: '30%',
|
||||
render(field: Entry['field'], entry: Entry) {
|
||||
render(field: Entry['field'], _entry: Entry) {
|
||||
return CONDITION_FIELD_TITLE[field];
|
||||
},
|
||||
},
|
||||
|
@ -55,8 +55,8 @@ const getEntriesColumnDefinitions = (): Array<EuiTableFieldDataColumnType<Entry>
|
|||
sortable: false,
|
||||
truncateText: true,
|
||||
width: '20%',
|
||||
render(field: Entry['operator'], entry: Entry) {
|
||||
return OPERATOR_TITLE[field];
|
||||
render(_field: Entry['operator'], entry: Entry) {
|
||||
return entry.type === 'wildcard' ? OPERATOR_TITLES.matches : OPERATOR_TITLES.is;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -10,8 +10,8 @@ import {
|
|||
TrustedApp,
|
||||
MacosLinuxConditionEntry,
|
||||
WindowsConditionEntry,
|
||||
ConditionEntry,
|
||||
ConditionEntryField,
|
||||
OperatorFieldIds,
|
||||
} from '../../../../../common/endpoint/types';
|
||||
|
||||
export { OS_TITLES } from '../../../common/translations';
|
||||
|
@ -52,10 +52,13 @@ export const CONDITION_FIELD_DESCRIPTION: { [K in ConditionEntryField]: string }
|
|||
),
|
||||
};
|
||||
|
||||
export const OPERATOR_TITLE: { [K in ConditionEntry['operator']]: string } = {
|
||||
included: i18n.translate('xpack.securitySolution.trustedapps.card.operator.includes', {
|
||||
export const OPERATOR_TITLES: { [K in OperatorFieldIds]: string } = {
|
||||
is: i18n.translate('xpack.securitySolution.trustedapps.card.operator.is', {
|
||||
defaultMessage: 'is',
|
||||
}),
|
||||
matches: i18n.translate('xpack.securitySolution.trustedapps.card.operator.matches', {
|
||||
defaultMessage: 'matches',
|
||||
}),
|
||||
};
|
||||
|
||||
export const PROPERTY_TITLES: Readonly<
|
||||
|
|
|
@ -22,6 +22,8 @@ import {
|
|||
translatedEntryMatchAnyMatcher,
|
||||
TranslatedEntryMatcher,
|
||||
translatedEntryMatchMatcher,
|
||||
TranslatedEntryMatchWildcardMatcher,
|
||||
translatedEntryMatchWildcardMatcher,
|
||||
TranslatedEntryNestedEntry,
|
||||
translatedEntryNestedEntry,
|
||||
TranslatedExceptionListItem,
|
||||
|
@ -203,6 +205,10 @@ function getMatcherFunction(field: string, matchAny?: boolean): TranslatedEntryM
|
|||
: 'exact_cased';
|
||||
}
|
||||
|
||||
function getMatcherWildcardFunction(field: string): TranslatedEntryMatchWildcardMatcher {
|
||||
return field.endsWith('.caseless') ? 'wildcard_caseless' : 'wildcard_cased';
|
||||
}
|
||||
|
||||
function normalizeFieldName(field: string): string {
|
||||
return field.endsWith('.caseless') ? field.substring(0, field.lastIndexOf('.')) : field;
|
||||
}
|
||||
|
@ -272,6 +278,17 @@ function translateEntry(
|
|||
}
|
||||
: undefined;
|
||||
}
|
||||
case 'wildcard': {
|
||||
const matcher = getMatcherWildcardFunction(entry.field);
|
||||
return translatedEntryMatchWildcardMatcher.is(matcher)
|
||||
? {
|
||||
field: normalizeFieldName(entry.field),
|
||||
operator: entry.operator,
|
||||
type: matcher,
|
||||
value: entry.value,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,8 +66,8 @@ const NEW_TRUSTED_APP: NewTrustedApp = {
|
|||
os: OperatingSystem.LINUX,
|
||||
effectScope: { type: 'global' },
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.PATH, '/bin/malware'),
|
||||
createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'),
|
||||
createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'),
|
||||
createConditionEntry(ConditionEntryField.HASH, 'match', '1234234659af249ddf3e40864e9fb241'),
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -83,8 +83,8 @@ const TRUSTED_APP: TrustedApp = {
|
|||
os: OperatingSystem.LINUX,
|
||||
effectScope: { type: 'global' },
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'),
|
||||
createConditionEntry(ConditionEntryField.PATH, '/bin/malware'),
|
||||
createConditionEntry(ConditionEntryField.HASH, 'match', '1234234659af249ddf3e40864e9fb241'),
|
||||
createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'),
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -79,13 +79,19 @@ describe('mapping', () => {
|
|||
description: 'Linux Trusted App',
|
||||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.LINUX,
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')],
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')],
|
||||
},
|
||||
createExceptionListItemOptions({
|
||||
name: 'linux trusted app',
|
||||
description: 'Linux Trusted App',
|
||||
osTypes: ['linux'],
|
||||
entries: [createEntryMatch('process.executable.caseless', '/bin/malware')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.executable.caseless',
|
||||
|
||||
'/bin/malware'
|
||||
),
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -97,13 +103,19 @@ describe('mapping', () => {
|
|||
description: 'MacOS Trusted App',
|
||||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.MAC,
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')],
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')],
|
||||
},
|
||||
createExceptionListItemOptions({
|
||||
name: 'macos trusted app',
|
||||
description: 'MacOS Trusted App',
|
||||
osTypes: ['macos'],
|
||||
entries: [createEntryMatch('process.executable.caseless', '/bin/malware')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.executable.caseless',
|
||||
|
||||
'/bin/malware'
|
||||
),
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -115,13 +127,21 @@ describe('mapping', () => {
|
|||
description: 'Windows Trusted App',
|
||||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.WINDOWS,
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, 'C:\\Program Files\\Malware')],
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.PATH, 'match', 'C:\\Program Files\\Malware'),
|
||||
],
|
||||
},
|
||||
createExceptionListItemOptions({
|
||||
name: 'windows trusted app',
|
||||
description: 'Windows Trusted App',
|
||||
osTypes: ['windows'],
|
||||
entries: [createEntryMatch('process.executable.caseless', 'C:\\Program Files\\Malware')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.executable.caseless',
|
||||
|
||||
'C:\\Program Files\\Malware'
|
||||
),
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -133,7 +153,7 @@ describe('mapping', () => {
|
|||
description: 'Signed Trusted App',
|
||||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.WINDOWS,
|
||||
entries: [createConditionEntry(ConditionEntryField.SIGNER, 'Microsoft Windows')],
|
||||
entries: [createConditionEntry(ConditionEntryField.SIGNER, 'match', 'Microsoft Windows')],
|
||||
},
|
||||
createExceptionListItemOptions({
|
||||
name: 'Signed trusted app',
|
||||
|
@ -157,14 +177,24 @@ describe('mapping', () => {
|
|||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.LINUX,
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'),
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'match',
|
||||
'1234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
},
|
||||
createExceptionListItemOptions({
|
||||
name: 'MD5 trusted app',
|
||||
description: 'MD5 Trusted App',
|
||||
osTypes: ['linux'],
|
||||
entries: [createEntryMatch('process.hash.md5', '1234234659af249ddf3e40864e9fb241')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.hash.md5',
|
||||
|
||||
'1234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -179,6 +209,7 @@ describe('mapping', () => {
|
|||
entries: [
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'match',
|
||||
'f635da961234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
|
@ -188,7 +219,11 @@ describe('mapping', () => {
|
|||
description: 'SHA1 Trusted App',
|
||||
osTypes: ['linux'],
|
||||
entries: [
|
||||
createEntryMatch('process.hash.sha1', 'f635da961234234659af249ddf3e40864e9fb241'),
|
||||
createEntryMatch(
|
||||
'process.hash.sha1',
|
||||
|
||||
'f635da961234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
@ -204,6 +239,7 @@ describe('mapping', () => {
|
|||
entries: [
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'match',
|
||||
'f635da96124659af249ddf3e40864e9fb234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
|
@ -215,6 +251,7 @@ describe('mapping', () => {
|
|||
entries: [
|
||||
createEntryMatch(
|
||||
'process.hash.sha256',
|
||||
|
||||
'f635da96124659af249ddf3e40864e9fb234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
|
@ -230,14 +267,24 @@ describe('mapping', () => {
|
|||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.LINUX,
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.HASH, '1234234659Af249ddf3e40864E9FB241'),
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'match',
|
||||
'1234234659Af249ddf3e40864E9FB241'
|
||||
),
|
||||
],
|
||||
},
|
||||
createExceptionListItemOptions({
|
||||
name: 'MD5 trusted app',
|
||||
description: 'MD5 Trusted App',
|
||||
osTypes: ['linux'],
|
||||
entries: [createEntryMatch('process.hash.md5', '1234234659af249ddf3e40864e9fb241')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.hash.md5',
|
||||
|
||||
'1234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -257,7 +304,13 @@ describe('mapping', () => {
|
|||
created_at: '11/11/2011T11:11:11.111',
|
||||
created_by: 'admin',
|
||||
os_types: ['linux'],
|
||||
entries: [createEntryMatch('process.executable.caseless', '/bin/malware')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.executable.caseless',
|
||||
|
||||
'/bin/malware'
|
||||
),
|
||||
],
|
||||
}),
|
||||
{
|
||||
id: '123',
|
||||
|
@ -270,7 +323,7 @@ describe('mapping', () => {
|
|||
updated_at: '11/11/2011T11:11:11.111',
|
||||
updated_by: 'admin',
|
||||
os: OperatingSystem.LINUX,
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')],
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -284,7 +337,13 @@ describe('mapping', () => {
|
|||
created_at: '11/11/2011T11:11:11.111',
|
||||
created_by: 'admin',
|
||||
os_types: ['macos'],
|
||||
entries: [createEntryMatch('process.executable.caseless', '/bin/malware')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.executable.caseless',
|
||||
|
||||
'/bin/malware'
|
||||
),
|
||||
],
|
||||
}),
|
||||
{
|
||||
id: '123',
|
||||
|
@ -297,7 +356,7 @@ describe('mapping', () => {
|
|||
updated_at: '11/11/2011T11:11:11.111',
|
||||
updated_by: 'admin',
|
||||
os: OperatingSystem.MAC,
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')],
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -311,7 +370,13 @@ describe('mapping', () => {
|
|||
created_at: '11/11/2011T11:11:11.111',
|
||||
created_by: 'admin',
|
||||
os_types: ['windows'],
|
||||
entries: [createEntryMatch('process.executable.caseless', 'C:\\Program Files\\Malware')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.executable.caseless',
|
||||
|
||||
'C:\\Program Files\\Malware'
|
||||
),
|
||||
],
|
||||
}),
|
||||
{
|
||||
id: '123',
|
||||
|
@ -324,7 +389,9 @@ describe('mapping', () => {
|
|||
updated_at: '11/11/2011T11:11:11.111',
|
||||
updated_by: 'admin',
|
||||
os: OperatingSystem.WINDOWS,
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, 'C:\\Program Files\\Malware')],
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.PATH, 'match', 'C:\\Program Files\\Malware'),
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -356,7 +423,7 @@ describe('mapping', () => {
|
|||
updated_at: '11/11/2011T11:11:11.111',
|
||||
updated_by: 'admin',
|
||||
os: OperatingSystem.WINDOWS,
|
||||
entries: [createConditionEntry(ConditionEntryField.SIGNER, 'Microsoft Windows')],
|
||||
entries: [createConditionEntry(ConditionEntryField.SIGNER, 'match', 'Microsoft Windows')],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -370,7 +437,13 @@ describe('mapping', () => {
|
|||
created_at: '11/11/2011T11:11:11.111',
|
||||
created_by: 'admin',
|
||||
os_types: ['linux'],
|
||||
entries: [createEntryMatch('process.hash.md5', '1234234659af249ddf3e40864e9fb241')],
|
||||
entries: [
|
||||
createEntryMatch(
|
||||
'process.hash.md5',
|
||||
|
||||
'1234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
}),
|
||||
{
|
||||
id: '123',
|
||||
|
@ -384,7 +457,11 @@ describe('mapping', () => {
|
|||
updated_by: 'admin',
|
||||
os: OperatingSystem.LINUX,
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'),
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'match',
|
||||
'1234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
}
|
||||
);
|
||||
|
@ -400,7 +477,11 @@ describe('mapping', () => {
|
|||
created_by: 'admin',
|
||||
os_types: ['linux'],
|
||||
entries: [
|
||||
createEntryMatch('process.hash.sha1', 'f635da961234234659af249ddf3e40864e9fb241'),
|
||||
createEntryMatch(
|
||||
'process.hash.sha1',
|
||||
|
||||
'f635da961234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
}),
|
||||
{
|
||||
|
@ -417,6 +498,7 @@ describe('mapping', () => {
|
|||
entries: [
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'match',
|
||||
'f635da961234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
|
@ -436,6 +518,7 @@ describe('mapping', () => {
|
|||
entries: [
|
||||
createEntryMatch(
|
||||
'process.hash.sha256',
|
||||
|
||||
'f635da96124659af249ddf3e40864e9fb234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
|
@ -454,6 +537,7 @@ describe('mapping', () => {
|
|||
entries: [
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'match',
|
||||
'f635da96124659af249ddf3e40864e9fb234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
|
@ -469,7 +553,7 @@ describe('mapping', () => {
|
|||
description: 'Linux Trusted App',
|
||||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.LINUX,
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, '/bin/malware')],
|
||||
entries: [createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware')],
|
||||
version: 'abc',
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import { OsType } from '../../../../../lists/common/schemas';
|
|||
import {
|
||||
EntriesArray,
|
||||
EntryMatch,
|
||||
EntryMatchWildcard,
|
||||
EntryNested,
|
||||
ExceptionListItemSchema,
|
||||
NestedEntriesArray,
|
||||
|
@ -28,6 +29,7 @@ import {
|
|||
OperatingSystem,
|
||||
TrustedApp,
|
||||
UpdateTrustedApp,
|
||||
TrustedAppEntryTypes,
|
||||
} from '../../../../common/endpoint/types';
|
||||
|
||||
type ConditionEntriesMap = { [K in ConditionEntryField]?: ConditionEntry<K> };
|
||||
|
@ -46,6 +48,7 @@ const OPERATING_SYSTEM_TO_OS_TYPE: Mapping<OperatingSystem, OsType> = {
|
|||
};
|
||||
|
||||
const POLICY_REFERENCE_PREFIX = 'policy:';
|
||||
const OPERATOR_VALUE = 'included';
|
||||
|
||||
const filterUndefined = <T>(list: Array<T | undefined>): T[] => {
|
||||
return list.filter((item: T | undefined): item is T => item !== undefined);
|
||||
|
@ -53,9 +56,10 @@ const filterUndefined = <T>(list: Array<T | undefined>): T[] => {
|
|||
|
||||
export const createConditionEntry = <T extends ConditionEntryField>(
|
||||
field: T,
|
||||
type: TrustedAppEntryTypes,
|
||||
value: string
|
||||
): ConditionEntry<T> => {
|
||||
return { field, value, type: 'match', operator: 'included' };
|
||||
return { field, value, type, operator: OPERATOR_VALUE };
|
||||
};
|
||||
|
||||
export const tagsToEffectScope = (tags: string[]): EffectScope => {
|
||||
|
@ -78,12 +82,23 @@ export const entriesToConditionEntriesMap = (entries: EntriesArray): ConditionEn
|
|||
if (entry.field.startsWith('process.hash') && entry.type === 'match') {
|
||||
return {
|
||||
...result,
|
||||
[ConditionEntryField.HASH]: createConditionEntry(ConditionEntryField.HASH, entry.value),
|
||||
[ConditionEntryField.HASH]: createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
entry.type,
|
||||
entry.value
|
||||
),
|
||||
};
|
||||
} else if (entry.field === 'process.executable.caseless' && entry.type === 'match') {
|
||||
} else if (
|
||||
entry.field === 'process.executable.caseless' &&
|
||||
(entry.type === 'match' || entry.type === 'wildcard')
|
||||
) {
|
||||
return {
|
||||
...result,
|
||||
[ConditionEntryField.PATH]: createConditionEntry(ConditionEntryField.PATH, entry.value),
|
||||
[ConditionEntryField.PATH]: createConditionEntry(
|
||||
ConditionEntryField.PATH,
|
||||
entry.type,
|
||||
entry.value
|
||||
),
|
||||
};
|
||||
} else if (entry.field === 'process.Ext.code_signature' && entry.type === 'nested') {
|
||||
const subjectNameCondition = entry.entries.find((subEntry): subEntry is EntryMatch => {
|
||||
|
@ -95,6 +110,7 @@ export const entriesToConditionEntriesMap = (entries: EntriesArray): ConditionEn
|
|||
...result,
|
||||
[ConditionEntryField.SIGNER]: createConditionEntry(
|
||||
ConditionEntryField.SIGNER,
|
||||
subjectNameCondition.type,
|
||||
subjectNameCondition.value
|
||||
),
|
||||
};
|
||||
|
@ -166,7 +182,11 @@ const hashType = (hash: string): 'md5' | 'sha256' | 'sha1' | undefined => {
|
|||
};
|
||||
|
||||
export const createEntryMatch = (field: string, value: string): EntryMatch => {
|
||||
return { field, value, type: 'match', operator: 'included' };
|
||||
return { field, value, type: 'match', operator: OPERATOR_VALUE };
|
||||
};
|
||||
|
||||
export const createEntryMatchWildcard = (field: string, value: string): EntryMatchWildcard => {
|
||||
return { field, value, type: 'wildcard', operator: OPERATOR_VALUE };
|
||||
};
|
||||
|
||||
export const createEntryNested = (field: string, entries: NestedEntriesArray): EntryNested => {
|
||||
|
@ -193,6 +213,11 @@ export const conditionEntriesToEntries = (conditionEntries: ConditionEntry[]): E
|
|||
createEntryMatch('trusted', 'true'),
|
||||
createEntryMatch('subject_name', conditionEntry.value),
|
||||
]);
|
||||
} else if (
|
||||
conditionEntry.field === ConditionEntryField.PATH &&
|
||||
conditionEntry.type === 'wildcard'
|
||||
) {
|
||||
return createEntryMatchWildcard(`process.executable.caseless`, conditionEntry.value);
|
||||
} else {
|
||||
return createEntryMatch(`process.executable.caseless`, conditionEntry.value);
|
||||
}
|
||||
|
|
|
@ -65,8 +65,8 @@ const TRUSTED_APP: TrustedApp = {
|
|||
os: OperatingSystem.LINUX,
|
||||
effectScope: { type: 'global' },
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'),
|
||||
createConditionEntry(ConditionEntryField.PATH, '/bin/malware'),
|
||||
createConditionEntry(ConditionEntryField.HASH, 'match', '1234234659af249ddf3e40864e9fb241'),
|
||||
createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'),
|
||||
],
|
||||
};
|
||||
|
||||
|
@ -109,8 +109,35 @@ describe('service', () => {
|
|||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.LINUX,
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.PATH, '/bin/malware'),
|
||||
createConditionEntry(ConditionEntryField.HASH, '1234234659af249ddf3e40864e9fb241'),
|
||||
createConditionEntry(ConditionEntryField.PATH, 'match', '/bin/malware'),
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'match',
|
||||
'1234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
expect(result).toEqual({ data: TRUSTED_APP });
|
||||
|
||||
expect(exceptionsListClient.createTrustedAppsList).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should create trusted app with correct wildcard type', async () => {
|
||||
exceptionsListClient.createExceptionListItem.mockResolvedValue(EXCEPTION_LIST_ITEM);
|
||||
|
||||
const result = await createTrustedApp(exceptionsListClient, {
|
||||
name: 'linux trusted app 1',
|
||||
description: 'Linux trusted app 1',
|
||||
effectScope: { type: 'global' },
|
||||
os: OperatingSystem.LINUX,
|
||||
entries: [
|
||||
createConditionEntry(ConditionEntryField.PATH, 'wildcard', '/bin/malware'),
|
||||
createConditionEntry(
|
||||
ConditionEntryField.HASH,
|
||||
'wildcard',
|
||||
'1234234659af249ddf3e40864e9fb241'
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
@ -30,6 +30,24 @@ export const translatedEntryMatchMatcher = t.keyof({
|
|||
});
|
||||
export type TranslatedEntryMatchMatcher = t.TypeOf<typeof translatedEntryMatchMatcher>;
|
||||
|
||||
export const translatedEntryMatchWildcardMatcher = t.keyof({
|
||||
wildcard_cased: null,
|
||||
wildcard_caseless: null,
|
||||
});
|
||||
export type TranslatedEntryMatchWildcardMatcher = t.TypeOf<
|
||||
typeof translatedEntryMatchWildcardMatcher
|
||||
>;
|
||||
|
||||
export const translatedEntryMatchWildcard = t.exact(
|
||||
t.type({
|
||||
field: t.string,
|
||||
operator,
|
||||
type: translatedEntryMatchWildcardMatcher,
|
||||
value: t.string,
|
||||
})
|
||||
);
|
||||
export type TranslatedEntryMatchWildcard = t.TypeOf<typeof translatedEntryMatchWildcard>;
|
||||
|
||||
export const translatedEntryMatch = t.exact(
|
||||
t.type({
|
||||
field: t.string,
|
||||
|
@ -61,6 +79,7 @@ export type TranslatedEntryNested = t.TypeOf<typeof translatedEntryNested>;
|
|||
export const translatedEntry = t.union([
|
||||
translatedEntryNested,
|
||||
translatedEntryMatch,
|
||||
translatedEntryMatchWildcard,
|
||||
translatedEntryMatchAny,
|
||||
]);
|
||||
export type TranslatedEntry = t.TypeOf<typeof translatedEntry>;
|
||||
|
|
|
@ -20118,7 +20118,6 @@
|
|||
"xpack.securitySolution.topN.closeButtonLabel": "閉じる",
|
||||
"xpack.securitySolution.topN.rawEventsSelectLabel": "未加工イベント",
|
||||
"xpack.securitySolution.trustedapps.aboutInfo": "パフォーマンスを改善したり、ホストで実行されている他のアプリケーションとの競合を解消したりするには、信頼できるアプリケーションを追加します。信頼できるアプリケーションは、Endpoint Securityを実行しているホストに適用されます。",
|
||||
"xpack.securitySolution.trustedapps.card.operator.includes": "is",
|
||||
"xpack.securitySolution.trustedapps.card.removeButtonLabel": "削除",
|
||||
"xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] フィールドエントリには値が必要です",
|
||||
"xpack.securitySolution.trustedapps.create.conditionRequiredMsg": "1つ以上のフィールド定義が必要です",
|
||||
|
|
|
@ -20439,7 +20439,6 @@
|
|||
"xpack.securitySolution.topN.closeButtonLabel": "关闭",
|
||||
"xpack.securitySolution.topN.rawEventsSelectLabel": "原始事件",
|
||||
"xpack.securitySolution.trustedapps.aboutInfo": "添加受信任的应用程序,以提高性能或缓解与主机上运行的其他应用程序的冲突。受信任的应用程序将应用于运行 Endpoint Security 的主机。",
|
||||
"xpack.securitySolution.trustedapps.card.operator.includes": "是",
|
||||
"xpack.securitySolution.trustedapps.card.removeButtonLabel": "移除",
|
||||
"xpack.securitySolution.trustedapps.create.conditionFieldValueRequiredMsg": "[{row}] 字段条目必须包含值",
|
||||
"xpack.securitySolution.trustedapps.create.conditionRequiredMsg": "至少需要一个字段定义",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue