diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx
index 793f0e6c2a420b..e0fb4e554ee3c4 100644
--- a/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx
+++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_disable_users.tsx
@@ -80,7 +80,6 @@ export const ConfirmDisableUsers: FunctionComponent =
}
confirmButtonColor={isSystemUser ? 'danger' : undefined}
isLoading={state.loading}
- ownFocus
>
{isSystemUser ? (
diff --git a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx
index a1aac5bc0a8cb4..2cb4cf8b4a9e2c 100644
--- a/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx
+++ b/x-pack/plugins/security/public/management/users/edit_user/confirm_enable_users.tsx
@@ -67,7 +67,6 @@ export const ConfirmEnableUsers: FunctionComponent = ({
}
)}
isLoading={state.loading}
- ownFocus
>
diff --git a/x-pack/plugins/security/server/audit/audit_service.test.ts b/x-pack/plugins/security/server/audit/audit_service.test.ts
index 3b07d766d7cb46..f59fd6ecdec919 100644
--- a/x-pack/plugins/security/server/audit/audit_service.test.ts
+++ b/x-pack/plugins/security/server/audit/audit_service.test.ts
@@ -76,9 +76,9 @@ describe('#setup', () => {
config: {
enabled: true,
appender: {
- kind: 'console',
+ type: 'console',
layout: {
- kind: 'pattern',
+ type: 'pattern',
},
},
},
@@ -102,9 +102,9 @@ describe('#setup', () => {
config: {
enabled: true,
appender: {
- kind: 'console',
+ type: 'console',
layout: {
- kind: 'pattern',
+ type: 'pattern',
},
},
},
@@ -251,9 +251,9 @@ describe('#createLoggingConfig', () => {
createLoggingConfig({
enabled: true,
appender: {
- kind: 'console',
+ type: 'console',
layout: {
- kind: 'pattern',
+ type: 'pattern',
},
},
})
@@ -264,10 +264,10 @@ describe('#createLoggingConfig', () => {
Object {
"appenders": Object {
"auditTrailAppender": Object {
- "kind": "console",
"layout": Object {
- "kind": "pattern",
+ "type": "pattern",
},
+ "type": "console",
},
},
"loggers": Array [
@@ -275,8 +275,8 @@ describe('#createLoggingConfig', () => {
"appenders": Array [
"auditTrailAppender",
],
- "context": "audit.ecs",
"level": "info",
+ "name": "audit.ecs",
},
],
}
@@ -293,9 +293,9 @@ describe('#createLoggingConfig', () => {
createLoggingConfig({
enabled: false,
appender: {
- kind: 'console',
+ type: 'console',
layout: {
- kind: 'pattern',
+ type: 'pattern',
},
},
})
@@ -331,9 +331,9 @@ describe('#createLoggingConfig', () => {
createLoggingConfig({
enabled: true,
appender: {
- kind: 'console',
+ type: 'console',
layout: {
- kind: 'pattern',
+ type: 'pattern',
},
},
})
diff --git a/x-pack/plugins/security/server/audit/audit_service.ts b/x-pack/plugins/security/server/audit/audit_service.ts
index 42e36e50d6d42d..99dd2c82ec9fe5 100644
--- a/x-pack/plugins/security/server/audit/audit_service.ts
+++ b/x-pack/plugins/security/server/audit/audit_service.ts
@@ -224,16 +224,16 @@ export const createLoggingConfig = (config: ConfigType['audit']) =>
map, LoggerContextConfigInput>((features) => ({
appenders: {
auditTrailAppender: config.appender ?? {
- kind: 'console',
+ type: 'console',
layout: {
- kind: 'pattern',
+ type: 'pattern',
highlight: true,
},
},
},
loggers: [
{
- context: 'audit.ecs',
+ name: 'audit.ecs',
level: config.enabled && config.appender && features.allowAuditLogging ? 'info' : 'off',
appenders: ['auditTrailAppender'],
},
diff --git a/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap b/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap
index 76d284a21984e9..04190fbf5eacdd 100644
--- a/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap
+++ b/x-pack/plugins/security/server/authorization/__snapshots__/validate_es_response.test.ts.snap
@@ -1,12 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`validateEsPrivilegeResponse fails validation when an action is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action3]: expected value of type [boolean] but got [string]"`;
+exports[`validateEsPrivilegeResponse fails validation when an action is malformed in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: expected value of type [boolean] but got [string]"`;
-exports[`validateEsPrivilegeResponse fails validation when an action is missing in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action2]: expected value of type [boolean] but got [undefined]"`;
+exports[`validateEsPrivilegeResponse fails validation when an action is missing in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected actions"`;
exports[`validateEsPrivilegeResponse fails validation when an expected resource property is missing from the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected resources"`;
-exports[`validateEsPrivilegeResponse fails validation when an extra action is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: [action4]: definition for this key is missing"`;
+exports[`validateEsPrivilegeResponse fails validation when an extra action is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.foo-application]: Payload did not match expected actions"`;
exports[`validateEsPrivilegeResponse fails validation when an extra application is present in the response 1`] = `"Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.otherApplication]: definition for this key is missing"`;
diff --git a/x-pack/plugins/security/server/authorization/check_privileges.test.ts b/x-pack/plugins/security/server/authorization/check_privileges.test.ts
index 93f5efed58fb8d..5bca46f22a5123 100644
--- a/x-pack/plugins/security/server/authorization/check_privileges.test.ts
+++ b/x-pack/plugins/security/server/authorization/check_privileges.test.ts
@@ -316,7 +316,7 @@ describe('#atSpace', () => {
},
});
expect(result).toMatchInlineSnapshot(
- `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:bar-type/get]: definition for this key is missing]`
+ `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]`
);
});
@@ -338,7 +338,7 @@ describe('#atSpace', () => {
},
});
expect(result).toMatchInlineSnapshot(
- `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:foo-type/get]: expected value of type [boolean] but got [undefined]]`
+ `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]`
);
});
});
@@ -1092,7 +1092,7 @@ describe('#atSpaces', () => {
},
});
expect(result).toMatchInlineSnapshot(
- `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [mock-action:version]: expected value of type [boolean] but got [undefined]]`
+ `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]`
);
});
@@ -2266,7 +2266,7 @@ describe('#globally', () => {
},
});
expect(result).toMatchInlineSnapshot(
- `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [mock-action:version]: expected value of type [boolean] but got [undefined]]`
+ `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]`
);
});
@@ -2384,7 +2384,7 @@ describe('#globally', () => {
},
});
expect(result).toMatchInlineSnapshot(
- `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:bar-type/get]: definition for this key is missing]`
+ `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]`
);
});
@@ -2405,7 +2405,7 @@ describe('#globally', () => {
},
});
expect(result).toMatchInlineSnapshot(
- `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: [saved_object:foo-type/get]: expected value of type [boolean] but got [undefined]]`
+ `[Error: Invalid response received from Elasticsearch has_privilege endpoint. Error: [application.kibana-our_application]: Payload did not match expected actions]`
);
});
});
diff --git a/x-pack/plugins/security/server/authorization/validate_es_response.ts b/x-pack/plugins/security/server/authorization/validate_es_response.ts
index 19afaaf035c15e..270ff26716e3f2 100644
--- a/x-pack/plugins/security/server/authorization/validate_es_response.ts
+++ b/x-pack/plugins/security/server/authorization/validate_es_response.ts
@@ -8,6 +8,11 @@
import { schema } from '@kbn/config-schema';
import { HasPrivilegesResponse } from './types';
+/**
+ * Validates an Elasticsearch "Has privileges" response against the expected application, actions, and resources.
+ *
+ * Note: the `actions` and `resources` parameters must be unique string arrays; any duplicates will cause validation to fail.
+ */
export function validateEsPrivilegeResponse(
response: HasPrivilegesResponse,
application: string,
@@ -24,21 +29,29 @@ export function validateEsPrivilegeResponse(
return response;
}
-function buildActionsValidationSchema(actions: string[]) {
- return schema.object({
- ...actions.reduce>((acc, action) => {
- return {
- ...acc,
- [action]: schema.boolean(),
- };
- }, {}),
- });
-}
-
function buildValidationSchema(application: string, actions: string[], resources: string[]) {
- const actionValidationSchema = buildActionsValidationSchema(actions);
+ const actionValidationSchema = schema.boolean();
+ const actionsValidationSchema = schema.object(
+ {},
+ {
+ unknowns: 'allow',
+ validate: (value) => {
+ const actualActions = Object.keys(value).sort();
+ if (
+ actions.length !== actualActions.length ||
+ ![...actions].sort().every((x, i) => x === actualActions[i])
+ ) {
+ throw new Error('Payload did not match expected actions');
+ }
+
+ Object.values(value).forEach((actionResult) => {
+ actionValidationSchema.validate(actionResult);
+ });
+ },
+ }
+ );
- const resourceValidationSchema = schema.object(
+ const resourcesValidationSchema = schema.object(
{},
{
unknowns: 'allow',
@@ -46,13 +59,13 @@ function buildValidationSchema(application: string, actions: string[], resources
const actualResources = Object.keys(value).sort();
if (
resources.length !== actualResources.length ||
- !resources.sort().every((x, i) => x === actualResources[i])
+ ![...resources].sort().every((x, i) => x === actualResources[i])
) {
throw new Error('Payload did not match expected resources');
}
Object.values(value).forEach((actionResult) => {
- actionValidationSchema.validate(actionResult);
+ actionsValidationSchema.validate(actionResult);
});
},
}
@@ -63,7 +76,7 @@ function buildValidationSchema(application: string, actions: string[], resources
has_all_requested: schema.boolean(),
cluster: schema.object({}, { unknowns: 'allow' }),
application: schema.object({
- [application]: resourceValidationSchema,
+ [application]: resourcesValidationSchema,
}),
index: schema.object({}, { unknowns: 'allow' }),
});
diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts
index d4dcca8bebb0c1..53e4152b3c8fbf 100644
--- a/x-pack/plugins/security/server/config.test.ts
+++ b/x-pack/plugins/security/server/config.test.ts
@@ -1558,21 +1558,21 @@ describe('createConfig()', () => {
ConfigSchema.validate({
audit: {
appender: {
- kind: 'file',
- path: '/path/to/file.txt',
+ type: 'file',
+ fileName: '/path/to/file.txt',
layout: {
- kind: 'json',
+ type: 'json',
},
},
},
}).audit.appender
).toMatchInlineSnapshot(`
Object {
- "kind": "file",
+ "fileName": "/path/to/file.txt",
"layout": Object {
- "kind": "json",
+ "type": "json",
},
- "path": "/path/to/file.txt",
+ "type": "file",
}
`);
});
@@ -1583,12 +1583,12 @@ describe('createConfig()', () => {
audit: {
// no layout configured
appender: {
- kind: 'file',
+ type: 'file',
path: '/path/to/file.txt',
},
},
})
- ).toThrow('[audit.appender.2.kind]: expected value to equal [legacy-appender]');
+ ).toThrow('[audit.appender.2.type]: expected value to equal [legacy-appender]');
});
it('rejects an ignore_filter when no appender is configured', () => {
diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts
index bdb02d8ed99750..c4c7f399e7b5d3 100644
--- a/x-pack/plugins/security/server/config_deprecations.test.ts
+++ b/x-pack/plugins/security/server/config_deprecations.test.ts
@@ -52,6 +52,117 @@ describe('Config Deprecations', () => {
`);
});
+ it('renames audit.appender.kind to audit.appender.type', () => {
+ const config = {
+ xpack: {
+ security: {
+ audit: {
+ appender: {
+ kind: 'console',
+ },
+ },
+ },
+ },
+ };
+ const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
+ expect(migrated.xpack.security.audit.appender.kind).not.toBeDefined();
+ expect(migrated.xpack.security.audit.appender.type).toEqual('console');
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "\\"xpack.security.audit.appender.kind\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.type\\"",
+ ]
+ `);
+ });
+
+ it('renames audit.appender.layout.kind to audit.appender.layout.type', () => {
+ const config = {
+ xpack: {
+ security: {
+ audit: {
+ appender: {
+ layout: { kind: 'pattern' },
+ },
+ },
+ },
+ },
+ };
+ const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
+ expect(migrated.xpack.security.audit.appender.layout.kind).not.toBeDefined();
+ expect(migrated.xpack.security.audit.appender.layout.type).toEqual('pattern');
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "\\"xpack.security.audit.appender.layout.kind\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.layout.type\\"",
+ ]
+ `);
+ });
+
+ it('renames audit.appender.policy.kind to audit.appender.policy.type', () => {
+ const config = {
+ xpack: {
+ security: {
+ audit: {
+ appender: {
+ policy: { kind: 'time-interval' },
+ },
+ },
+ },
+ },
+ };
+ const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
+ expect(migrated.xpack.security.audit.appender.policy.kind).not.toBeDefined();
+ expect(migrated.xpack.security.audit.appender.policy.type).toEqual('time-interval');
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "\\"xpack.security.audit.appender.policy.kind\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.policy.type\\"",
+ ]
+ `);
+ });
+
+ it('renames audit.appender.strategy.kind to audit.appender.strategy.type', () => {
+ const config = {
+ xpack: {
+ security: {
+ audit: {
+ appender: {
+ strategy: { kind: 'numeric' },
+ },
+ },
+ },
+ },
+ };
+ const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
+ expect(migrated.xpack.security.audit.appender.strategy.kind).not.toBeDefined();
+ expect(migrated.xpack.security.audit.appender.strategy.type).toEqual('numeric');
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "\\"xpack.security.audit.appender.strategy.kind\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.strategy.type\\"",
+ ]
+ `);
+ });
+
+ it('renames audit.appender.path to audit.appender.fileName', () => {
+ const config = {
+ xpack: {
+ security: {
+ audit: {
+ appender: {
+ type: 'file',
+ path: './audit.log',
+ },
+ },
+ },
+ },
+ };
+ const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
+ expect(migrated.xpack.security.audit.appender.path).not.toBeDefined();
+ expect(migrated.xpack.security.audit.appender.fileName).toEqual('./audit.log');
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "\\"xpack.security.audit.appender.path\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.fileName\\"",
+ ]
+ `);
+ });
+
it(`warns that 'authorization.legacyFallback.enabled' is unused`, () => {
const config = {
xpack: {
diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts
index 65d18f0a4e7eb3..a7bb5e09fb919d 100644
--- a/x-pack/plugins/security/server/config_deprecations.ts
+++ b/x-pack/plugins/security/server/config_deprecations.ts
@@ -12,6 +12,13 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
unused,
}) => [
rename('sessionTimeout', 'session.idleTimeout'),
+
+ rename('audit.appender.kind', 'audit.appender.type'),
+ rename('audit.appender.layout.kind', 'audit.appender.layout.type'),
+ rename('audit.appender.policy.kind', 'audit.appender.policy.type'),
+ rename('audit.appender.strategy.kind', 'audit.appender.strategy.type'),
+ rename('audit.appender.path', 'audit.appender.fileName'),
+
unused('authorization.legacyFallback.enabled'),
unused('authc.saml.maxRedirectURLSize'),
// Deprecation warning for the old array-based format of `xpack.security.authc.providers`.
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
index 5d1f7572672990..aade8be4f503fb 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts
@@ -325,7 +325,7 @@ export const job_status = t.keyof({
succeeded: null,
failed: null,
'going to run': null,
- 'partial failure': null,
+ warning: null,
});
export type JobStatus = t.TypeOf;
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts
index b76a762ca6cbf0..981a5422a05949 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/add_prepackaged_rules_schema.ts
@@ -55,6 +55,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
} from '../types/threat_mapping';
import {
@@ -133,6 +134,7 @@ export const addPrepackagedRulesSchema = t.intersection([
threat_query, // defaults to "undefined" if not set during decode
threat_index, // defaults to "undefined" if not set during decode
threat_language, // defaults "undefined" if not set during decode
+ threat_indicator_path, // defaults "undefined" if not set during decode
concurrent_searches, // defaults to "undefined" if not set during decode
items_per_search, // defaults to "undefined" if not set during decode
})
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts
index 0a7b8b120ba7ef..8fa5809abe68b4 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/import_rules_schema.ts
@@ -62,6 +62,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
} from '../types/threat_mapping';
import {
@@ -152,6 +153,7 @@ export const importRulesSchema = t.intersection([
threat_query, // defaults to "undefined" if not set during decode
threat_index, // defaults to "undefined" if not set during decode
threat_language, // defaults "undefined" if not set during decode
+ threat_indicator_path, // defaults to "undefined" if not set during decode
concurrent_searches, // defaults to "undefined" if not set during decode
items_per_search, // defaults to "undefined" if not set during decode
})
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts
index 9d5331aeab8e4e..920fbaf4915c5c 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/patch_rules_schema.ts
@@ -57,6 +57,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
} from '../types/threat_mapping';
import { listArrayOrUndefined } from '../types/lists';
@@ -112,6 +113,7 @@ export const patchRulesSchema = t.exact(
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
concurrent_searches,
items_per_search,
})
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts
index 87e5acb5428df7..fb29e37a53fdbe 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.mock.ts
@@ -56,6 +56,7 @@ export const getCreateThreatMatchRulesSchemaMock = (
rule_id: ruleId,
threat_query: '*:*',
threat_index: ['list-index'],
+ threat_indicator_path: 'threat.indicator',
threat_mapping: [
{
entries: [
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts
index 14b47c8b2b3280..6b8211b23088ca 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.test.ts
@@ -1152,7 +1152,7 @@ describe('create rules schema', () => {
});
});
- describe('threat_mapping', () => {
+ describe('threat_match', () => {
test('You can set a threat query, index, mapping, filters when creating a rule', () => {
const payload = getCreateThreatMatchRulesSchemaMock();
const decoded = createRulesSchema.decode(payload);
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts
index 1c9ebe00333157..5cf2b6242b2f89 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/request/rule_schemas.ts
@@ -13,6 +13,7 @@ import {
threat_query,
threat_mapping,
threat_index,
+ threat_indicator_path,
concurrent_searches,
items_per_search,
} from '../types/threat_mapping';
@@ -213,6 +214,7 @@ const threatMatchRuleParams = {
filters,
saved_id,
threat_filters,
+ threat_indicator_path,
threat_language: t.keyof({ kuery: null, lucene: null }),
concurrent_searches,
items_per_search,
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts
index b14c646e862d36..cf07389e207b34 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.mocks.ts
@@ -150,6 +150,7 @@ export const getThreatMatchingSchemaPartialMock = (enabled = false): Partial {
expect(fields).toEqual(expected);
});
- test('should return 8 fields for a rule of type "threat_match"', () => {
+ test('should return nine (9) fields for a rule of type "threat_match"', () => {
const fields = addThreatMatchFields({ type: 'threat_match' });
- expect(fields.length).toEqual(8);
+ expect(fields.length).toEqual(9);
});
});
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
index bcdb0fa9b085d6..6bd54973e064f1 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/response/rules_schema.ts
@@ -70,6 +70,7 @@ import {
threat_filters,
threat_mapping,
threat_language,
+ threat_indicator_path,
} from '../types/threat_mapping';
import { DefaultListArray } from '../types/lists_default_array';
@@ -151,6 +152,7 @@ export const dependentRulesSchema = t.partial({
items_per_search,
threat_mapping,
threat_language,
+ threat_indicator_path,
});
/**
@@ -286,6 +288,9 @@ export const addThreatMatchFields = (typeAndTimelineOnly: TypeAndTimelineOnly):
t.exact(t.type({ threat_mapping: dependentRulesSchema.props.threat_mapping })),
t.exact(t.partial({ threat_language: dependentRulesSchema.props.threat_language })),
t.exact(t.partial({ threat_filters: dependentRulesSchema.props.threat_filters })),
+ t.exact(
+ t.partial({ threat_indicator_path: dependentRulesSchema.props.threat_indicator_path })
+ ),
t.exact(t.partial({ saved_id: dependentRulesSchema.props.saved_id })),
t.exact(t.partial({ concurrent_searches: dependentRulesSchema.props.concurrent_searches })),
t.exact(
diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts
index d3975df488de9e..aab06941686c26 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/types/threat_mapping.ts
@@ -18,6 +18,11 @@ export type ThreatQuery = t.TypeOf;
export const threatQueryOrUndefined = t.union([threat_query, t.undefined]);
export type ThreatQueryOrUndefined = t.TypeOf;
+export const threat_indicator_path = t.string;
+export type ThreatIndicatorPath = t.TypeOf;
+export const threatIndicatorPathOrUndefined = t.union([threat_indicator_path, t.undefined]);
+export type ThreatIndicatorPathOrUndefined = t.TypeOf;
+
export const threat_filters = t.array(t.unknown); // Filters are not easily type-able yet
export type ThreatFilters = t.TypeOf;
export const threatFiltersOrUndefined = t.union([threat_filters, t.undefined]);
diff --git a/x-pack/plugins/security_solution/common/detection_engine/utils.ts b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
index 080b704e9c193b..725a2eb9fea7bb 100644
--- a/x-pack/plugins/security_solution/common/detection_engine/utils.ts
+++ b/x-pack/plugins/security_solution/common/detection_engine/utils.ts
@@ -30,4 +30,5 @@ export const isEqlRule = (ruleType: Type | undefined): boolean => ruleType === '
export const isThresholdRule = (ruleType: Type | undefined): boolean => ruleType === 'threshold';
export const isQueryRule = (ruleType: Type | undefined): boolean =>
ruleType === 'query' || ruleType === 'saved_query';
-export const isThreatMatchRule = (ruleType: Type): boolean => ruleType === 'threat_match';
+export const isThreatMatchRule = (ruleType: Type | undefined): boolean =>
+ ruleType === 'threat_match';
diff --git a/x-pack/plugins/security_solution/common/ecs/file/index.ts b/x-pack/plugins/security_solution/common/ecs/file/index.ts
index 06abc7fd87541c..5e409b1095cf59 100644
--- a/x-pack/plugins/security_solution/common/ecs/file/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/file/index.ts
@@ -5,14 +5,22 @@
* 2.0.
*/
+interface Original {
+ name?: string[];
+ path?: string[];
+}
+
export interface CodeSignature {
subject_name: string[];
trusted: string[];
}
export interface Ext {
- code_signature: CodeSignature[] | CodeSignature;
+ code_signature?: CodeSignature[] | CodeSignature;
+ original?: Original;
}
export interface Hash {
+ md5?: string[];
+ sha1?: string[];
sha256: string[];
}
diff --git a/x-pack/plugins/security_solution/common/ecs/index.ts b/x-pack/plugins/security_solution/common/ecs/index.ts
index e3bcd11097cf79..ec23b677168cda 100644
--- a/x-pack/plugins/security_solution/common/ecs/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/index.ts
@@ -15,6 +15,7 @@ import { FileEcs } from './file';
import { GeoEcs } from './geo';
import { HostEcs } from './host';
import { NetworkEcs } from './network';
+import { RegistryEcs } from './registry';
import { RuleEcs } from './rule';
import { SignalEcs } from './signal';
import { SourceEcs } from './source';
@@ -40,6 +41,7 @@ export interface Ecs {
geo?: GeoEcs;
host?: HostEcs;
network?: NetworkEcs;
+ registry?: RegistryEcs;
rule?: RuleEcs;
signal?: SignalEcs;
source?: SourceEcs;
@@ -55,4 +57,6 @@ export interface Ecs {
process?: ProcessEcs;
file?: FileEcs;
system?: SystemEcs;
+ // This should be temporary
+ eql?: { parentId: string; sequenceNumber: string };
}
diff --git a/x-pack/plugins/security_solution/common/ecs/process/index.ts b/x-pack/plugins/security_solution/common/ecs/process/index.ts
index 3a8ccc309aecb9..931adf2dd70b8b 100644
--- a/x-pack/plugins/security_solution/common/ecs/process/index.ts
+++ b/x-pack/plugins/security_solution/common/ecs/process/index.ts
@@ -28,6 +28,7 @@ export interface ProcessHashData {
export interface ProcessParentData {
name?: string[];
+ pid?: number[];
}
export interface Thread {
diff --git a/x-pack/plugins/security_solution/common/ecs/registry/index.ts b/x-pack/plugins/security_solution/common/ecs/registry/index.ts
new file mode 100644
index 00000000000000..c756fb139199e7
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/ecs/registry/index.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export interface RegistryEcs {
+ hive?: string[];
+ key?: string[];
+ path?: string[];
+ value?: string[];
+}
diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
index ffeaf853828f13..8aec9768dd50d2 100644
--- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts
@@ -101,6 +101,7 @@ const POLICY_RESPONSE_STATUSES: HostPolicyResponseActionStatus[] = [
HostPolicyResponseActionStatus.success,
HostPolicyResponseActionStatus.failure,
HostPolicyResponseActionStatus.warning,
+ HostPolicyResponseActionStatus.unsupported,
];
const APPLIED_POLICIES: Array<{
@@ -1492,7 +1493,7 @@ export class EndpointDocGenerator {
{
name: 'workflow',
message: 'Failed to apply a portion of the configuration (kernel)',
- status: HostPolicyResponseActionStatus.success,
+ status: HostPolicyResponseActionStatus.unsupported,
},
{
name: 'download_model',
@@ -1637,6 +1638,7 @@ export class EndpointDocGenerator {
HostPolicyResponseActionStatus.failure,
HostPolicyResponseActionStatus.success,
HostPolicyResponseActionStatus.warning,
+ HostPolicyResponseActionStatus.unsupported,
]);
}
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts
index d361c0d6282a34..94a09b385a08c8 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts
@@ -933,6 +933,7 @@ export enum HostPolicyResponseActionStatus {
success = 'success',
failure = 'failure',
warning = 'warning',
+ unsupported = 'unsupported',
}
/**
diff --git a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts
index 40e353263bcc8c..7e19944ea5856c 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/security_solution/hosts/common/index.ts
@@ -13,6 +13,7 @@ export enum HostPolicyResponseActionStatus {
success = 'success',
failure = 'failure',
warning = 'warning',
+ unsupported = 'unsupported',
}
export enum HostsFields {
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts
index 6f5bea87f55088..ada22437a1530d 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/all/index.ts
@@ -37,4 +37,5 @@ export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse {
export interface TimelineEventsAllRequestOptions extends TimelineRequestOptionsPaginated {
fields: string[];
fieldRequested: string[];
+ language: 'eql' | 'kuery' | 'lucene';
}
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts
new file mode 100644
index 00000000000000..6bf01e478a9727
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/eql/index.ts
@@ -0,0 +1,47 @@
+/*
+ * 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 { EuiComboBoxOptionOption } from '@elastic/eui';
+import {
+ EqlSearchStrategyRequest,
+ EqlSearchStrategyResponse,
+} from '../../../../../../data_enhanced/common';
+import { Inspect, Maybe, PaginationInputPaginated } from '../../..';
+import { TimelineEdges, TimelineEventsAllRequestOptions } from '../..';
+import { EqlSearchResponse } from '../../../../detection_engine/types';
+
+export interface TimelineEqlRequestOptions
+ extends EqlSearchStrategyRequest,
+ Omit {
+ eventCategoryField?: string;
+ tiebreakerField?: string;
+ timestampField?: string;
+ size?: number;
+}
+
+export interface TimelineEqlResponse extends EqlSearchStrategyResponse> {
+ edges: TimelineEdges[];
+ totalCount: number;
+ pageInfo: Pick;
+ inspect: Maybe;
+}
+
+export interface EqlOptionsData {
+ keywordFields: EuiComboBoxOptionOption[];
+ dateFields: EuiComboBoxOptionOption[];
+ nonDateFields: EuiComboBoxOptionOption[];
+}
+
+export interface EqlOptionsSelected {
+ eventCategoryField?: string;
+ tiebreakerField?: string;
+ timestampField?: string;
+ query?: string;
+ size?: number;
+}
+
+export type FieldsEqlOptions = keyof EqlOptionsSelected;
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts
index 7ffbde2ebec028..c4d6f70a275871 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts
@@ -8,6 +8,7 @@
export * from './all';
export * from './details';
export * from './last_event_time';
+export * from './eql';
export enum TimelineEventsQueries {
all = 'eventsAll',
diff --git a/x-pack/plugins/security_solution/common/shared_imports.ts b/x-pack/plugins/security_solution/common/shared_imports.ts
index d6ec668e1b0f9f..988f0ad0c125d4 100644
--- a/x-pack/plugins/security_solution/common/shared_imports.ts
+++ b/x-pack/plugins/security_solution/common/shared_imports.ts
@@ -43,6 +43,7 @@ export {
ExceptionListType,
Type,
ENDPOINT_LIST_ID,
+ ENDPOINT_TRUSTED_APPS_LIST_ID,
osTypeArray,
OsTypeArray,
} from '../../lists/common';
diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts
index cee8ccdea3e9e1..5fb7d1a74fc367 100644
--- a/x-pack/plugins/security_solution/common/types/timeline/index.ts
+++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts
@@ -103,6 +103,17 @@ const SavedFilterRuntimeType = runtimeTypes.partial({
script: unionWithNullType(runtimeTypes.string),
});
+/*
+ * eqlOptionsQuery -> filterQuery Types
+ */
+const EqlOptionsRuntimeType = runtimeTypes.partial({
+ eventCategoryField: unionWithNullType(runtimeTypes.string),
+ query: unionWithNullType(runtimeTypes.string),
+ tiebreakerField: unionWithNullType(runtimeTypes.string),
+ timestampField: unionWithNullType(runtimeTypes.string),
+ size: unionWithNullType(runtimeTypes.union([runtimeTypes.string, runtimeTypes.number])),
+});
+
/*
* kqlQuery -> filterQuery Types
*/
@@ -180,10 +191,13 @@ export type TimelineStatusLiteralWithNull = runtimeTypes.TypeOf<
>;
export enum RowRendererId {
+ alerts = 'alerts',
auditd = 'auditd',
auditd_file = 'auditd_file',
+ library = 'library',
netflow = 'netflow',
plain = 'plain',
+ registry = 'registry',
suricata = 'suricata',
system = 'system',
system_dns = 'system_dns',
@@ -243,6 +257,7 @@ export const SavedTimelineRuntimeType = runtimeTypes.partial({
columns: unionWithNullType(runtimeTypes.array(SavedColumnHeaderRuntimeType)),
dataProviders: unionWithNullType(runtimeTypes.array(SavedDataProviderRuntimeType)),
description: unionWithNullType(runtimeTypes.string),
+ eqlOptions: unionWithNullType(EqlOptionsRuntimeType),
eventType: unionWithNullType(runtimeTypes.string),
excludedRowRendererIds: unionWithNullType(runtimeTypes.array(RowRendererIdRuntimeType)),
favorite: unionWithNullType(runtimeTypes.array(SavedFavoriteRuntimeType)),
@@ -281,7 +296,7 @@ export enum TimelineId {
active = 'timeline-1',
casePage = 'timeline-case',
test = 'test', // Reserved for testing purposes
- test2 = 'test2',
+ alternateTest = 'alternateTest',
}
export const TimelineIdLiteralRt = runtimeTypes.union([
@@ -410,13 +425,14 @@ export const importTimelineResultSchema = runtimeTypes.exact(
export type ImportTimelineResultSchema = runtimeTypes.TypeOf;
-export type TimelineEventsType = 'all' | 'raw' | 'alert' | 'signal' | 'custom';
+export type TimelineEventsType = 'all' | 'raw' | 'alert' | 'signal' | 'custom' | 'eql';
export enum TimelineTabs {
query = 'query',
graph = 'graph',
notes = 'notes',
pinned = 'pinned',
+ eql = 'eql',
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/x-pack/plugins/security_solution/common/utility_types.ts b/x-pack/plugins/security_solution/common/utility_types.ts
index 3c13e6af837bc0..498b18dccaca56 100644
--- a/x-pack/plugins/security_solution/common/utility_types.ts
+++ b/x-pack/plugins/security_solution/common/utility_types.ts
@@ -36,6 +36,12 @@ export const stringEnum = (enumObj: T, enumName = 'enum') =>
*
* Optionally you can avoid the use of this by using early returns and TypeScript will clear your type checking without complaints
* but there are situations and times where this function might still be needed.
+ *
+ * If you see an error, DO NOT cast "as never" such as:
+ * assertUnreachable(x as never) // BUG IN YOUR CODE NOW AND IT WILL THROW DURING RUNTIME
+ * If you see code like that remove it, as that deactivates the intent of this utility.
+ * If you need to do that, then you should remove assertUnreachable from your code and
+ * use a default at the end of the switch instead.
* @param x Unreachable field
* @param message Message of error thrown
*/
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
new file mode 100644
index 00000000000000..1c6c604b84fbb2
--- /dev/null
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_index_outdated.spec.ts
@@ -0,0 +1,196 @@
+/*
+ * 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 { ROLES } from '../../../common/test';
+import { DETECTIONS_RULE_MANAGEMENT_URL, DETECTIONS_URL } from '../../urls/navigation';
+import { newRule } from '../../objects/rule';
+import { PAGE_TITLE } from '../../screens/common/page';
+
+import {
+ login,
+ loginAndWaitForPageWithoutDateRange,
+ waitForPageWithoutDateRange,
+} from '../../tasks/login';
+import { waitForAlertsIndexToBeCreated } from '../../tasks/alerts';
+import { goToRuleDetails } from '../../tasks/alerts_detection_rules';
+import { createCustomRule, deleteCustomRule } from '../../tasks/api_calls/rules';
+import { getCallOut, waitForCallOutToBeShown } from '../../tasks/common/callouts';
+import { cleanKibana } from '../../tasks/common';
+
+const loadPageAsPlatformEngineerUser = (url: string) => {
+ waitForPageWithoutDateRange(url, ROLES.soc_manager);
+ waitForPageTitleToBeShown();
+};
+
+const waitForPageTitleToBeShown = () => {
+ cy.get(PAGE_TITLE).should('be.visible');
+};
+
+describe('Detections > Need Admin Callouts indicating an admin is needed to migrate the alert data set', () => {
+ const NEED_ADMIN_FOR_UPDATE_CALLOUT = 'need-admin-for-update-rules';
+
+ before(() => {
+ // First, we have to open the app on behalf of a privileged user in order to initialize it.
+ // Otherwise the app will be disabled and show a "welcome"-like page.
+ cleanKibana();
+ loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer);
+ waitForAlertsIndexToBeCreated();
+
+ // After that we can login as a soc manager.
+ login(ROLES.soc_manager);
+ });
+
+ context(
+ 'The users index_mapping_outdated is "true" and their admin callouts should show up',
+ () => {
+ beforeEach(() => {
+ // Index mapping outdated is forced to return true as being outdated so that we get the
+ // need admin callouts being shown.
+ cy.intercept('GET', '/api/detection_engine/index', {
+ index_mapping_outdated: true,
+ name: '.siem-signals-default',
+ });
+ });
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_URL);
+ });
+
+ it('We show the need admin primary callout', () => {
+ waitForCallOutToBeShown(NEED_ADMIN_FOR_UPDATE_CALLOUT, 'primary');
+ });
+ });
+
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
+
+ it('We show 1 primary callout of need admin', () => {
+ waitForCallOutToBeShown(NEED_ADMIN_FOR_UPDATE_CALLOUT, 'primary');
+ });
+ });
+
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
+
+ afterEach(() => {
+ deleteCustomRule();
+ });
+
+ it('We show 1 primary callout', () => {
+ waitForCallOutToBeShown(NEED_ADMIN_FOR_UPDATE_CALLOUT, 'primary');
+ });
+ });
+ }
+ );
+
+ context(
+ 'The users index_mapping_outdated is "false" and their admin callouts should not show up ',
+ () => {
+ beforeEach(() => {
+ // Index mapping outdated is forced to return true as being outdated so that we get the
+ // need admin callouts being shown.
+ cy.intercept('GET', '/api/detection_engine/index', {
+ index_mapping_outdated: false,
+ name: '.siem-signals-default',
+ });
+ });
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_URL);
+ });
+
+ it('We show the need admin primary callout', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
+
+ it('We show 1 primary callout of need admin', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
+
+ afterEach(() => {
+ deleteCustomRule();
+ });
+
+ it('We show 1 primary callout', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+ }
+ );
+
+ context(
+ 'The users index_mapping_outdated is "null" and their admin callouts should not show up ',
+ () => {
+ beforeEach(() => {
+ // Index mapping outdated is forced to return true as being outdated so that we get the
+ // need admin callouts being shown.
+ cy.intercept('GET', '/api/detection_engine/index', {
+ index_mapping_outdated: null,
+ name: '.siem-signals-default',
+ });
+ });
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_URL);
+ });
+
+ it('We show the need admin primary callout', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
+
+ it('We show 1 primary callout of need admin', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsPlatformEngineerUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
+
+ afterEach(() => {
+ deleteCustomRule();
+ });
+
+ it('We show 1 primary callout', () => {
+ getCallOut(NEED_ADMIN_FOR_UPDATE_CALLOUT).should('not.exist');
+ });
+ });
+ }
+ );
+});
diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_readonly.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_readonly.spec.ts
index 85257f7d9176f5..d807857cd72bde 100644
--- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_readonly.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_detection_callouts_readonly.spec.ts
@@ -26,6 +26,11 @@ const loadPageAsReadOnlyUser = (url: string) => {
waitForPageTitleToBeShown();
};
+const loadPageAsPlatformEngineer = (url: string) => {
+ waitForPageWithoutDateRange(url, ROLES.platform_engineer);
+ waitForPageTitleToBeShown();
+};
+
const reloadPage = () => {
cy.reload();
waitForPageTitleToBeShown();
@@ -35,7 +40,7 @@ const waitForPageTitleToBeShown = () => {
cy.get(PAGE_TITLE).should('be.visible');
};
-describe('Detections > Callouts indicating read-only access to resources', () => {
+describe('Detections > Callouts', () => {
const ALERTS_CALLOUT = 'read-only-access-to-alerts';
const RULES_CALLOUT = 'read-only-access-to-rules';
@@ -50,75 +55,119 @@ describe('Detections > Callouts indicating read-only access to resources', () =>
login(ROLES.reader);
});
- context('On Detections home page', () => {
- beforeEach(() => {
- loadPageAsReadOnlyUser(DETECTIONS_URL);
- });
-
- it('We show one primary callout', () => {
- waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
- });
+ context('indicating read-only access to resources', () => {
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsReadOnlyUser(DETECTIONS_URL);
+ });
- context('When a user clicks Dismiss on the callout', () => {
- it('We hide it and persist the dismissal', () => {
+ it('We show one primary callout', () => {
waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
- dismissCallOut(ALERTS_CALLOUT);
- reloadPage();
- getCallOut(ALERTS_CALLOUT).should('not.exist');
});
- });
- });
- context('On Rules Management page', () => {
- beforeEach(() => {
- loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ context('When a user clicks Dismiss on the callout', () => {
+ it('We hide it and persist the dismissal', () => {
+ waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
+ dismissCallOut(ALERTS_CALLOUT);
+ reloadPage();
+ getCallOut(ALERTS_CALLOUT).should('not.exist');
+ });
+ });
});
- it('We show one primary callout', () => {
- waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
- });
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
- context('When a user clicks Dismiss on the callout', () => {
- it('We hide it and persist the dismissal', () => {
+ it('We show one primary callout', () => {
waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
- dismissCallOut(RULES_CALLOUT);
- reloadPage();
- getCallOut(RULES_CALLOUT).should('not.exist');
});
- });
- });
- context('On Rule Details page', () => {
- beforeEach(() => {
- createCustomRule(newRule);
- loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
- waitForPageTitleToBeShown();
- goToRuleDetails();
+ context('When a user clicks Dismiss on the callout', () => {
+ it('We hide it and persist the dismissal', () => {
+ waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
+ dismissCallOut(RULES_CALLOUT);
+ reloadPage();
+ getCallOut(RULES_CALLOUT).should('not.exist');
+ });
+ });
});
- afterEach(() => {
- deleteCustomRule();
- });
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsReadOnlyUser(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
- it('We show two primary callouts', () => {
- waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
- waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
- });
+ afterEach(() => {
+ deleteCustomRule();
+ });
- context('When a user clicks Dismiss on the callouts', () => {
- it('We hide them and persist the dismissal', () => {
+ it('We show two primary callouts', () => {
waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
+ });
- dismissCallOut(ALERTS_CALLOUT);
- reloadPage();
+ context('When a user clicks Dismiss on the callouts', () => {
+ it('We hide them and persist the dismissal', () => {
+ waitForCallOutToBeShown(ALERTS_CALLOUT, 'primary');
+ waitForCallOutToBeShown(RULES_CALLOUT, 'primary');
+ dismissCallOut(ALERTS_CALLOUT);
+ reloadPage();
+
+ getCallOut(ALERTS_CALLOUT).should('not.exist');
+ getCallOut(RULES_CALLOUT).should('be.visible');
+
+ dismissCallOut(RULES_CALLOUT);
+ reloadPage();
+
+ getCallOut(ALERTS_CALLOUT).should('not.exist');
+ getCallOut(RULES_CALLOUT).should('not.exist');
+ });
+ });
+ });
+ });
+
+ context('indicating read-write access to resources', () => {
+ context('On Detections home page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineer(DETECTIONS_URL);
+ });
+
+ it('We show no callout', () => {
+ getCallOut(ALERTS_CALLOUT).should('not.exist');
+ getCallOut(RULES_CALLOUT).should('not.exist');
+ });
+ });
+
+ context('On Rules Management page', () => {
+ beforeEach(() => {
+ loadPageAsPlatformEngineer(DETECTIONS_RULE_MANAGEMENT_URL);
+ });
+
+ it('We show no callout', () => {
getCallOut(ALERTS_CALLOUT).should('not.exist');
- getCallOut(RULES_CALLOUT).should('be.visible');
+ getCallOut(RULES_CALLOUT).should('not.exist');
+ });
+ });
- dismissCallOut(RULES_CALLOUT);
- reloadPage();
+ context('On Rule Details page', () => {
+ beforeEach(() => {
+ createCustomRule(newRule);
+ loadPageAsPlatformEngineer(DETECTIONS_RULE_MANAGEMENT_URL);
+ waitForPageTitleToBeShown();
+ goToRuleDetails();
+ });
+
+ afterEach(() => {
+ deleteCustomRule();
+ });
+ it('We show no callouts', () => {
getCallOut(ALERTS_CALLOUT).should('not.exist');
getCallOut(RULES_CALLOUT).should('not.exist');
});
diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
index cbcfed8a4cf95a..a2fb94e462023e 100644
--- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts
@@ -129,6 +129,10 @@ export const RISK_MAPPING_OVERRIDE_OPTION = '#risk_score-mapping-override';
export const RISK_OVERRIDE =
'[data-test-subj="detectionEngineStepAboutRuleRiskScore-riskOverride"]';
+export const RULES_CREATION_FORM = '[data-test-subj="stepDefineRule"]';
+
+export const RULES_CREATION_PREVIEW = '[data-test-subj="ruleCreationQueryPreview"]';
+
export const RULE_DESCRIPTION_INPUT =
'[data-test-subj="detectionEngineStepAboutRuleDescription"] [data-test-subj="input"]';
diff --git a/x-pack/plugins/security_solution/cypress/tasks/common/callouts.ts b/x-pack/plugins/security_solution/cypress/tasks/common/callouts.ts
index 4139c911e4063d..8440409f80f38e 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/common/callouts.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/common/callouts.ts
@@ -12,13 +12,11 @@ export const getCallOut = (id: string, options?: Cypress.Timeoutable) => {
};
export const waitForCallOutToBeShown = (id: string, color: string) => {
- getCallOut(id, { timeout: 10000 })
- .should('be.visible')
- .should('have.class', `euiCallOut--${color}`);
+ getCallOut(id).should('be.visible').should('have.class', `euiCallOut--${color}`);
};
export const dismissCallOut = (id: string) => {
- getCallOut(id, { timeout: 10000 }).within(() => {
+ getCallOut(id).within(() => {
cy.get(CALLOUT_DISMISS_BTN).should('be.visible').click();
cy.root().should('not.exist');
});
diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
index 475ce5ecb15727..02ba3937ed542a 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts
@@ -78,6 +78,8 @@ import {
AT_LEAST_ONE_VALID_MATCH,
AT_LEAST_ONE_INDEX_PATTERN,
CUSTOM_QUERY_REQUIRED,
+ RULES_CREATION_FORM,
+ RULES_CREATION_PREVIEW,
} from '../screens/create_new_rule';
import { TOAST_ERROR } from '../screens/shared';
import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline';
@@ -271,23 +273,26 @@ export const fillDefineThresholdRuleAndContinue = (rule: ThresholdRule) => {
};
export const fillDefineEqlRuleAndContinue = (rule: CustomRule) => {
- cy.get(EQL_QUERY_INPUT).should('exist');
- cy.get(EQL_QUERY_INPUT).should('be.visible');
- cy.get(EQL_QUERY_INPUT).type(rule.customQuery!);
- cy.get(EQL_QUERY_VALIDATION_SPINNER).should('not.exist');
- cy.get(QUERY_PREVIEW_BUTTON).should('not.be.disabled').click({ force: true });
+ cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).should('exist');
+ cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).should('be.visible');
+ cy.get(RULES_CREATION_FORM).find(EQL_QUERY_INPUT).type(rule.customQuery!);
+ cy.get(RULES_CREATION_FORM).find(EQL_QUERY_VALIDATION_SPINNER).should('not.exist');
+ cy.get(RULES_CREATION_PREVIEW)
+ .find(QUERY_PREVIEW_BUTTON)
+ .should('not.be.disabled')
+ .click({ force: true });
cy.get(EQL_QUERY_PREVIEW_HISTOGRAM)
.invoke('text')
.then((text) => {
if (text !== 'Hits') {
- cy.get(QUERY_PREVIEW_BUTTON).click({ force: true });
+ cy.get(RULES_CREATION_PREVIEW).find(QUERY_PREVIEW_BUTTON).click({ force: true });
cy.get(EQL_QUERY_PREVIEW_HISTOGRAM).should('contain.text', 'Hits');
}
});
cy.get(TOAST_ERROR).should('not.exist');
cy.get(DEFINE_CONTINUE_BUTTON).should('exist').click({ force: true });
- cy.get(EQL_QUERY_INPUT).should('not.exist');
+ cy.get(`${RULES_CREATION_FORM} ${EQL_QUERY_INPUT}`).should('not.exist');
};
/**
@@ -480,7 +485,7 @@ export const waitForAlertsToPopulate = async () => {
export const waitForTheRuleToBeExecuted = () => {
cy.waitUntil(() => {
- cy.get(REFRESH_BUTTON).click();
+ cy.get(REFRESH_BUTTON).click({ force: true });
return cy
.get(RULE_STATUS)
.invoke('text')
diff --git a/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts b/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts
index 018be0ec72f22f..5fef4f2f5569be 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/date_picker.ts
@@ -33,7 +33,7 @@ export const setStartDate = (date: string) => {
};
export const setTimelineEndDate = (date: string) => {
- cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON_TIMELINE).click({ force: true });
+ cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON_TIMELINE).first().click({ force: true });
cy.get(DATE_PICKER_ABSOLUTE_TAB).first().click({ force: true });
@@ -47,7 +47,7 @@ export const setTimelineEndDate = (date: string) => {
};
export const setTimelineStartDate = (date: string) => {
- cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE).click({
+ cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON_TIMELINE).first().click({
force: true,
});
@@ -68,6 +68,7 @@ export const updateDates = () => {
export const updateTimelineDates = () => {
cy.get(DATE_PICKER_APPLY_BUTTON_TIMELINE)
+ .first()
.click({ force: true })
.should('not.have.text', 'Updating');
};
diff --git a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
index d6f9b002256f3c..ada09d9c05c087 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/timeline.ts
@@ -99,7 +99,7 @@ export const goToQueryTab = () => {
export const addNotesToTimeline = (notes: string) => {
goToNotesTab();
cy.get(NOTES_TEXT_AREA).type(notes);
- cy.get(ADD_NOTE_BUTTON).click();
+ cy.get(ADD_NOTE_BUTTON).click({ force: true });
cy.get(QUERY_TAB_BUTTON).click();
};
diff --git a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx
index 1b67aaeb795dd1..eb75d896ae7788 100644
--- a/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/confirm_delete_case/index.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal } from '@elastic/eui';
import * as i18n from './translations';
interface ConfirmDeleteCaseModalProps {
@@ -28,20 +28,18 @@ const ConfirmDeleteCaseModalComp: React.FC = ({
return null;
}
return (
-
-
- {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION}
-
-
+
+ {isPlural ? i18n.CONFIRM_QUESTION_PLURAL : i18n.CONFIRM_QUESTION}
+
);
};
diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx
index 1dfabda8068f17..eda8ed8cdfbcd5 100644
--- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx
@@ -6,13 +6,7 @@
*/
import React, { memo } from 'react';
-import {
- EuiModal,
- EuiModalBody,
- EuiModalHeader,
- EuiModalHeaderTitle,
- EuiOverlayMask,
-} from '@elastic/eui';
+import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui';
import { useGetUserSavedObjectPermissions } from '../../../common/lib/kibana';
import { Case } from '../../containers/types';
@@ -34,16 +28,14 @@ const AllCasesModalComponent: React.FC = ({
const userCanCrud = userPermissions?.crud ?? false;
return isModalOpen ? (
-
-
-
- {i18n.SELECT_CASE_TITLE}
-
-
-
-
-
-
+
+
+ {i18n.SELECT_CASE_TITLE}
+
+
+
+
+
) : null;
};
diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx
index 3595f2c916af71..8dd5080666cb38 100644
--- a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx
@@ -7,13 +7,7 @@
import React, { memo } from 'react';
import styled from 'styled-components';
-import {
- EuiModal,
- EuiModalBody,
- EuiModalHeader,
- EuiModalHeaderTitle,
- EuiOverlayMask,
-} from '@elastic/eui';
+import { EuiModal, EuiModalBody, EuiModalHeader, EuiModalHeaderTitle } from '@elastic/eui';
import { FormContext } from '../create/form_context';
import { CreateCaseForm } from '../create/form';
@@ -40,21 +34,19 @@ const CreateModalComponent: React.FC = ({
onSuccess,
}) => {
return isModalOpen ? (
-
-
-
- {i18n.CREATE_TITLE}
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {i18n.CREATE_TITLE}
+
+
+
+
+
+
+
+
+
+
) : null;
};
diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx
index 4ce6948ed8791c..c4fa0304735341 100644
--- a/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx
+++ b/x-pack/plugins/security_solution/public/cases/containers/use_post_case.tsx
@@ -6,6 +6,7 @@
*/
import { useReducer, useCallback, useRef, useEffect } from 'react';
+
import { CasePostRequest } from '../../../../case/common/api';
import { errorToToaster, useStateToaster } from '../../common/components/toasters';
import { postCase } from './api';
@@ -16,6 +17,7 @@ interface NewCaseState {
isError: boolean;
}
type Action = { type: 'FETCH_INIT' } | { type: 'FETCH_SUCCESS' } | { type: 'FETCH_FAILURE' };
+
const dataFetchReducer = (state: NewCaseState, action: Action): NewCaseState => {
switch (action.type) {
case 'FETCH_INIT':
@@ -76,6 +78,7 @@ export const usePostCase = (): UsePostCase => {
},
[dispatchToaster]
);
+
useEffect(() => {
return () => {
abortCtrl.current.abort();
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout.test.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout.test.tsx
new file mode 100644
index 00000000000000..f908a79361d0a5
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout.test.tsx
@@ -0,0 +1,114 @@
+/*
+ * 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 { mount } from 'enzyme';
+import React from 'react';
+import { TestProviders } from '../../mock';
+import { CallOut } from './callout';
+import { CallOutMessage } from './callout_types';
+
+describe('callout', () => {
+ let message: CallOutMessage = {
+ type: 'primary',
+ id: 'some-id',
+ title: 'title',
+ description: <>{'some description'}>,
+ };
+
+ beforeEach(() => {
+ message = {
+ type: 'primary',
+ id: 'some-id',
+ title: 'title',
+ description: <>{'some description'}>,
+ };
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ test('renders the callout data-test-subj from the given id', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-some-id"]')).toEqual(true);
+ });
+
+ test('renders the callout dismiss button by default', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(true);
+ });
+
+ test('renders the callout dismiss button if given an explicit true to enable it', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(true);
+ });
+
+ test('Does NOT render the callout dismiss button if given an explicit false to disable it', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(false);
+ });
+
+ test('onDismiss callback operates when dismiss button is clicked', () => {
+ const onDismiss = jest.fn();
+ const wrapper = mount(
+
+
+
+ );
+ wrapper.find('[data-test-subj="callout-dismiss-btn"]').first().simulate('click');
+ expect(onDismiss).toBeCalledWith(message);
+ });
+
+ test('dismissButtonText can be set', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('[data-test-subj="callout-dismiss-btn"]').first().text()).toEqual(
+ 'Some other text'
+ );
+ });
+
+ test('a default icon type of "iInCircle" will be chosen if no iconType is set and the message type is "primary"', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('[data-test-subj="callout-some-id"]').first().prop('iconType')).toEqual(
+ 'iInCircle'
+ );
+ });
+
+ test('icon type can be changed from the type within the message', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('[data-test-subj="callout-some-id"]').first().prop('iconType')).toEqual(
+ 'something_else'
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout.tsx
index f6e0c89cab266c..2077e421c427a1 100644
--- a/x-pack/plugins/security_solution/public/common/components/callouts/callout.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout.tsx
@@ -8,8 +8,8 @@
import React, { FC, memo } from 'react';
import { EuiCallOut } from '@elastic/eui';
+import { assertUnreachable } from '../../../../common/utility_types';
import { CallOutType, CallOutMessage } from './callout_types';
-import { CallOutDescription } from './callout_description';
import { CallOutDismissButton } from './callout_dismiss_button';
export interface CallOutProps {
@@ -17,6 +17,7 @@ export interface CallOutProps {
iconType?: string;
dismissButtonText?: string;
onDismiss?: (message: CallOutMessage) => void;
+ showDismissButton?: boolean;
}
const CallOutComponent: FC = ({
@@ -24,8 +25,9 @@ const CallOutComponent: FC = ({
iconType,
dismissButtonText,
onDismiss,
+ showDismissButton = true,
}) => {
- const { type, id, title } = message;
+ const { type, id, title, description } = message;
const finalIconType = iconType ?? getDefaultIconType(type);
return (
@@ -36,8 +38,10 @@ const CallOutComponent: FC = ({
data-test-subj={`callout-${id}`}
data-test-messages={`[${id}]`}
>
-
-
+ {description}
+ {showDismissButton && (
+
+ )}
);
};
@@ -53,7 +57,7 @@ const getDefaultIconType = (type: CallOutType): string => {
case 'danger':
return 'alert';
default:
- return '';
+ return assertUnreachable(type);
}
};
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout_description.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout_description.tsx
deleted file mode 100644
index dbb1267c73323d..00000000000000
--- a/x-pack/plugins/security_solution/public/common/components/callouts/callout_description.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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 React, { FC } from 'react';
-import { EuiDescriptionList } from '@elastic/eui';
-import { CallOutMessage } from './callout_types';
-
-export interface CallOutDescriptionProps {
- messages: CallOutMessage | CallOutMessage[];
-}
-
-export const CallOutDescription: FC = ({ messages }) => {
- if (!Array.isArray(messages)) {
- return messages.description;
- }
-
- if (messages.length < 1) {
- return null;
- }
-
- return ;
-};
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout_persistent_switcher.tsx b/x-pack/plugins/security_solution/public/common/components/callouts/callout_persistent_switcher.tsx
new file mode 100644
index 00000000000000..5b67410bb904a8
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout_persistent_switcher.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 React, { FC, memo } from 'react';
+
+import { CallOutMessage } from './callout_types';
+import { CallOut } from './callout';
+
+export interface CallOutPersistentSwitcherProps {
+ condition: boolean;
+ message: CallOutMessage;
+}
+
+const CallOutPersistentSwitcherComponent: FC = ({
+ condition,
+ message,
+}): JSX.Element | null =>
+ condition ? : null;
+
+export const CallOutPersistentSwitcher = memo(CallOutPersistentSwitcherComponent);
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/callout_types.ts b/x-pack/plugins/security_solution/public/common/components/callouts/callout_types.ts
index 604f7b3e61c794..e04638a57ad06a 100644
--- a/x-pack/plugins/security_solution/public/common/components/callouts/callout_types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/callout_types.ts
@@ -5,7 +5,9 @@
* 2.0.
*/
-export type CallOutType = 'primary' | 'success' | 'warning' | 'danger';
+import { EuiCallOutProps } from '@elastic/eui';
+
+export type CallOutType = NonNullable;
export interface CallOutMessage {
type: CallOutType;
diff --git a/x-pack/plugins/security_solution/public/common/components/callouts/index.ts b/x-pack/plugins/security_solution/public/common/components/callouts/index.ts
index 222bf5daee6f57..0b7ec42744a6e4 100644
--- a/x-pack/plugins/security_solution/public/common/components/callouts/index.ts
+++ b/x-pack/plugins/security_solution/public/common/components/callouts/index.ts
@@ -8,3 +8,4 @@
export * from './callout_switcher';
export * from './callout_types';
export * from './callout';
+export * from './callout_persistent_switcher';
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
index a37528fcb24d7c..3ecc17589fe084 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
@@ -201,7 +201,7 @@ describe('EventsViewer', () => {
testProps = {
...testProps,
// Update with a new id, to force columns back to default.
- id: TimelineId.test2,
+ id: TimelineId.alternateTest,
};
const wrapper = mount(
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
index dc7388438c012a..5ea11f61f9a7e5 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
@@ -14,7 +14,6 @@ import {
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalFooter,
- EuiOverlayMask,
EuiButton,
EuiButtonEmpty,
EuiHorizontalRule,
@@ -348,133 +347,129 @@ export const AddExceptionModal = memo(function AddExceptionModal({
}, [maybeRule]);
return (
-
-
-
- {addExceptionMessage}
-
- {ruleName}
-
-
-
- {fetchOrCreateListError != null && (
-
-
-
+
+
+ {addExceptionMessage}
+
+ {ruleName}
+
+
+
+ {fetchOrCreateListError != null && (
+
+
+
+ )}
+ {fetchOrCreateListError == null &&
+ (isLoadingExceptionList ||
+ isIndexPatternLoading ||
+ isSignalIndexLoading ||
+ isSignalIndexPatternLoading) && (
+
)}
- {fetchOrCreateListError == null &&
- (isLoadingExceptionList ||
- isIndexPatternLoading ||
- isSignalIndexLoading ||
- isSignalIndexPatternLoading) && (
-
- )}
- {fetchOrCreateListError == null &&
- !isSignalIndexLoading &&
- !isSignalIndexPatternLoading &&
- !isLoadingExceptionList &&
- !isIndexPatternLoading &&
- !isRuleLoading &&
- !mlJobLoading &&
- ruleExceptionList && (
- <>
-
- {isRuleEQLSequenceStatement && (
- <>
-
-
- >
- )}
- {i18n.EXCEPTION_BUILDER_INFO}
-
-
-
-
-
-
-
-
-
- {alertData !== undefined && alertStatus !== 'closed' && (
-
-
-
- )}
+ {fetchOrCreateListError == null &&
+ !isSignalIndexLoading &&
+ !isSignalIndexPatternLoading &&
+ !isLoadingExceptionList &&
+ !isIndexPatternLoading &&
+ !isRuleLoading &&
+ !mlJobLoading &&
+ ruleExceptionList && (
+ <>
+
+ {isRuleEQLSequenceStatement && (
+ <>
+
+
+ >
+ )}
+ {i18n.EXCEPTION_BUILDER_INFO}
+
+
+
+
+
+
+
+
+
+ {alertData !== undefined && alertStatus !== 'closed' && (
- {exceptionListType === 'endpoint' && (
- <>
-
-
- {i18n.ENDPOINT_QUARANTINE_TEXT}
-
- >
- )}
-
- >
- )}
- {fetchOrCreateListError == null && (
-
- {i18n.CANCEL}
-
-
- {addExceptionMessage}
-
-
+ )}
+
+
+
+ {exceptionListType === 'endpoint' && (
+ <>
+
+
+ {i18n.ENDPOINT_QUARANTINE_TEXT}
+
+ >
+ )}
+
+ >
)}
-
-
+ {fetchOrCreateListError == null && (
+
+ {i18n.CANCEL}
+
+
+ {addExceptionMessage}
+
+
+ )}
+
);
});
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
index 75b7bf2aabd7fd..336732016e9369 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
@@ -12,7 +12,6 @@ import {
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalFooter,
- EuiOverlayMask,
EuiButton,
EuiButtonEmpty,
EuiHorizontalRule,
@@ -281,125 +280,121 @@ export const EditExceptionModal = memo(function EditExceptionModal({
}, [maybeRule]);
return (
-
-
-
-
- {exceptionListType === 'endpoint'
- ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE
- : i18n.EDIT_EXCEPTION_TITLE}
-
-
- {ruleName}
-
-
- {(addExceptionIsLoading || isIndexPatternLoading || isSignalIndexLoading) && (
-
- )}
- {!isSignalIndexLoading &&
- !addExceptionIsLoading &&
- !isIndexPatternLoading &&
- !isRuleLoading &&
- !mlJobLoading && (
- <>
-
- {isRuleEQLSequenceStatement && (
- <>
-
-
- >
- )}
- {i18n.EXCEPTION_BUILDER_INFO}
-
-
-
-
-
-
-
-
-
-
-
+
+
+ {exceptionListType === 'endpoint'
+ ? i18n.EDIT_ENDPOINT_EXCEPTION_TITLE
+ : i18n.EDIT_EXCEPTION_TITLE}
+
+
+ {ruleName}
+
+
+ {(addExceptionIsLoading || isIndexPatternLoading || isSignalIndexLoading) && (
+
+ )}
+ {!isSignalIndexLoading &&
+ !addExceptionIsLoading &&
+ !isIndexPatternLoading &&
+ !isRuleLoading &&
+ !mlJobLoading && (
+ <>
+
+ {isRuleEQLSequenceStatement && (
+ <>
+
-
- {exceptionListType === 'endpoint' && (
- <>
-
-
- {i18n.ENDPOINT_QUARANTINE_TEXT}
-
- >
- )}
-
- >
- )}
- {updateError != null && (
-
-
-
- )}
- {hasVersionConflict && (
-
-
- {i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}
-
-
- )}
- {updateError == null && (
-
- {i18n.CANCEL}
-
-
- {i18n.EDIT_EXCEPTION_SAVE_BUTTON}
-
-
+
+ >
+ )}
+ {i18n.EXCEPTION_BUILDER_INFO}
+
+
+
+
+
+
+
+
+
+
+
+
+ {exceptionListType === 'endpoint' && (
+ <>
+
+
+ {i18n.ENDPOINT_QUARANTINE_TEXT}
+
+ >
+ )}
+
+ >
)}
-
-
+ {updateError != null && (
+
+
+
+ )}
+ {hasVersionConflict && (
+
+
+ {i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}
+
+
+ )}
+ {updateError == null && (
+
+ {i18n.CANCEL}
+
+
+ {i18n.EDIT_EXCEPTION_SAVE_BUTTON}
+
+
+ )}
+
);
});
diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap
index 6503dd8dfb5086..d1a41b1c32c102 100644
--- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/__snapshots__/index.test.tsx.snap
@@ -2,64 +2,62 @@
exports[`ImportDataModal renders correctly against snapshot 1`] = `
-
-
-
-
- title
-
-
-
-
-
- description
-
-
-
-
-
-
-
-
-
- Cancel
-
-
- submitBtnText
-
-
-
-
+
+
+
+ title
+
+
+
+
+
+ description
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+ submitBtnText
+
+
+
`;
diff --git a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx
index 8a29ce3799321f..4c3dc2a249b4ff 100644
--- a/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/import_data_modal/index.tsx
@@ -15,7 +15,6 @@ import {
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
- EuiOverlayMask,
EuiSpacer,
EuiText,
} from '@elastic/eui';
@@ -132,51 +131,49 @@ export const ImportDataModalComponent = ({
return (
<>
{showModal && (
-
-
-
- {title}
-
-
-
-
- {description}
-
-
-
- {
- setSelectedFiles(files && files.length > 0 ? files : null);
- }}
- display={'large'}
- fullWidth={true}
- isLoading={isImporting}
+
+
+ {title}
+
+
+
+
+ {description}
+
+
+
+ {
+ setSelectedFiles(files && files.length > 0 ? files : null);
+ }}
+ display={'large'}
+ fullWidth={true}
+ isLoading={isImporting}
+ />
+
+ {showCheckBox && (
+ setOverwrite(!overwrite)}
/>
-
- {showCheckBox && (
- setOverwrite(!overwrite)}
- />
- )}
-
-
-
- {i18n.CANCEL_BUTTON}
-
- {submitBtnText}
-
-
-
-
+ )}
+
+
+
+ {i18n.CANCEL_BUTTON}
+
+ {submitBtnText}
+
+
+
)}
>
);
diff --git a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx
index ece29cd360ce71..a5c0144531110a 100644
--- a/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/inspect/modal.tsx
@@ -15,7 +15,6 @@ import {
EuiModalHeader,
EuiModalHeaderTitle,
EuiModalFooter,
- EuiOverlayMask,
EuiSpacer,
EuiTabbedContent,
} from '@elastic/eui';
@@ -211,24 +210,22 @@ export const ModalInspectQuery = ({
];
return (
-
-
-
-
- {i18n.INSPECT} {title}
-
-
-
-
-
-
-
-
-
- {i18n.CLOSE}
-
-
-
-
+
+
+
+ {i18n.INSPECT} {title}
+
+
+
+
+
+
+
+
+
+ {i18n.CLOSE}
+
+
+
);
};
diff --git a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
index 778916ad2d07ac..be5702550a44c8 100644
--- a/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/paginated_table/__snapshots__/index.test.tsx.snap
@@ -246,6 +246,12 @@ exports[`Paginated Table Component rendering it renders the default load more ta
},
"euiFilePickerTallHeight": "128px",
"euiFlyoutBorder": "1px solid #343741",
+ "euiFlyoutPaddingModifiers": Object {
+ "paddingLarge": "24px",
+ "paddingMedium": "16px",
+ "paddingNone": 0,
+ "paddingSmall": "8px",
+ },
"euiFocusBackgroundColor": "#08334a",
"euiFocusRingAnimStartColor": "rgba(27, 169, 245, 0)",
"euiFocusRingAnimStartSize": "6px",
@@ -357,6 +363,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta
},
"euiMarkdownEditorMinHeight": "150px",
"euiPageBackgroundColor": "#1a1b20",
+ "euiPageDefaultMaxWidth": "1000px",
"euiPaletteColorBlind": Object {
"euiColorVis0": Object {
"behindText": "#6dccb1",
@@ -534,6 +541,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta
"euiSwitchWidthCompressed": "28px",
"euiSwitchWidthMini": "22px",
"euiTabFontSize": "16px",
+ "euiTabFontSizeL": "18px",
"euiTabFontSizeS": "14px",
"euiTableActionsAreaWidth": "40px",
"euiTableActionsBorderColor": "rgba(83, 89, 102, 0.09999999999999998)",
diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap
index f7924f37d2c173..5e008e28073de1 100644
--- a/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap
@@ -1,50 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Modal all errors rendering it renders the default all errors modal when isShowing is positive 1`] = `
-
-
-
-
- Your visualization has error(s)
-
-
-
-
-
-
-
- Error 1, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
-
-
-
-
-
- Close
-
-
-
-
+
+
+
+ Your visualization has error(s)
+
+
+
+
+
+
+
+ Error 1, Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+
+
+
+
+ Close
+
+
+
`;
diff --git a/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx b/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx
index 873ebe97317f4f..0a78139f5fe3a1 100644
--- a/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/toasters/modal_all_errors.tsx
@@ -7,7 +7,6 @@
import {
EuiButton,
- EuiOverlayMask,
EuiModal,
EuiModalHeader,
EuiModalHeaderTitle,
@@ -36,36 +35,34 @@ const ModalAllErrorsComponent: React.FC = ({ isShowing, toast, t
if (!isShowing || toast == null) return null;
return (
-
-
-
- {i18n.TITLE_ERROR_MODAL}
-
+
+
+ {i18n.TITLE_ERROR_MODAL}
+
-
-
-
- {toast.errors != null &&
- toast.errors.map((error, index) => (
- 100 ? `${error.substring(0, 100)} ...` : error}
- data-test-subj="modal-all-errors-accordion"
- >
- {error}
-
- ))}
-
+
+
+
+ {toast.errors != null &&
+ toast.errors.map((error, index) => (
+ 100 ? `${error.substring(0, 100)} ...` : error}
+ data-test-subj="modal-all-errors-accordion"
+ >
+ {error}
+
+ ))}
+
-
-
- {i18n.CLOSE_ERROR_MODAL}
-
-
-
-
+
+
+ {i18n.CLOSE_ERROR_MODAL}
+
+
+
);
};
diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx
index 2c744b228ba212..2fa63205ffe975 100644
--- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx
@@ -66,7 +66,12 @@ export const useInitSourcerer = (
selectedPatterns: [...ConfigIndexPatterns, signalIndexName],
})
);
- } else if (signalIndexNameSelector != null && initialTimelineSourcerer.current) {
+ } else if (
+ signalIndexNameSelector != null &&
+ (activeTimeline == null ||
+ (activeTimeline != null && activeTimeline.savedObjectId == null)) &&
+ initialTimelineSourcerer.current
+ ) {
initialTimelineSourcerer.current = false;
dispatch(
sourcererActions.setSelectedIndexPatterns({
diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_timeline_events_count.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_timeline_events_count.tsx
index 714da27908423f..e801db0190799d 100644
--- a/x-pack/plugins/security_solution/public/common/hooks/use_timeline_events_count.tsx
+++ b/x-pack/plugins/security_solution/public/common/hooks/use_timeline_events_count.tsx
@@ -12,17 +12,28 @@ import { createPortalNode, OutPortal } from 'react-reverse-portal';
* A singleton portal for rendering content in the global header
*/
const timelineEventsCountPortalNodeSingleton = createPortalNode();
+const eqlEventsCountPortalNodeSingleton = createPortalNode();
export const useTimelineEventsCountPortal = () => {
const [timelineEventsCountPortalNode] = useState(timelineEventsCountPortalNodeSingleton);
-
- return { timelineEventsCountPortalNode };
+ return { portalNode: timelineEventsCountPortalNode };
};
export const TimelineEventsCountBadge = React.memo(() => {
- const { timelineEventsCountPortalNode } = useTimelineEventsCountPortal();
-
- return ;
+ const { portalNode } = useTimelineEventsCountPortal();
+ return ;
});
TimelineEventsCountBadge.displayName = 'TimelineEventsCountBadge';
+
+export const useEqlEventsCountPortal = () => {
+ const [eqlEventsCountPortalNode] = useState(eqlEventsCountPortalNodeSingleton);
+ return { portalNode: eqlEventsCountPortalNode };
+};
+
+export const EqlEventsCountBadge = React.memo(() => {
+ const { portalNode } = useEqlEventsCountPortal();
+ return ;
+});
+
+EqlEventsCountBadge.displayName = 'EqlEventsCountBadge';
diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
index bfd25aa469c931..5eae3a4d729882 100644
--- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
@@ -212,6 +212,11 @@ export const mockGlobalState: State = {
itemsPerPage: 5,
dataProviders: [],
description: '',
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ },
eventIdToNoteIds: {},
excludedRowRendererIds: [],
expandedDetail: {},
diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_endgame_ecs_data.ts b/x-pack/plugins/security_solution/public/common/mock/mock_endgame_ecs_data.ts
index 1082b5f9474e53..3400844e671b30 100644
--- a/x-pack/plugins/security_solution/public/common/mock/mock_endgame_ecs_data.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/mock_endgame_ecs_data.ts
@@ -343,6 +343,885 @@ export const mockEndpointFileDeletionEvent: Ecs = {
_id: 'mnXHO3cBPmkOXwyNlyv_',
};
+export const mockEndpointFileCreationMalwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['efca0a88adab8b92e4a333b56db5fbaa'],
+ sha256: ['8c177f6129dddbd36cae196ef9d9eb71f50cee44640068f24830e83d6a9dd1d0'],
+ sha1: ['e55e587058112c60d015994424f70a7a8e78afb1'],
+ },
+ parent: {
+ name: ['explorer.exe'],
+ pid: [1008],
+ },
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTg5NDQtMTMyNDkwNjg0NzIuNzM4OTY4NTAw',
+ ],
+ executable: ['C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'],
+ name: ['chrome.exe'],
+ pid: [8944],
+ args: ['C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1518)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1518)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ name: ['win2019-endpoint-1'],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['creation'],
+ id: ['LsuMZVr+sdhvehVM++++Ic8J'],
+ kind: ['alert'],
+ module: ['endpoint'],
+ type: ['info', 'creation', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ file: {
+ path: ['C:\\Users\\sean\\Downloads\\6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmp'],
+ owner: ['sean'],
+ hash: {
+ md5: ['c1f8d2b73b4c2488f95e7305f0421bdf'],
+ sha256: ['7cc42618e580f233fee47e82312cc5c3476cb5de9219ba3f9eb7f99ac0659c30'],
+ sha1: ['542b2796e9f57a92504f852b6698148bba9ff289'],
+ },
+ name: ['6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmp'],
+ extension: ['tmp'],
+ size: [196608],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2020-11-05T16:48:19.923Z',
+ message: ['Malware Prevention Alert'],
+ _id: 'dGZQmXUB-o9SpDeMqvln',
+};
+
+export const mockEndpointFileCreationMalwareDetectionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['16d6a536bb2115dcbd16011e6991a9fd'],
+ sha256: ['6637eca55fedbabc510168f0c4696d41971c89e5d1fb440f2f9391e6ab0e8f54'],
+ sha1: ['05cc6d37603ca9076f3baf4dc421500c5cf69e4c'],
+ },
+ entity_id: [
+ 'Yjk3ZWYwODktNzYyZi00ZTljLTg3OWMtNmQ5MDM1ZjBmYTUzLTQ0MDAtMTMyNDM2MTgwMzIuMjA0MzMxMDA=',
+ ],
+ executable: ['C:\\Python27\\python.exe'],
+ parent: {
+ name: ['pythonservice.exe'],
+ pid: [2936],
+ },
+ name: ['python.exe'],
+ args: ['C:\\Python27\\python.exe', 'main.py', '-a,execute', '-p', 'c:\\temp'],
+ pid: [4400],
+ },
+ host: {
+ os: {
+ full: ['Windows 10 Pro 1903 (10.0.18362.1016)'],
+ name: ['Windows'],
+ version: ['1903 (10.0.18362.1016)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1903 (10.0.18362.1016)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ ip: ['10.1.2.3'],
+ id: ['c85e6c40-d4a1-db21-7458-2565a6b857f3'],
+ architecture: ['x86_64'],
+ name: ['DESKTOP-1'],
+ },
+ file: {
+ path: ['C:\\temp\\mimikatz_write.exe'],
+ owner: ['Administrators'],
+ hash: {
+ md5: ['cc52aebdf82048364119f117f52dbba0'],
+ sha256: ['263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'],
+ sha1: ['c929f6ff2d6d1085ee69625cd8efb92101a0e906'],
+ },
+ name: ['mimikatz_write.exe'],
+ extension: ['exe'],
+ size: [1265456],
+ },
+ event: {
+ id: ['Lp/73XQ38EF48a6i+++++5Ds'],
+ module: ['endpoint'],
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['creation'],
+ kind: ['signal'],
+ type: ['info', 'creation', 'allowed'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Malware Detection Alert'],
+ timestamp: '2020-09-03T15:51:50.209Z',
+ _id: '51e04f7dad15fe394a3f7ed582ad4528c8ce62948e315571fc3388befd9aa0e6',
+};
+
+export const mockEndpointFilesEncryptedRansomwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['85bc517e37fe24f909e4378a46a4b567'],
+ sha256: ['e9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7'],
+ sha1: ['10a3671c0fbc2bce14fc94891e87e2f4ba07e0df'],
+ },
+ parent: {
+ name: ['cmd.exe'],
+ pid: [10680],
+ },
+ entity_id: [
+ 'OTI1MTRiMTYtMWJkNi05NzljLWE2MDMtOTgwY2ZkNzQ4M2IwLTYwNTYtMTMyNTczODEzMzYuNzIxNTIxODAw',
+ ],
+ name: ['powershell.exe'],
+ pid: [6056],
+ args: ['powershell.exe', '-file', 'mock_ransomware_v3.ps1'],
+ },
+ host: {
+ os: {
+ full: ['Windows 7 Enterprise Service Pack 1 (6.1.7601)'],
+ name: ['Windows'],
+ version: ['Service Pack 1 (6.1.7601)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['Service Pack 1 (6.1.7601)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['c6bb2832-d58c-4c57-9d1f-3b102ea74d46'],
+ name: ['DESKTOP-1'],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'process', 'file'],
+ outcome: ['success'],
+ code: ['ransomware'],
+ action: ['files-encrypted'],
+ id: ['M0A1DXHIg6/kaeku+++++1Gv'],
+ kind: ['alert'],
+ module: ['endpoint'],
+ type: ['info', 'start', 'change', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2021-02-09T21:55:48.941Z',
+ message: ['Ransomware Prevention Alert'],
+ _id: 'BfvLiHcBVXUk10dUK1Pk',
+};
+
+export const mockEndpointFilesEncryptedRansomwareDetectionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['85bc517e37fe24f909e4378a46a4b567'],
+ sha256: ['e9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7'],
+ sha1: ['10a3671c0fbc2bce14fc94891e87e2f4ba07e0df'],
+ },
+ parent: {
+ name: ['cmd.exe'],
+ pid: [8616],
+ },
+ entity_id: [
+ 'MDAwODRkOTAtZDRhOC1kOTZhLWVmYWItZDU1ZWFhNDY1N2M2LTQ2ODQtMTMyNTc0NjE2MzEuNDM3NDUzMDA=',
+ ],
+ executable: ['C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'],
+ name: ['powershell.exe'],
+ pid: [4684],
+ args: ['powershell.exe', '-file', 'mock_ransomware_v3.ps1'],
+ },
+ host: {
+ os: {
+ full: ['Windows 7 Enterprise Service Pack 1 (6.1.7601)'],
+ name: ['Windows'],
+ version: ['Service Pack 1 (6.1.7601)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['Service Pack 1 (6.1.7601)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['c6bb2832-d58c-4c57-9d1f-3b102ea74d46'],
+ name: ['DESKTOP-1'],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'process', 'file'],
+ code: ['ransomware'],
+ action: ['files-encrypted'],
+ id: ['M0ExfR/BggxoHQ1e+++++1Zv'],
+ kind: ['alert'],
+ module: ['endpoint'],
+ type: ['info', 'start', 'change', 'allowed'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2021-02-10T20:14:03.927Z',
+ message: ['Ransomware Detection Alert'],
+ _id: 'enyUjXcBxUk8qlINZEJr',
+};
+
+export const mockEndpointFileModificationMalwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['47ea9e07b7dbfbeba368bd95a3a2d25b'],
+ sha256: ['f45557c0b57dec4c000d8cb7d7068c8a4dccf392de740501b1046994460d77ea'],
+ sha1: ['da714f84a7bbaee2be9f1ca0262aca649657cf3e'],
+ },
+ parent: {
+ name: ['C:\\Windows\\System32\\userinit.exe'],
+ pid: [356],
+ },
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTEwMDgtMTMyNDc1Njk3ODUuODA0NzQyMDA=',
+ ],
+ executable: ['C:\\Windows\\explorer.exe'],
+ name: ['explorer.exe'],
+ pid: [1008],
+ args: ['C:\\Windows\\Explorer.EXE'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1518)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1518)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ name: ['win2019-endpoint-1'],
+ },
+ file: {
+ path: ['C:\\Users\\sean\\Downloads\\mimikatz_trunk (1)\\x64\\mimikatz - Copy.exe'],
+ owner: ['sean'],
+ hash: {
+ md5: ['a3cb3b02a683275f7e0a0f8a9a5c9e07'],
+ sha256: ['31eb1de7e840a342fd468e558e5ab627bcb4c542a8fe01aec4d5ba01d539a0fc'],
+ sha1: ['d241df7b9d2ec0b8194751cd5ce153e27cc40fa4'],
+ },
+ name: ['mimikatz - Copy.exe'],
+ extension: ['exe'],
+ size: [1309448],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['modification'],
+ id: ['LsuMZVr+sdhvehVM++++GvWi'],
+ kind: ['alert'],
+ created: ['2020-11-04T22:40:51.724Z'],
+ module: ['endpoint'],
+ type: ['info', 'change', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2020-11-04T22:40:51.724Z',
+ message: ['Malware Prevention Alert'],
+ _id: 'j0RtlXUB-o9SpDeMLdEE',
+};
+
+export const mockEndpointFileModificationMalwareDetectionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['c93876879542fc4710ab1d3b52382d95'],
+ sha256: ['0ead4d0131ca81aa4820efdcd3c6053eab23179a46c5480c94d7c11eb8451d62'],
+ sha1: ['def88472b5d92022b6182bfe031c043ddfc5ff0f'],
+ },
+ parent: {
+ name: ['Python'],
+ pid: [97],
+ },
+ entity_id: [
+ 'ZGQ0NDBhNjMtZjcyNy00NGY4LWI5M2UtNzQzZWEzMDBiYTk2LTU5OTUtMTMyNDM2MTg1MzkuOTUyNjkwMDA=',
+ ],
+ executable: [
+ '/usr/local/Cellar/python/2.7.14/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python',
+ ],
+ name: ['Python'],
+ args: [
+ '/usr/local/Cellar/python/2.7.14/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python',
+ 'main.py',
+ '-a',
+ 'modify',
+ ],
+ pid: [5995],
+ },
+ host: {
+ os: {
+ full: ['macOS 10.14.1'],
+ name: ['macOS'],
+ version: ['10.14.1'],
+ platform: ['macos'],
+ family: ['macos'],
+ kernel: [
+ 'Darwin Kernel Version 18.2.0: Fri Oct 5 19:40:55 PDT 2018; root:xnu-4903.221.2~1/RELEASE_X86_64',
+ ],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ ip: ['10.1.2.3'],
+ id: ['7d59b1a5-afa1-6531-07ea-691602558230'],
+ architecture: ['x86_64'],
+ name: ['mac-1.local'],
+ },
+ file: {
+ mtime: ['2020-09-03T14:55:42.842Z'],
+ path: ['/private/var/root/write_malware/modules/write_malware/aircrack'],
+ owner: ['root'],
+ hash: {
+ md5: ['59328cdab10fb4f25a026eb362440422'],
+ sha256: ['f0954d9673878b2223b00b7ec770c7b438d876a9bb44ec78457e5c618f31f52b'],
+ sha1: ['f10b043652da8c444e04aede3a9ce4a10ef9028e'],
+ },
+ name: ['aircrack'],
+ size: [240916],
+ },
+ event: {
+ id: ['Lp21aufnU2nkG+fO++++++7h'],
+ module: ['endpoint'],
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['modification'],
+ type: ['info', 'change', 'allowed'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Malware Detection Alert'],
+ timestamp: '2020-09-03T15:01:19.445Z',
+ _id: '04d309c7e4cf7c4e54b7e3d93c38399e51797eed2484078487f4d6661f94da2c',
+};
+
+export const mockEndpointFileRenameMalwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['47ea9e07b7dbfbeba368bd95a3a2d25b'],
+ sha256: ['f45557c0b57dec4c000d8cb7d7068c8a4dccf392de740501b1046994460d77ea'],
+ sha1: ['da714f84a7bbaee2be9f1ca0262aca649657cf3e'],
+ },
+ parent: {
+ name: ['C:\\Windows\\System32\\userinit.exe'],
+ pid: [356],
+ },
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTEwMDgtMTMyNDc1Njk3ODUuODA0NzQyMDA=',
+ ],
+ executable: ['C:\\Windows\\explorer.exe'],
+ name: ['explorer.exe'],
+ pid: [1008],
+ args: ['C:\\Windows\\Explorer.EXE'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1518)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1518)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ name: ['win2019-endpoint-1'],
+ },
+ file: {
+ mtime: ['2020-11-04T21:48:47.559Z'],
+ path: [
+ 'C:\\Users\\sean\\Downloads\\23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exe',
+ ],
+ owner: ['sean'],
+ hash: {
+ md5: ['9798063a1fe056ef2f1d6f5217e7b82b'],
+ sha256: ['23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97'],
+ sha1: ['ced72fe7fc3835385faea41c657efab7b9f883cd'],
+ },
+ name: ['23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exe'],
+ extension: ['exe'],
+ size: [242010],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'file'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['rename'],
+ id: ['LsuMZVr+sdhvehVM++++GppA'],
+ kind: ['alert'],
+ module: ['endpoint'],
+ type: ['info', 'change', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2020-11-04T21:48:57.847Z',
+ message: ['Malware Prevention Alert'],
+ _id: 'qtA9lXUBn9bLIbfPj-Tu',
+};
+
+export const mockEndpointFileRenameMalwareDetectionAlert: Ecs = {
+ ...mockEndpointFileRenameMalwarePreventionAlert,
+ event: {
+ ...mockEndpointFileRenameMalwarePreventionAlert.event,
+ type: ['info', 'change', 'allowed'],
+ },
+ message: ['Malware Detection Alert'],
+ _id: 'CD7B6A22-809C-4502-BB94-BC38901EC942',
+};
+
+// NOTE: see `mock_timeline_data.ts` for the mockEndpointProcessExecutionMalwarePreventionAlert
+
+export const mockEndpointProcessExecutionMalwareDetectionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['cc52aebdf82048364119f117f52dbba0'],
+ sha256: ['263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'],
+ sha1: ['c929f6ff2d6d1085ee69625cd8efb92101a0e906'],
+ },
+ entity_id: [
+ 'Yjk3ZWYwODktNzYyZi00ZTljLTg3OWMtNmQ5MDM1ZjBmYTUzLTg2NjgtMTMyNDM2MTgwMzQuODU3Njg5MDA=',
+ ],
+ executable: ['C:\\temp\\mimikatz_write.exe'],
+ parent: {
+ name: ['python.exe'],
+ },
+ name: ['mimikatz_write.exe'],
+ args: ['c:\\temp\\mimikatz_write.exe'],
+ pid: [8668],
+ },
+ host: {
+ os: {
+ full: ['Windows 10 Pro 1903 (10.0.18362.1016)'],
+ name: ['Windows'],
+ version: ['1903 (10.0.18362.1016)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1903 (10.0.18362.1016)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ ip: ['10.1.2.3'],
+ id: ['c85e6c40-d4a1-db21-7458-2565a6b857f3'],
+ architecture: ['x86_64'],
+ name: ['DESKTOP-1'],
+ },
+ file: {
+ mtime: ['2020-09-03T14:47:14.647Z'],
+ path: ['C:\\temp\\mimikatz_write.exe'],
+ owner: ['Administrators'],
+ hash: {
+ md5: ['cc52aebdf82048364119f117f52dbba0'],
+ sha256: ['263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'],
+ sha1: ['c929f6ff2d6d1085ee69625cd8efb92101a0e906'],
+ },
+ name: ['mimikatz_write.exe'],
+ extension: ['exe'],
+ size: [1265456],
+ },
+ event: {
+ id: ['Lp/73XQ38EF48a6i+++++5Do'],
+ module: ['endpoint'],
+ category: ['malware', 'intrusion_detection', 'process'],
+ outcome: ['success'],
+ code: ['malicious_file'],
+ action: ['execution'],
+ kind: ['signal'],
+ type: ['info', 'start', 'allowed'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Malware Detection Alert'],
+ timestamp: '2020-09-03T15:51:50.209Z',
+ _id: '96b3db3079891faaf155f1ada645b7364a03018c65677ce002f18038e7ce1c47',
+};
+
+export const mockEndpointFileModificationEvent: Ecs = {
+ file: {
+ path: ['/Users/admin/Library/Application Support/CrashReporter/.dat.nosync01a5.6hoWv1'],
+ name: ['.dat.nosync01a5.6hoWv1'],
+ },
+ host: {
+ os: {
+ full: ['macOS 10.14.6'],
+ name: ['macOS'],
+ version: ['10.14.6'],
+ family: ['macos'],
+ kernel: [
+ 'Darwin Kernel Version 18.7.0: Mon Aug 31 20:53:32 PDT 2020; root:xnu-4903.278.44~1/RELEASE_X86_64',
+ ],
+ platform: ['macos'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['test-Mac.local'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['fce6b9f1-5c09-d8f0-3d99-9ecb30f995df'],
+ },
+ event: {
+ category: ['file'],
+ kind: ['event'],
+ module: ['endpoint'],
+ action: ['modification'],
+ type: ['change'],
+ dataset: ['endpoint.events.file'],
+ },
+ process: {
+ name: ['diagnostics_agent'],
+ pid: [421],
+ entity_id: ['OTA1ZDkzMTctMjIxOS00ZjQ1LTg4NTMtYzNiYzk1NGU1ZGU4LTQyMS0xMzI0OTEwNTIwOC4w'],
+ executable: ['/System/Library/CoreServices/diagnostics_agent'],
+ },
+ user: {
+ id: ['501'],
+ name: ['admin'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint file event'],
+ timestamp: '2021-02-02T18:56:12.871Z',
+ _id: 'ulkWZHcBGrBB52F2vFf_',
+};
+
+export const mockEndpointFileOverwriteEvent: Ecs = {
+ file: {
+ path: ['C:\\Windows\\ServiceState\\EventLog\\Data\\lastalive0.dat'],
+ extension: ['dat'],
+ name: ['lastalive0.dat'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ platform: ['windows'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['windows-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['ce6fa3c3-fda1-4984-9bce-f6d602a5bd1a'],
+ },
+ event: {
+ category: ['file'],
+ kind: ['event'],
+ created: ['2021-02-02T21:40:14.400Z'],
+ module: ['endpoint'],
+ action: ['overwrite'],
+ type: ['change'],
+ id: ['Lzty2lsJxA05IUWg++++Icrn'],
+ dataset: ['endpoint.events.file'],
+ },
+ process: {
+ name: ['svchost.exe'],
+ pid: [1228],
+ entity_id: [
+ 'YjUwNDNiMTMtYTdjNi0xZGFlLTEyZWQtODQ1ZDlhNTRhZmQyLTEyMjgtMTMyNTQ5ODc1MDcuODc1MTIxNjAw',
+ ],
+ executable: ['C:\\Windows\\System32\\svchost.exe'],
+ },
+ user: {
+ id: ['S-1-5-19'],
+ name: ['LOCAL SERVICE'],
+ domain: ['NT AUTHORITY'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint file event'],
+ timestamp: '2021-02-02T21:40:14.400Z',
+ _id: 'LBmxZHcBtgfIO53sCImw',
+};
+
+export const mockEndpointFileRenameEvent: Ecs = {
+ file: {
+ path: ['C:\\Windows\\System32\\sru\\SRU.log'],
+ Ext: {
+ original: {
+ path: ['C:\\Windows\\System32\\sru\\SRUtmp.log'],
+ name: ['SRUtmp.log'],
+ },
+ },
+ extension: ['log'],
+ name: ['SRU.log'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['windows-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['ce6fa3c3-fda1-4984-9bce-f6d602a5bd1a'],
+ },
+ event: {
+ category: ['file'],
+ kind: ['event'],
+ created: ['2021-02-01T16:43:00.373Z'],
+ module: ['endpoint'],
+ action: ['rename'],
+ type: ['change'],
+ id: ['Lzty2lsJxA05IUWg++++I3jv'],
+ dataset: ['endpoint.events.file'],
+ },
+ process: {
+ name: ['svchost.exe'],
+ pid: [1204],
+ entity_id: [
+ 'YjUwNDNiMTMtYTdjNi0xZGFlLTEyZWQtODQ1ZDlhNTRhZmQyLTEyMDQtMTMyNTQ5ODc2NzQuNzQ5MjUzNzAw',
+ ],
+ executable: ['C:\\Windows\\System32\\svchost.exe'],
+ },
+ user: {
+ id: ['S-1-5-19'],
+ name: ['LOCAL SERVICE'],
+ domain: ['NT AUTHORITY'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint file event'],
+ timestamp: '2021-02-01T16:43:00.373Z',
+ _id: 'OlJ8XncBGrBB52F2Oga7',
+};
+
+// NOTE: see `mock_timeline_data.ts` for the mockEndpointRegistryModificationEvent
+
+// NOTE: see `mock_timeline_data.ts` for the mockEndpointLibraryLoadEvent
+
+export const mockEndpointNetworkHttpRequestEvent: Ecs = {
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ platform: ['windows'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['win2019-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ },
+ event: {
+ category: ['network'],
+ kind: ['event'],
+ module: ['endpoint'],
+ action: ['http_request'],
+ type: ['protocol'],
+ id: ['LzzWB9jjGmCwGMvk++++FD+p'],
+ dataset: ['endpoint.events.network'],
+ },
+ process: {
+ name: ['svchost.exe'],
+ pid: [2232],
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTIyMzItMTMyNTUwNzg2ODkuNTA1NzEzMDA=',
+ ],
+ executable: ['C:\\Windows\\System32\\svchost.exe'],
+ },
+ destination: {
+ geo: {
+ region_name: ['Arizona'],
+ continent_name: ['North America'],
+ city_name: ['Phoenix'],
+ country_name: ['United States'],
+ region_iso_code: ['US-AZ'],
+ country_iso_code: ['US'],
+ },
+ port: [80],
+ ip: ['10.11.12.13'],
+ },
+ source: {
+ ip: ['10.1.2.3'],
+ port: [51570],
+ },
+ http: {
+ request: {
+ body: {
+ content: [
+ 'GET /msdownload/update/v3/static/trustedr/en/authrootstl.cab?b3d6249cb8dde683 HTTP/1.1\r\nConnection: Keep-Alive\r\nAccept: */*\r\nIf-Modified-Since: Fri, 15 Jan 2021 00:46:38 GMT\r\nIf-None-Match: "0ebbae1d7ead61:0"\r\nUser-Agent: Microsoft-CryptoAPI/10.0\r\nHost: ctldl.windowsupdate.com\r\n\r\n',
+ ],
+ bytes: [281],
+ },
+ },
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ user: {
+ name: ['NETWORK SERVICE'],
+ domain: ['NT AUTHORITY'],
+ },
+ network: {
+ protocol: ['http'],
+ direction: ['outgoing'],
+ transport: ['tcp'],
+ },
+ message: ['Endpoint network event'],
+ timestamp: '2021-02-08T19:19:38.241Z',
+ _id: '5Qwdg3cBX5UUcOOY03W7',
+};
+
+export const mockEndpointProcessExecEvent: Ecs = {
+ process: {
+ hash: {
+ md5: ['fbc61bd19421211e341e6d9b3f65e334'],
+ sha256: ['4bc018ac461706496302d1faab0a8bb39aad974eb432758665103165f3a2dd2b'],
+ sha1: ['1dc525922869533265fbeac8f7d3021489b60129'],
+ },
+ name: ['mdworker_shared'],
+ parent: {
+ name: ['launchd'],
+ pid: [1],
+ },
+ pid: [4454],
+ entity_id: [
+ 'OTA1ZDkzMTctMjIxOS00ZjQ1LTg4NTMtYzNiYzk1NGU1ZGU4LTQ0NTQtMTMyNTY3NjYwMDEuNzIwMjkwMDA=',
+ ],
+ executable: [
+ '/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared',
+ ],
+ args: [
+ '/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared',
+ '-s',
+ 'mdworker',
+ '-c',
+ 'MDSImporterWorker',
+ '-m',
+ 'com.apple.mdworker.shared',
+ ],
+ },
+ host: {
+ os: {
+ full: ['macOS 10.14.6'],
+ name: ['macOS'],
+ version: ['10.14.6'],
+ family: ['macos'],
+ kernel: [
+ 'Darwin Kernel Version 18.7.0: Mon Aug 31 20:53:32 PDT 2020; root:xnu-4903.278.44~1/RELEASE_X86_64',
+ ],
+ platform: ['macos'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['test-mac.local'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['fce6b9f1-5c09-d8f0-3d99-9ecb30f995df'],
+ },
+ event: {
+ category: ['process'],
+ kind: ['event'],
+ module: ['endpoint'],
+ action: ['exec'],
+ type: ['start'],
+ id: ['LuH/UjERrFf60dea+++++NW7'],
+ dataset: ['endpoint.events.process'],
+ },
+ user: {
+ id: ['501'],
+ name: ['admin'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint process event'],
+ timestamp: '2021-02-02T19:00:01.972Z',
+ _id: '8lkaZHcBGrBB52F2aN8c',
+};
+
+export const mockEndpointProcessForkEvent: Ecs = {
+ process: {
+ hash: {
+ md5: ['24a77cf54ab89f3d0772c65204074710'],
+ sha256: ['cbf3d059cc9f9c0adff5ef15bf331b95ab381837fa0adecd965a41b5846f4bd4'],
+ sha1: ['6cc7c36da55c7af0969539fae73768fbef11aa1a'],
+ },
+ name: ['zoom.us'],
+ parent: {
+ name: ['zoom.us'],
+ pid: [3961],
+ },
+ pid: [4042],
+ entity_id: [
+ 'OTA1ZDkzMTctMjIxOS00ZjQ1LTg4NTMtYzNiYzk1NGU1ZGU4LTQwNDItMTMyNTY2ODI5MjQuNzYxNDAwMA==',
+ ],
+ executable: ['/Applications/zoom.us.app/Contents/MacOS/zoom.us'],
+ args: ['/Applications/zoom.us.app/Contents/MacOS/zoom.us'],
+ },
+ host: {
+ os: {
+ full: ['macOS 10.14.6'],
+ name: ['macOS'],
+ version: ['10.14.6'],
+ family: ['macos'],
+ kernel: [
+ 'Darwin Kernel Version 18.7.0: Mon Aug 31 20:53:32 PDT 2020; root:xnu-4903.278.44~1/RELEASE_X86_64',
+ ],
+ platform: ['macos'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['test-mac.local'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['fce6b9f1-5c09-d8f0-3d99-9ecb30f995df'],
+ },
+ event: {
+ category: ['process'],
+ kind: ['event'],
+ module: ['endpoint'],
+ action: ['fork'],
+ type: ['start'],
+ id: ['LuH/UjERrFf60dea+++++KYC'],
+ dataset: ['endpoint.events.process'],
+ },
+ user: {
+ id: ['501'],
+ name: ['admin'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ message: ['Endpoint process event'],
+ timestamp: '2021-02-01T19:55:24.907Z',
+ _id: 'KXomX3cBGrBB52F2S9XY',
+};
+
export const mockEndgameIpv4ConnectionAcceptEvent: Ecs = {
_id: 'LsjPcG0BOpWiDweSCNfu',
user: {
diff --git a/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts b/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
index cc75518cf2899d..f016b6cc34539c 100644
--- a/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/mock_timeline_data.ts
@@ -1302,3 +1302,186 @@ export const mockDnsEvent: Ecs = {
ip: ['10.9.9.9'],
},
};
+
+export const mockEndpointProcessExecutionMalwarePreventionAlert: Ecs = {
+ process: {
+ hash: {
+ md5: ['177afc1eb0be88eb9983fb74111260c4'],
+ sha256: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb'],
+ sha1: ['f573b85e9beb32121f1949217947b2adc6749e3d'],
+ },
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTY5MjAtMTMyNDg5OTk2OTAuNDgzMzA3NzAw',
+ ],
+ executable: [
+ 'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
+ ],
+ name: [
+ 'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
+ ],
+ pid: [6920],
+ args: [
+ 'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
+ ],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1518)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1518)'],
+ platform: ['windows'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1518)'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ name: ['win2019-endpoint-1'],
+ },
+ file: {
+ mtime: ['2020-11-04T21:40:51.494Z'],
+ path: [
+ 'C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe',
+ ],
+ owner: ['sean'],
+ hash: {
+ md5: ['177afc1eb0be88eb9983fb74111260c4'],
+ sha256: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb'],
+ sha1: ['f573b85e9beb32121f1949217947b2adc6749e3d'],
+ },
+ name: ['3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe'],
+ extension: ['exe'],
+ size: [1604112],
+ },
+ event: {
+ category: ['malware', 'intrusion_detection', 'process'],
+ outcome: ['success'],
+ severity: [73],
+ code: ['malicious_file'],
+ action: ['execution'],
+ id: ['LsuMZVr+sdhvehVM++++Gp2Y'],
+ kind: ['alert'],
+ created: ['2020-11-04T21:41:30.533Z'],
+ module: ['endpoint'],
+ type: ['info', 'start', 'denied'],
+ dataset: ['endpoint.alerts'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ timestamp: '2020-11-04T21:41:30.533Z',
+ message: ['Malware Prevention Alert'],
+ _id: '0dA2lXUBn9bLIbfPkY7d',
+};
+
+export const mockEndpointLibraryLoadEvent: Ecs = {
+ file: {
+ path: ['C:\\Windows\\System32\\bcrypt.dll'],
+ hash: {
+ md5: ['00439016776de367bad087d739a03797'],
+ sha1: ['2c4ba5c1482987d50a182bad915f52cd6611ee63'],
+ sha256: ['e70f5d8f87aab14e3160227d38387889befbe37fa4f8f5adc59eff52804b35fd'],
+ },
+ name: ['bcrypt.dll'],
+ },
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ platform: ['windows'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['win2019-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ },
+ event: {
+ category: ['library'],
+ kind: ['event'],
+ created: ['2021-02-05T21:27:23.921Z'],
+ module: ['endpoint'],
+ action: ['load'],
+ type: ['start'],
+ id: ['LzzWB9jjGmCwGMvk++++Da5H'],
+ dataset: ['endpoint.events.library'],
+ },
+ process: {
+ name: ['sshd.exe'],
+ pid: [9644],
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTk2NDQtMTMyNTcwMzQwNDEuNzgyMTczODAw',
+ ],
+ executable: ['C:\\Program Files\\OpenSSH-Win64\\sshd.exe'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ user: {
+ name: ['SYSTEM'],
+ domain: ['NT AUTHORITY'],
+ },
+ message: ['Endpoint DLL load event'],
+ timestamp: '2021-02-05T21:27:23.921Z',
+ _id: 'IAUYdHcBGrBB52F2zo8Q',
+};
+
+export const mockEndpointRegistryModificationEvent: Ecs = {
+ host: {
+ os: {
+ full: ['Windows Server 2019 Datacenter 1809 (10.0.17763.1697)'],
+ name: ['Windows'],
+ version: ['1809 (10.0.17763.1697)'],
+ family: ['windows'],
+ kernel: ['1809 (10.0.17763.1697)'],
+ platform: ['windows'],
+ },
+ mac: ['aa:bb:cc:dd:ee:ff'],
+ name: ['win2019-endpoint-1'],
+ architecture: ['x86_64'],
+ ip: ['10.1.2.3'],
+ id: ['d8ad572e-d224-4044-a57d-f5a84c0dfe5d'],
+ },
+ event: {
+ category: ['registry'],
+ kind: ['event'],
+ created: ['2021-02-04T13:44:31.559Z'],
+ module: ['endpoint'],
+ action: ['modification'],
+ type: ['change'],
+ id: ['LzzWB9jjGmCwGMvk++++CbOn'],
+ dataset: ['endpoint.events.registry'],
+ },
+ process: {
+ name: ['GoogleUpdate.exe'],
+ pid: [7408],
+ entity_id: [
+ 'MWQxNWNmOWUtM2RjNy01Yjk3LWY1ODYtNzQzZjdjMjUxOGIyLTc0MDgtMTMyNTY5MTk4NDguODY4NTI0ODAw',
+ ],
+ executable: ['C:\\Program Files (x86)\\Google\\Update\\GoogleUpdate.exe'],
+ },
+ registry: {
+ hive: ['HKLM'],
+ key: [
+ 'SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState',
+ ],
+ path: [
+ 'HKLM\\SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState\\StateValue',
+ ],
+ value: ['StateValue'],
+ },
+ agent: {
+ type: ['endpoint'],
+ },
+ user: {
+ name: ['SYSTEM'],
+ domain: ['NT AUTHORITY'],
+ },
+ message: ['Endpoint registry event'],
+ timestamp: '2021-02-04T13:44:31.559Z',
+ _id: '4cxLbXcBGrBB52F2uOfF',
+};
diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
index 351caa2df3e317..70ed497ce0caca 100644
--- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
@@ -2106,6 +2106,11 @@ export const mockTimelineModel: TimelineModel = {
},
deletedEventIds: [],
description: 'This is a sample rule description',
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
@@ -2229,6 +2234,13 @@ export const defaultTimelineProps: CreateTimelineProps = {
dateRange: { end: '2018-11-05T19:03:25.937Z', start: '2018-11-05T18:58:25.937Z' },
deletedEventIds: [],
description: '',
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ query: '',
+ size: 100,
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts
index 3835e708514252..fbf4caad9793dc 100644
--- a/x-pack/plugins/security_solution/public/common/store/types.ts
+++ b/x-pack/plugins/security_solution/public/common/store/types.ts
@@ -37,7 +37,7 @@ export type StoreState = HostsPluginState &
*/
export type State = CombinedState;
-export type KueryFilterQueryKind = 'kuery' | 'lucene';
+export type KueryFilterQueryKind = 'kuery' | 'lucene' | 'eql';
export interface KueryFilterQuery {
kind: KueryFilterQueryKind;
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
index 3c3d79c0c518fa..143c39daace66a 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
@@ -153,6 +153,13 @@ describe('alert actions', () => {
},
deletedEventIds: [],
description: 'This is a sample rule description',
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ query: '',
+ size: 100,
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.test.tsx
new file mode 100644
index 00000000000000..66b2bae98c1ae4
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.test.tsx
@@ -0,0 +1,195 @@
+/*
+ * 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 { mount } from 'enzyme';
+import React from 'react';
+import { NeedAdminForUpdateRulesCallOut } from './index';
+import { TestProviders } from '../../../../common/mock';
+import * as userInfo from '../../user_info';
+
+describe('need_admin_for_update_callout', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('hasIndexManage is "null"', () => {
+ const hasIndexManage = null;
+ test('Does NOT render when "signalIndexMappingOutdated" is true', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(
+ jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true, hasIndexManage }])
+ );
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does not render a button as this is always persistent', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(false);
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is false', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: false }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is null', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: null }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+ });
+
+ describe('hasIndexManage is "false"', () => {
+ const hasIndexManage = false;
+ test('renders when "signalIndexMappingOutdated" is true', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(
+ jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true, hasIndexManage }])
+ );
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ true
+ );
+ });
+
+ test('Does not render a button as this is always persistent', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(false);
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is false', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: false }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is null', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: null }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+ });
+
+ describe('hasIndexManage is "true"', () => {
+ const hasIndexManage = true;
+ test('Does not render when "signalIndexMappingOutdated" is true', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(
+ jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true, hasIndexManage }])
+ );
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does not render a button as this is always persistent', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: true }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-dismiss-btn"]')).toEqual(false);
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is false', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: false }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+
+ test('Does NOT render when signalIndexMappingOutdated is null', () => {
+ jest
+ .spyOn(userInfo, 'useUserData')
+ .mockImplementation(jest.fn().mockReturnValue([{ signalIndexMappingOutdated: null }]));
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.exists('[data-test-subj="callout-need-admin-for-update-rules"]')).toEqual(
+ false
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx
new file mode 100644
index 00000000000000..fd0be8e0021933
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/index.tsx
@@ -0,0 +1,52 @@
+/*
+ * 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 React, { memo } from 'react';
+import { CallOutMessage, CallOutPersistentSwitcher } from '../../../../common/components/callouts';
+import { useUserData } from '../../user_info';
+
+import * as i18n from './translations';
+
+const needAdminForUpdateRulesMessage: CallOutMessage = {
+ type: 'primary',
+ id: 'need-admin-for-update-rules',
+ title: i18n.NEED_ADMIN_CALLOUT_TITLE,
+ description: i18n.needAdminForUpdateCallOutBody(),
+};
+
+/**
+ * Callout component that lets the user know that an administrator is needed for performing
+ * and auto-update of signals or not. For this component to render the user must:
+ * - Have the permissions to be able to read "signalIndexMappingOutdated" and that condition is "true"
+ * - Have the permissions to be able to read "hasIndexManage" and that condition is "false"
+ *
+ * Some users do not have sufficient privileges to be able to determine if "signalIndexMappingOutdated"
+ * is outdated or not. Same could apply to "hasIndexManage". When users do not have enough permissions
+ * to determine if "signalIndexMappingOutdated" is true or false, the permissions system returns a "null"
+ * instead.
+ *
+ * If the user has the permissions to see that signalIndexMappingOutdated is true and that
+ * hasIndexManage is also true, then the user should be performing the update on the page which is
+ * why we do not show it for that condition.
+ */
+const NeedAdminForUpdateCallOutComponent = (): JSX.Element => {
+ const [{ signalIndexMappingOutdated, hasIndexManage }] = useUserData();
+
+ const signalIndexMappingIsOutdated =
+ signalIndexMappingOutdated != null && signalIndexMappingOutdated;
+
+ const userDoesntHaveIndexManage = hasIndexManage != null && !hasIndexManage;
+
+ return (
+
+ );
+};
+
+export const NeedAdminForUpdateRulesCallOut = memo(NeedAdminForUpdateCallOutComponent);
diff --git a/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/translations.tsx b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/translations.tsx
new file mode 100644
index 00000000000000..791093788b8e1e
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/detections/components/callouts/need_admin_for_update_callout/translations.tsx
@@ -0,0 +1,52 @@
+/*
+ * 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 React from 'react';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+import {
+ SecuritySolutionRequirementsLink,
+ DetectionsRequirementsLink,
+} from '../../../../common/components/links_to_docs';
+
+export const NEED_ADMIN_CALLOUT_TITLE = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.needAdminForUpdateCallOutBody.messageTitle',
+ {
+ defaultMessage: 'Administration permissions required for alert migration',
+ }
+);
+
+/**
+ * Returns the formatted message of the call out body as a JSX Element with both the message
+ * and two documentation links.
+ */
+export const needAdminForUpdateCallOutBody = (): JSX.Element => (
+
+
+
+ ),
+ docs: (
+
+ ),
+ }}
+ />
+);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx
index df09fc1d12e58e..e37fd2eb26ff9b 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/eql_query_bar.tsx
@@ -15,6 +15,11 @@ import { DefineStepRule } from '../../../pages/detection_engine/rules/types';
import * as i18n from './translations';
import { EqlQueryBarFooter } from './footer';
import { getValidationResults } from './validators';
+import {
+ EqlOptionsData,
+ EqlOptionsSelected,
+ FieldsEqlOptions,
+} from '../../../../../common/search_strategy';
const TextArea = styled(EuiTextArea)`
display: block;
@@ -28,14 +33,22 @@ export interface EqlQueryBarProps {
dataTestSubj: string;
field: FieldHook;
idAria?: string;
+ optionsData?: EqlOptionsData;
+ optionsSelected?: EqlOptionsSelected;
+ onOptionsChange?: (field: FieldsEqlOptions, newValue: string | null) => void;
onValidityChange?: (arg: boolean) => void;
+ onValiditingChange?: (arg: boolean) => void;
}
export const EqlQueryBar: FC = ({
dataTestSubj,
field,
idAria,
+ optionsData,
+ optionsSelected,
+ onOptionsChange,
onValidityChange,
+ onValiditingChange,
}) => {
const { addError } = useAppToasts();
const [errorMessages, setErrorMessages] = useState([]);
@@ -62,10 +75,18 @@ export const EqlQueryBar: FC = ({
}
}, [error, addError]);
+ useEffect(() => {
+ if (onValiditingChange) {
+ onValiditingChange(isValidating);
+ }
+ }, [isValidating, onValiditingChange]);
+
const handleChange = useCallback(
(e: ChangeEvent) => {
const newQuery = e.target.value;
-
+ if (onValiditingChange) {
+ onValiditingChange(true);
+ }
setErrorMessages([]);
setValue({
filters: [],
@@ -75,7 +96,7 @@ export const EqlQueryBar: FC = ({
},
});
},
- [setValue]
+ [setValue, onValiditingChange]
);
return (
@@ -84,7 +105,7 @@ export const EqlQueryBar: FC = ({
labelAppend={field.labelAppend}
helpText={field.helpText}
error={message}
- isInvalid={!isValid}
+ isInvalid={!isValid && !isValidating}
fullWidth
data-test-subj={dataTestSubj}
describedByIds={idAria ? [idAria] : undefined}
@@ -93,11 +114,17 @@ export const EqlQueryBar: FC = ({
-
+
>
);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/footer.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/footer.tsx
index 232f92e66c3873..b2b5badac21272 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/footer.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/footer.tsx
@@ -5,10 +5,28 @@
* 2.0.
*/
-import React, { FC } from 'react';
+import {
+ EuiButtonIcon,
+ EuiComboBoxOptionOption,
+ EuiComboBox,
+ EuiFieldNumber,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFormRow,
+ EuiLoadingSpinner,
+ EuiPanel,
+ EuiPopover,
+ EuiPopoverTitle,
+} from '@elastic/eui';
+import React, { FC, useCallback, useMemo, useRef, useState } from 'react';
import styled from 'styled-components';
-import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiPanel } from '@elastic/eui';
+import { Cancelable, debounce } from 'lodash';
+import {
+ EqlOptionsData,
+ EqlOptionsSelected,
+ FieldsEqlOptions,
+} from '../../../../../common/search_strategy';
import * as i18n from './translations';
import { ErrorsPopover } from './errors_popover';
import { EqlOverviewLink } from './eql_overview_link';
@@ -16,8 +34,13 @@ import { EqlOverviewLink } from './eql_overview_link';
export interface Props {
errors: string[];
isLoading?: boolean;
+ optionsData?: EqlOptionsData;
+ optionsSelected?: EqlOptionsSelected;
+ onOptionsChange?: (field: FieldsEqlOptions, newValue: string | null) => void;
}
+type SizeVoidFunc = (newSize: string) => void;
+
const Container = styled(EuiPanel)`
border-radius: 0;
background: ${({ theme }) => theme.eui.euiPageBackgroundColor};
@@ -28,22 +51,189 @@ const FlexGroup = styled(EuiFlexGroup)`
min-height: ${({ theme }) => theme.eui.euiSizeXL};
`;
+const FlexItemLeftBorder = styled(EuiFlexItem)`
+ border-left: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
+`;
+
+const FlexItemWithMarginRight = styled(EuiFlexItem)`
+ margin-right: ${({ theme }) => theme.eui.euiSizeS};
+`;
+
const Spinner = styled(EuiLoadingSpinner)`
margin: 0 ${({ theme }) => theme.eui.euiSizeS};
`;
-export const EqlQueryBarFooter: FC = ({ errors, isLoading }) => (
-
-
-
- {errors.length > 0 && (
-
+const singleSelection = { asPlainText: true };
+
+export const EqlQueryBarFooter: FC = ({
+ errors,
+ isLoading,
+ optionsData,
+ optionsSelected,
+ onOptionsChange,
+}) => {
+ const [openEqlSettings, setIsOpenEqlSettings] = useState(false);
+ const [localSize, setLocalSize] = useState(optionsSelected?.size ?? 100);
+ const debounceSize = useRef();
+ const openEqlSettingsHandler = useCallback(() => setIsOpenEqlSettings(true), []);
+ const closeEqlSettingsHandler = useCallback(() => setIsOpenEqlSettings(false), []);
+ const handleEventCategoryField = useCallback(
+ (opt: EuiComboBoxOptionOption[]) => {
+ if (onOptionsChange) {
+ if (opt.length > 0) {
+ onOptionsChange('eventCategoryField', opt[0].label);
+ } else {
+ onOptionsChange('eventCategoryField', null);
+ }
+ }
+ },
+ [onOptionsChange]
+ );
+ const handleTiebreakerField = useCallback(
+ (opt: EuiComboBoxOptionOption[]) => {
+ if (onOptionsChange) {
+ if (opt.length > 0) {
+ onOptionsChange('tiebreakerField', opt[0].label);
+ } else {
+ onOptionsChange('tiebreakerField', null);
+ }
+ }
+ },
+ [onOptionsChange]
+ );
+ const handleTimestampField = useCallback(
+ (opt: EuiComboBoxOptionOption[]) => {
+ if (onOptionsChange) {
+ if (opt.length > 0) {
+ onOptionsChange('timestampField', opt[0].label);
+ } else {
+ onOptionsChange('timestampField', null);
+ }
+ }
+ },
+ [onOptionsChange]
+ );
+ const handleSizeField = useCallback(
+ (evt) => {
+ if (onOptionsChange) {
+ setLocalSize(evt?.target?.value);
+ if (debounceSize.current?.cancel) {
+ debounceSize.current?.cancel();
+ }
+ debounceSize.current = debounce((newSize) => onOptionsChange('size', newSize), 800);
+ debounceSize.current(evt?.target?.value);
+ }
+ },
+ [onOptionsChange]
+ );
+
+ const eventCategoryField = useMemo(
+ () =>
+ optionsSelected?.eventCategoryField != null
+ ? [{ label: optionsSelected?.eventCategoryField }]
+ : undefined,
+ [optionsSelected?.eventCategoryField]
+ );
+ const tiebreakerField = useMemo(
+ () =>
+ optionsSelected?.tiebreakerField != null
+ ? [{ label: optionsSelected?.tiebreakerField }]
+ : undefined,
+ [optionsSelected?.tiebreakerField]
+ );
+ const timestampField = useMemo(
+ () =>
+ optionsSelected?.timestampField != null
+ ? [{ label: optionsSelected?.timestampField }]
+ : undefined,
+ [optionsSelected?.timestampField]
+ );
+
+ return (
+
+
+
+ {errors.length > 0 && (
+
+ )}
+ {isLoading && }
+
+ {!onOptionsChange && (
+
+
+
+ )}
+ {onOptionsChange && (
+ <>
+
+
+
+
+
+ }
+ isOpen={openEqlSettings}
+ closePopover={closeEqlSettingsHandler}
+ anchorPosition="downCenter"
+ ownFocus={true}
+ >
+ {i18n.EQL_SETTINGS_TITLE}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
)}
- {isLoading && }
-
-
-
-
-
-
-);
+
+
+ );
+};
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/translations.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/translations.ts
index 5faae3cf236942..463d326b4a74ac 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/translations.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/translations.ts
@@ -41,3 +41,69 @@ export const EQL_OVERVIEW_LINK_TEXT = i18n.translate(
defaultMessage: 'Event Query Language (EQL) Overview',
}
);
+
+export const EQL_SETTINGS_TITLE = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlSettings.title',
+ {
+ defaultMessage: 'EQL settings',
+ }
+);
+
+export const EQL_OPTIONS_SIZE_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOptionsSize.label',
+ {
+ defaultMessage: 'Size',
+ }
+);
+
+export const EQL_OPTIONS_SIZE_HELPER = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOptionsSize.text',
+ {
+ defaultMessage:
+ 'For basic queries, the maximum number of matching events to return. For sequence queries, the maximum number of matching sequences to return.',
+ }
+);
+
+export const EQL_OPTIONS_EVENT_CATEGORY_FIELD_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOptionsEventCategoryField.label',
+ {
+ defaultMessage: 'Event category field',
+ }
+);
+
+export const EQL_OPTIONS_EVENT_CATEGORY_FIELD_HELPER = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOptionsEventCategoryField.text',
+ {
+ defaultMessage:
+ 'Field containing the event classification, such as process, file, or network. This field is typically mapped as a field type in the keyword family',
+ }
+);
+
+export const EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOptionsEventTiebreakerField.label',
+ {
+ defaultMessage: 'Tiebreaker field',
+ }
+);
+
+export const EQL_OPTIONS_EVENT_TIEBREAKER_FIELD_HELPER = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOptionsEventTiebreakerField.text',
+ {
+ defaultMessage:
+ 'Field used to sort hits with the same timestamp in ascending, lexicographic order',
+ }
+);
+
+export const EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_LABEL = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOptionsEventTimestampField.label',
+ {
+ defaultMessage: 'Timestamp field',
+ }
+);
+
+export const EQL_OPTIONS_EVENT_TIMESTAMP_FIELD_HELPER = i18n.translate(
+ 'xpack.securitySolution.detectionEngine.eqlOptionsEventTimestampField.text',
+ {
+ defaultMessage: 'Field containing event timestamp',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts
index 2fe18d04478fc2..249d26e9918948 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/eql_query_bar/validators.ts
@@ -59,7 +59,8 @@ export const eqlValidator = async (
const query = queryValue.query as string;
const { index, ruleType } = formData as DefineStepRule;
- const needsValidation = isEqlRule(ruleType) && !isEmpty(query);
+ const needsValidation =
+ (ruleType === undefined && !isEmpty(query)) || (isEqlRule(ruleType) && !isEmpty(query));
if (!needsValidation) {
return;
}
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts
index 4ed971ea6a936e..cca745659d2ccf 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts
@@ -14,6 +14,9 @@ export const getStatusColor = (status: RuleStatusType | string | null) =>
? 'success'
: status === 'failed'
? 'danger'
- : status === 'executing' || status === 'going to run' || status === 'partial failure'
+ : status === 'executing' ||
+ status === 'going to run' ||
+ status === 'partial failure' ||
+ status === 'warning'
? 'warning'
: 'subdued';
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx
index 6292cc5b530b04..677e6de0ff485d 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/index.tsx
@@ -16,7 +16,11 @@ import {
import React, { memo, useCallback, useEffect, useState } from 'react';
import deepEqual from 'fast-deep-equal';
-import { useRuleStatus, RuleInfoStatus } from '../../../containers/detection_engine/rules';
+import {
+ useRuleStatus,
+ RuleInfoStatus,
+ RuleStatusType,
+} from '../../../containers/detection_engine/rules';
import { FormattedDate } from '../../../../common/components/formatted_date';
import { getEmptyTagValue } from '../../../../common/components/empty_value';
import { getStatusColor } from './helpers';
@@ -55,6 +59,19 @@ const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled })
}
}, [fetchRuleStatus, ruleId]);
+ const getStatus = useCallback((status: RuleStatusType | null | undefined) => {
+ if (status == null) {
+ return getEmptyTagValue();
+ } else if (status != null && status === 'partial failure') {
+ // Temporary fix if on upgrade a rule has a status of 'partial failure' we want to display that text as 'warning'
+ // On the next subsequent rule run, that 'partial failure' status will be re-written as a 'warning' status
+ // and this code will no longer be necessary
+ // TODO: remove this code in 8.0.0
+ return 'warning';
+ }
+ return status;
+ }, []);
+
return (
@@ -71,7 +88,7 @@ const RuleStatusComponent: React.FC = ({ ruleId, ruleEnabled })
- {currentStatus?.status ?? getEmptyTagValue()}
+ {getStatus(currentStatus?.status)}
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts
index 08feb5f2e51660..f73b2ccfb02ae6 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/default_value.ts
@@ -29,6 +29,7 @@ export const stepAboutDefaultValue: AboutStepRule = {
license: '',
ruleNameOverride: '',
tags: [],
+ threatIndicatorPath: '',
timestampOverride: '',
threat: threatDefault,
note: '',
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
index 209071d27536d5..25295a823ea66e 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
@@ -40,6 +40,7 @@ import { SeverityField } from '../severity_mapping';
import { RiskScoreField } from '../risk_score_mapping';
import { AutocompleteField } from '../autocomplete_field';
import { useFetchIndex } from '../../../../common/containers/source';
+import { isThreatMatchRule } from '../../../../../common/detection_engine/utils';
const CommonUseField = getUseField({ component: Field });
@@ -298,6 +299,23 @@ const StepAboutRuleComponent: FC = ({
/>
+ {isThreatMatchRule(defineRuleData?.ruleType) && (
+ <>
+
+ >
+ )}
+
= {
),
labelAppend: OptionalFieldLabel,
},
+ threatIndicatorPath: {
+ type: FIELD_TYPES.TEXT,
+ label: i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathLabel',
+ {
+ defaultMessage: 'Threat Indicator Path',
+ }
+ ),
+ helpText: i18n.translate(
+ 'xpack.securitySolution.detectionEngine.createRule.stepAboutRule.fieldThreatIndicatorPathHelpText',
+ {
+ defaultMessage:
+ 'Specify the document path containing your threat indicator fields. Used for enrichment of indicator match alerts. Defaults to threat.indicator unless otherwise specified.',
+ }
+ ),
+ labelAppend: OptionalFieldLabel,
+ },
timestampOverride: {
type: FIELD_TYPES.TEXT,
label: i18n.translate(
diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx
index adc46f08272d7e..aefa447269f46c 100644
--- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/modal.tsx
@@ -15,7 +15,6 @@ import {
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
- EuiOverlayMask,
EuiPanel,
EuiSpacer,
EuiText,
@@ -211,7 +210,7 @@ export const ValueListsModalComponent: React.FC = ({
const columns = buildColumns(handleExport, handleDelete);
return (
-
+ <>
{i18n.MODAL_TITLE}
@@ -255,7 +254,7 @@ export const ValueListsModalComponent: React.FC = ({
name={exportDownload.name}
onDownload={() => setExportDownload({})}
/>
-
+ >
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx
index 20744c3a22515f..e4d8e2cee32685 100644
--- a/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/value_lists_management_modal/reference_error_modal/reference_error_modal.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { EuiConfirmModal, EuiListGroup, EuiListGroupItem, EuiOverlayMask } from '@elastic/eui';
+import { EuiConfirmModal, EuiListGroup, EuiListGroupItem } from '@elastic/eui';
import styled from 'styled-components';
import { rgba } from 'polished';
@@ -59,28 +59,26 @@ export const ReferenceErrorModalComponent: React.FC =
}
return (
-
-
- {contentText}
-
-
- {references.map((r, index) => (
-
- ))}
-
-
-
-
+
+ {contentText}
+
+
+ {references.map((r, index) => (
+
+ ))}
+
+
+
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
index 591432829d90a0..b8f6c4bde3e8f7 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts
@@ -24,6 +24,7 @@ import {
listArray,
threat_query,
threat_index,
+ threat_indicator_path,
threat_mapping,
threat_language,
threat_filters,
@@ -77,6 +78,7 @@ const StatusTypes = t.union([
t.literal('failed'),
t.literal('going to run'),
t.literal('partial failure'),
+ t.literal('warning'),
]);
// TODO: make a ticket
@@ -132,6 +134,7 @@ export const RuleSchema = t.intersection([
threat_query,
threat_filters,
threat_index,
+ threat_indicator_path,
threat_mapping,
threat_language,
timeline_id: t.string,
@@ -252,7 +255,13 @@ export interface RuleStatus {
failures: RuleInfoStatus[];
}
-export type RuleStatusType = 'executing' | 'failed' | 'going to run' | 'succeeded';
+export type RuleStatusType =
+ | 'executing'
+ | 'failed'
+ | 'going to run'
+ | 'succeeded'
+ | 'partial failure'
+ | 'warning';
export interface RuleInfoStatus {
alert_id: string;
status_date: string;
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
index 0b3511ffe7c876..8d2f07e19b36af 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
@@ -53,6 +53,7 @@ import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import { buildShowBuildingBlockFilter } from '../../components/alerts_table/default_config';
import { useSourcererScope } from '../../../common/containers/sourcerer';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
+import { NeedAdminForUpdateRulesCallOut } from '../../components/callouts/need_admin_for_update_callout';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
@@ -193,6 +194,7 @@ const DetectionEnginePageComponent = () => {
<>
{hasEncryptionKey != null && !hasEncryptionKey && }
+
{indicesExist ? (
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
index 425848cd09af0d..d110f2d52b3c5e 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx
@@ -313,7 +313,11 @@ export const getMonitoringColumns = (
}}
href={formatUrl(getRuleDetailsUrl(item.id))}
>
- {value}
+ {/* Temporary fix if on upgrade a rule has a status of 'partial failure' we want to display that text as 'warning' */}
+ {/* On the next subsequent rule run, that 'partial failure' status will be re-written as a 'warning' status */}
+ {/* and this code will no longer be necessary */}
+ {/* TODO: remove this code in 8.0.0 */}
+ {value === 'partial failure' ? 'warning' : value}
);
},
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
index 89785efbb5047a..04bf3c544030a4 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/rules_tables.tsx
@@ -9,7 +9,6 @@ import {
EuiBasicTable,
EuiLoadingContent,
EuiProgress,
- EuiOverlayMask,
EuiConfirmModal,
EuiWindowEvent,
} from '@elastic/eui';
@@ -490,18 +489,16 @@ export const RulesTables = React.memo(
)}
{showIdleModal && (
-
-
- {i18n.REFRESH_PROMPT_BODY}
-
-
+
+ {i18n.REFRESH_PROMPT_BODY}
+
)}
{shouldShowRulesTable && (
<>
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
index 5e2aeb4ead9340..fdb0513d7b7082 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts
@@ -493,6 +493,15 @@ describe('helpers', () => {
expect(result.exceptions_list).toEqual([getListMock()]);
});
+ test('returns a threat indicator path', () => {
+ mockData = {
+ ...mockData,
+ threatIndicatorPath: 'my_custom.path',
+ };
+ const result = formatAboutStepData(mockData);
+ expect(result.threat_indicator_path).toEqual('my_custom.path');
+ });
+
test('returns formatted object with both exceptions_lists', () => {
const result = formatAboutStepData(
{
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
index c09f85ce7edccc..7c447214cfdebb 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts
@@ -288,6 +288,7 @@ export const formatAboutStepData = (
isBuildingBlock,
note,
ruleNameOverride,
+ threatIndicatorPath,
timestampOverride,
...rest
} = aboutStepData;
@@ -330,6 +331,7 @@ export const formatAboutStepData = (
...singleThreat,
framework: 'MITRE ATT&CK',
})),
+ threat_indicator_path: threatIndicatorPath,
timestamp_override: timestampOverride !== '' ? timestampOverride : undefined,
...(!isEmpty(note) ? { note } : {}),
...rest,
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
index 5836cac09e9b81..c4dc9b62c74cd5 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
@@ -103,6 +103,7 @@ import * as detectionI18n from '../../translations';
import * as ruleI18n from '../translations';
import * as i18n from './translations';
import { isTab } from '../../../../../common/components/accessibility/helpers';
+import { NeedAdminForUpdateRulesCallOut } from '../../../../components/callouts/need_admin_for_update_callout';
/**
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
@@ -314,7 +315,7 @@ const RuleDetailsPageComponent = () => {
/>
);
} else if (
- rule?.status === 'partial failure' &&
+ (rule?.status === 'warning' || rule?.status === 'partial failure') &&
ruleDetailTab === RuleDetailTabs.alerts &&
rule?.last_success_at != null
) {
@@ -468,6 +469,7 @@ const RuleDetailsPageComponent = () => {
return (
<>
+
{indicesExist ? (
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts
index 4e6d8f4d567b18..1d100fb9109d07 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts
@@ -52,7 +52,7 @@ export const ERROR_CALLOUT_TITLE = i18n.translate(
export const PARTIAL_FAILURE_CALLOUT_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.ruleDetails.partialErrorCalloutTitle',
{
- defaultMessage: 'Partial rule failure at',
+ defaultMessage: 'Warning at',
}
);
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx
index f0511602bd67f7..111eb8a5594a8d 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.test.tsx
@@ -116,6 +116,7 @@ describe('rule helpers', () => {
severity: { value: 'low', mapping: fillEmptySeverityMappings([]), isMappingChecked: false },
tags: ['tag1', 'tag2'],
threat: getThreatMock(),
+ threatIndicatorPath: '',
timestampOverride: 'event.ingested',
};
const scheduleRuleStepData = { from: '0s', interval: '5m' };
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx
index 35f9f0c658a6af..d37c2d9141f5d4 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/helpers.tsx
@@ -153,6 +153,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu
risk_score: riskScore,
tags,
threat,
+ threat_indicator_path: threatIndicatorPath,
} = rule;
return {
@@ -179,6 +180,7 @@ export const getAboutStepsData = (rule: Rule, detailsView: boolean): AboutStepRu
},
falsePositives,
threat: threat as Threats,
+ threatIndicatorPath: threatIndicatorPath ?? '',
};
};
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
index fee7f443e95a04..89cec168510103 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
@@ -35,6 +35,7 @@ import * as i18n from './translations';
import { SecurityPageName } from '../../../../app/types';
import { LinkButton } from '../../../../common/components/links';
import { useFormatUrl } from '../../../../common/components/link_to';
+import { NeedAdminForUpdateRulesCallOut } from '../../../components/callouts/need_admin_for_update_callout';
type Func = () => Promise;
@@ -158,6 +159,7 @@ const RulesPageComponent: React.FC = () => {
return (
<>
+
;
+ eqlOptions?: Maybe;
+
eventType?: Maybe;
excludedRowRendererIds?: Maybe;
@@ -163,6 +165,18 @@ export interface QueryMatchInput {
operator?: Maybe;
}
+export interface EqlOptionsInput {
+ eventCategoryField?: Maybe;
+
+ tiebreakerField?: Maybe;
+
+ timestampField?: Maybe;
+
+ query?: Maybe;
+
+ size?: Maybe;
+}
+
export interface FilterTimelineInput {
exists?: Maybe;
@@ -273,6 +287,7 @@ export enum HostPolicyResponseActionStatus {
success = 'success',
failure = 'failure',
warning = 'warning',
+ unsupported = 'unsupported',
}
export enum TimelineType {
@@ -286,10 +301,13 @@ export enum DataProviderType {
}
export enum RowRendererId {
+ alerts = 'alerts',
auditd = 'auditd',
auditd_file = 'auditd_file',
+ library = 'library',
netflow = 'netflow',
plain = 'plain',
+ registry = 'registry',
suricata = 'suricata',
system = 'system',
system_dns = 'system_dns',
@@ -605,6 +623,8 @@ export interface TimelineResult {
description?: Maybe;
+ eqlOptions?: Maybe;
+
eventIdToNoteIds?: Maybe;
eventType?: Maybe;
@@ -712,6 +732,18 @@ export interface DateRangePickerResult {
end?: Maybe;
}
+export interface EqlOptionsResult {
+ eventCategoryField?: Maybe;
+
+ tiebreakerField?: Maybe;
+
+ timestampField?: Maybe;
+
+ query?: Maybe;
+
+ size?: Maybe;
+}
+
export interface FavoriteTimelineResult {
fullName?: Maybe;
@@ -2220,6 +2252,8 @@ export namespace GetOneTimeline {
description: Maybe;
+ eqlOptions: Maybe;
+
eventType: Maybe;
eventIdToNoteIds: Maybe;
@@ -2367,6 +2401,20 @@ export namespace GetOneTimeline {
end: Maybe;
};
+ export type EqlOptions = {
+ __typename?: 'EqlOptionsResult';
+
+ eventCategoryField: Maybe;
+
+ tiebreakerField: Maybe;
+
+ timestampField: Maybe;
+
+ query: Maybe;
+
+ size: Maybe;
+ };
+
export type EventIdToNoteIds = {
__typename?: 'NoteResult';
diff --git a/x-pack/plugins/security_solution/public/helpers.ts b/x-pack/plugins/security_solution/public/helpers.ts
index b541cacf25cd1e..e12b2f7fc37f81 100644
--- a/x-pack/plugins/security_solution/public/helpers.ts
+++ b/x-pack/plugins/security_solution/public/helpers.ts
@@ -13,6 +13,7 @@ import {
FactoryQueryTypes,
StrategyResponseType,
} from '../common/search_strategy/security_solution';
+import { TimelineEqlResponse } from '../common/search_strategy/timeline';
import { SecurityPageName } from './app/types';
import { InspectResponse } from './types';
@@ -107,7 +108,7 @@ export const manageOldSiemRoutes = async (coreStart: CoreStart) => {
};
export const getInspectResponse = (
- response: StrategyResponseType,
+ response: StrategyResponseType | TimelineEqlResponse,
prevResponse: InspectResponse
): InspectResponse => ({
dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [],
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
index 0998809425665a..4547ae3b342437 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/reducer.ts
@@ -43,6 +43,7 @@ export const initialEndpointListState: Immutable = {
endpointsTotalError: undefined,
queryStrategyVersion: undefined,
policyVersionInfo: undefined,
+ hostStatus: undefined,
};
/* eslint-disable-next-line complexity */
@@ -109,6 +110,7 @@ export const endpointListReducer: ImmutableReducer = (
...state,
details: action.payload.metadata,
policyVersionInfo: action.payload.policy_info,
+ hostStatus: action.payload.host_status,
detailsLoading: false,
detailsError: undefined,
};
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
index 313ef3ed403b22..17ce24e7cda7ff 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/store/selectors.ts
@@ -16,6 +16,7 @@ import {
HostPolicyResponseConfiguration,
HostPolicyResponseActionStatus,
MetadataQueryStrategyVersions,
+ HostStatus,
} from '../../../../../common/endpoint/types';
import { EndpointState, EndpointIndexUIQueryParams } from '../types';
import { extractListPaginationParams } from '../../../common/routing';
@@ -224,6 +225,16 @@ export const showView: (state: EndpointState) => 'policy_response' | 'details' =
}
);
+/**
+ * Returns the Host Status which is connected the fleet agent
+ */
+export const hostStatusInfo: (state: Immutable) => HostStatus = createSelector(
+ (state) => state.hostStatus,
+ (hostStatus) => {
+ return hostStatus ? hostStatus : HostStatus.ERROR;
+ }
+);
+
/**
* Returns the Policy Response overall status
*/
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
index 104c17d332bd34..7e989276edeb6a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/types.ts
@@ -13,6 +13,7 @@ import {
AppLocation,
PolicyData,
MetadataQueryStrategyVersions,
+ HostStatus,
} from '../../../../common/endpoint/types';
import { ServerApiError } from '../../../common/types';
import { GetPackagesResponse } from '../../../../../fleet/common';
@@ -79,6 +80,9 @@ export interface EndpointState {
queryStrategyVersion?: MetadataQueryStrategyVersions;
/** The policy IDs and revision number of the corresponding agent, and endpoint. May be more recent than what's running */
policyVersionInfo?: HostInfo['policy_info'];
+ /** The status of the host, which is mapped to the Elastic Agent status in Fleet
+ */
+ hostStatus?: HostStatus;
}
/**
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
index ce16391206ec12..eb3e534ba427f5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/endpoint_details.tsx
@@ -10,23 +10,27 @@ import {
EuiDescriptionList,
EuiHealth,
EuiHorizontalRule,
- EuiLink,
EuiListGroup,
EuiListGroupItem,
EuiIcon,
EuiText,
EuiFlexGroup,
EuiFlexItem,
+ EuiBadge,
} from '@elastic/eui';
import React, { memo, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { isPolicyOutOfDate } from '../../utils';
-import { HostInfo, HostMetadata } from '../../../../../../common/endpoint/types';
+import { HostInfo, HostMetadata, HostStatus } from '../../../../../../common/endpoint/types';
import { useEndpointSelector, useAgentDetailsIngestUrl } from '../hooks';
import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { policyResponseStatus, uiQueryParams } from '../../store/selectors';
-import { POLICY_STATUS_TO_HEALTH_COLOR } from '../host_constants';
+import {
+ POLICY_STATUS_TO_HEALTH_COLOR,
+ POLICY_STATUS_TO_BADGE_COLOR,
+ HOST_STATUS_TO_HEALTH_COLOR,
+} from '../host_constants';
import { FormattedDateAndTime } from '../../../../../common/components/endpoint/formatted_date_time';
import { useNavigateByRouterEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_by_router_event_handler';
import { LinkToApp } from '../../../../../common/components/endpoint/link_to_app';
@@ -48,6 +52,7 @@ const LinkToExternalApp = styled.div`
margin-top: ${(props) => props.theme.eui.ruleMargins.marginMedium};
.linkToAppIcon {
margin-right: ${(props) => props.theme.eui.ruleMargins.marginXSmall};
+ vertical-align: top;
}
.linkToAppPopoutIcon {
margin-left: ${(props) => props.theme.eui.ruleMargins.marginXSmall};
@@ -57,7 +62,15 @@ const LinkToExternalApp = styled.div`
const openReassignFlyoutSearch = '?openReassignFlyout=true';
export const EndpointDetails = memo(
- ({ details, policyInfo }: { details: HostMetadata; policyInfo?: HostInfo['policy_info'] }) => {
+ ({
+ details,
+ policyInfo,
+ hostStatus,
+ }: {
+ details: HostMetadata;
+ policyInfo?: HostInfo['policy_info'];
+ hostStatus: HostStatus;
+ }) => {
const agentId = details.elastic.agent.id;
const {
url: agentDetailsUrl,
@@ -78,6 +91,25 @@ export const EndpointDetails = memo(
}),
description: details.host.os.full,
},
+ {
+ title: i18n.translate('xpack.securitySolution.endpoint.details.agentStatus', {
+ defaultMessage: 'Agent Status',
+ }),
+ description: (
+
+
+
+
+
+ ),
+ },
{
title: i18n.translate('xpack.securitySolution.endpoint.details.lastSeen', {
defaultMessage: 'Last Seen',
@@ -85,7 +117,7 @@ export const EndpointDetails = memo(
description: ,
},
];
- }, [details]);
+ }, [details, hostStatus]);
const [policyResponseUri, policyResponseRoutePath] = useMemo(() => {
// eslint-disable-next-line @typescript-eslint/naming-convention
@@ -135,13 +167,15 @@ export const EndpointDetails = memo(
defaultMessage: 'Integration Policy',
}),
description: (
- <>
-
- {details.Endpoint.policy.applied.name}
-
+
+
+
+ {details.Endpoint.policy.applied.name}
+
+
{details.Endpoint.policy.applied.endpoint_policy_version && (
@@ -167,7 +201,7 @@ export const EndpointDetails = memo(
)}
- >
+
),
},
{
@@ -175,25 +209,26 @@ export const EndpointDetails = memo(
defaultMessage: 'Policy Response',
}),
description: (
-
- {/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
-
-
-
-
-
-
+
+
+
+
),
},
];
@@ -248,7 +283,7 @@ export const EndpointDetails = memo(
onClick={handleReassignEndpointsClick}
data-test-subj="endpointDetailsLinkToIngest"
>
-
+
{
} = queryParams;
const details = useEndpointSelector(detailsData);
const policyInfo = useEndpointSelector(policyVersionInfo);
+ const hostStatus = useEndpointSelector(hostStatusInfo);
const loading = useEndpointSelector(detailsLoading);
const error = useEndpointSelector(detailsError);
const show = useEndpointSelector(showView);
@@ -83,7 +86,7 @@ export const EndpointDetailsFlyout = memo(() => {
onClose={handleFlyoutClose}
style={{ zIndex: 4001 }}
data-test-subj="endpointDetailsFlyout"
- size="s"
+ size="m"
>
{loading ? (
@@ -112,7 +115,11 @@ export const EndpointDetailsFlyout = memo(() => {
{show === 'details' && (
<>
-
+
>
)}
@@ -125,6 +132,14 @@ export const EndpointDetailsFlyout = memo(() => {
EndpointDetailsFlyout.displayName = 'EndpointDetailsFlyout';
+const PolicyResponseFlyout = styled.div`
+ .endpointDetailsPolicyResponseFlyoutBody {
+ .euiFlyoutBody__overflowContent {
+ padding-top: 0;
+ }
+ }
+`;
+
const PolicyResponseFlyoutPanel = memo<{
hostMeta: HostMetadata;
}>(({ hostMeta }) => {
@@ -165,12 +180,15 @@ const PolicyResponseFlyoutPanel = memo<{
}, [backToDetailsClickHandler, detailsUri]);
return (
- <>
+
-
+
)}
- >
+
);
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx
index 5c883e9affe1dc..b6c6be673da601 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/policy_response.tsx
@@ -29,7 +29,7 @@ import {
*/
const PolicyResponseConfigAccordion = styled(EuiAccordion)`
.euiAccordion__triggerWrapper {
- padding: ${(props) => props.theme.eui.paddingSizes.s};
+ padding: ${(props) => props.theme.eui.paddingSizes.xs};
}
&.euiAccordion-isOpen {
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
index 37a26d88053521..71f6d78caea7e8 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/host_constants.ts
@@ -25,6 +25,16 @@ export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze<
success: 'success',
warning: 'warning',
failure: 'danger',
+ unsupported: 'subdued',
+});
+
+export const POLICY_STATUS_TO_BADGE_COLOR = Object.freeze<
+ { [key in keyof typeof HostPolicyResponseActionStatus]: string }
+>({
+ success: 'secondary',
+ warning: 'warning',
+ failure: 'danger',
+ unsupported: 'default',
});
export const POLICY_STATUS_TO_TEXT = Object.freeze<
@@ -39,4 +49,7 @@ export const POLICY_STATUS_TO_TEXT = Object.freeze<
failure: i18n.translate('xpack.securitySolution.policyStatusText.failure', {
defaultMessage: 'Failure',
}),
+ unsupported: i18n.translate('xpack.securitySolution.policyStatusText.unsupported', {
+ defaultMessage: 'Unsupported',
+ }),
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index 99c313220e0680..9925b35616c918 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -645,49 +645,41 @@ describe('when on the list page', () => {
it('should display Success overall policy status', async () => {
const renderResult = await renderAndWaitForData();
- const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
- expect(policyStatusLink.textContent).toEqual('Success');
-
- const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth');
- expect(
- policyStatusHealth.querySelector('[data-euiicon-type][color="success"]')
- ).not.toBeNull();
+ const policyStatusBadge = await renderResult.findByTestId('policyStatusValue');
+ expect(policyStatusBadge.textContent).toEqual('Success');
+ expect(policyStatusBadge.getAttribute('style')).toMatch(
+ /background-color\: rgb\(109\, 204\, 177\)\;/
+ );
});
it('should display Warning overall policy status', async () => {
mockEndpointListApi(createPolicyResponse(HostPolicyResponseActionStatus.warning));
const renderResult = await renderAndWaitForData();
- const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
- expect(policyStatusLink.textContent).toEqual('Warning');
-
- const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth');
- expect(
- policyStatusHealth.querySelector('[data-euiicon-type][color="warning"]')
- ).not.toBeNull();
+ const policyStatusBadge = await renderResult.findByTestId('policyStatusValue');
+ expect(policyStatusBadge.textContent).toEqual('Warning');
+ expect(policyStatusBadge.getAttribute('style')).toMatch(
+ /background-color\: rgb\(241\, 216\, 111\)\;/
+ );
});
it('should display Failed overall policy status', async () => {
mockEndpointListApi(createPolicyResponse(HostPolicyResponseActionStatus.failure));
const renderResult = await renderAndWaitForData();
- const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
- expect(policyStatusLink.textContent).toEqual('Failed');
-
- const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth');
- expect(
- policyStatusHealth.querySelector('[data-euiicon-type][color="danger"]')
- ).not.toBeNull();
+ const policyStatusBadge = await renderResult.findByTestId('policyStatusValue');
+ expect(policyStatusBadge.textContent).toEqual('Failed');
+ expect(policyStatusBadge.getAttribute('style')).toMatch(
+ /background-color\: rgb\(255\, 126\, 98\)\;/
+ );
});
it('should display Unknown overall policy status', async () => {
mockEndpointListApi(createPolicyResponse('' as HostPolicyResponseActionStatus));
const renderResult = await renderAndWaitForData();
- const policyStatusLink = await renderResult.findByTestId('policyStatusValue');
- expect(policyStatusLink.textContent).toEqual('Unknown');
-
- const policyStatusHealth = await renderResult.findByTestId('policyStatusHealth');
- expect(
- policyStatusHealth.querySelector('[data-euiicon-type][color="subdued"]')
- ).not.toBeNull();
+ const policyStatusBadge = await renderResult.findByTestId('policyStatusValue');
+ expect(policyStatusBadge.textContent).toEqual('Unknown');
+ expect(policyStatusBadge.getAttribute('style')).toMatch(
+ /background-color\: rgb\(211\, 218\, 230\)\;/
+ );
});
it('should include the link to reassignment in Ingest', async () => {
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
index ec0198de585589..e14f56881d6733 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
@@ -12,7 +12,6 @@ import {
EuiButton,
EuiButtonEmpty,
EuiSpacer,
- EuiOverlayMask,
EuiConfirmModal,
EuiCallOut,
EuiLoadingSpinner,
@@ -234,59 +233,54 @@ const ConfirmUpdate = React.memo<{
onCancel: () => void;
}>(({ hostCount, onCancel, onConfirm }) => {
return (
-
-
- {hostCount > 0 && (
- <>
-
-
-
-
- >
- )}
-
-
-
-
-
+
+ {hostCount > 0 && (
+ <>
+
+
+
+
+ >
+ )}
+
+
+
+
);
});
diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
index 4e3dc953b539e2..bffd9806103721 100644
--- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/trusted_app_deletion_dialog.tsx
@@ -19,7 +19,6 @@ import {
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
- EuiOverlayMask,
EuiText,
} from '@elastic/eui';
@@ -100,36 +99,34 @@ export const TrustedAppDeletionDialog = memo(() => {
if (useTrustedAppsSelector(isDeletionDialogOpen)) {
return (
-
-
-
- {translations.title}
-
+
+
+ {translations.title}
+
-
-
- {translations.mainMessage}
- {translations.subMessage}
-
-
+
+
+ {translations.mainMessage}
+ {translations.subMessage}
+
+
-
-
- {translations.cancelButton}
-
+
+
+ {translations.cancelButton}
+
-
- {translations.confirmButton}
-
-
-
-
+
+ {translations.confirmButton}
+
+
+
);
} else {
return <>>;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx
index 35dcd88b77e04e..6713be176586cc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx
@@ -9,6 +9,7 @@ import React from 'react';
import { useKibana } from '../../../../common/lib/kibana';
import { TestProviders, mockIndexNames, mockIndexPattern } from '../../../../common/mock';
+import { TimelineId } from '../../../../../common/types/timeline';
import { useTimelineKpis } from '../../../containers/kpis';
import { FlyoutHeader } from '.';
import { useSourcererScope } from '../../../../common/containers/sourcerer';
@@ -33,6 +34,14 @@ const mockUseTimelineKpiResponse = {
hostCount: 1,
destinationIpCount: 1,
};
+
+const mockUseTimelineLargeKpiResponse = {
+ processCount: 1000,
+ userCount: 1000000,
+ sourceIpCount: 1000000000,
+ hostCount: 999,
+ destinationIpCount: 1,
+};
const defaultMocks = {
browserFields: mockBrowserFields,
docValueFields: mockDocValueFields,
@@ -65,7 +74,7 @@ describe('Timeline KPIs', () => {
it('renders the component, labels and values succesfully', async () => {
const wrapper = mount(
-
+
);
expect(wrapper.find('[data-test-subj="siem-timeline-kpis"]').exists()).toEqual(true);
@@ -87,7 +96,7 @@ describe('Timeline KPIs', () => {
it('renders a loading indicator for values', async () => {
const wrapper = mount(
-
+
);
expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual(
@@ -103,7 +112,7 @@ describe('Timeline KPIs', () => {
it('renders labels and the default empty string', async () => {
const wrapper = mount(
-
+
);
@@ -115,4 +124,29 @@ describe('Timeline KPIs', () => {
);
});
});
+
+ describe('when the response contains numbers larger than one thousand', () => {
+ beforeEach(() => {
+ mockUseTimelineKpis.mockReturnValue([false, mockUseTimelineLargeKpiResponse]);
+ });
+ it('formats the numbers correctly', async () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual(
+ expect.stringContaining('1k')
+ );
+ expect(wrapper.find('[data-test-subj="siem-timeline-user-kpi"]').first().text()).toEqual(
+ expect.stringContaining('1m')
+ );
+ expect(wrapper.find('[data-test-subj="siem-timeline-source-ip-kpi"]').first().text()).toEqual(
+ expect.stringContaining('1b')
+ );
+ expect(wrapper.find('[data-test-subj="siem-timeline-host-kpi"]').first().text()).toEqual(
+ expect.stringContaining('999')
+ );
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
index ae0d92bfe3d0be..033ee83fcc0d81 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx
@@ -126,11 +126,11 @@ const FlyoutHeaderPanelComponent: React.FC = ({ timeline
{show && (
- {activeTab === TimelineTabs.query && (
+ {(activeTab === TimelineTabs.query || activeTab === TimelineTabs.eql) && (
{
+ const kpiFormat = '0,0.[000]a';
+ const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT);
+ const formattedKpis = useMemo(() => {
+ return {
+ process: kpis === null ? getEmptyValue() : numeral(kpis.processCount).format(kpiFormat),
+ user: kpis === null ? getEmptyValue() : numeral(kpis.userCount).format(kpiFormat),
+ host: kpis === null ? getEmptyValue() : numeral(kpis.hostCount).format(kpiFormat),
+ sourceIp: kpis === null ? getEmptyValue() : numeral(kpis.sourceIpCount).format(kpiFormat),
+ destinationIp:
+ kpis === null ? getEmptyValue() : numeral(kpis.destinationIpCount).format(kpiFormat),
+ };
+ }, [kpis]);
+ const formattedKpiToolTips = useMemo(() => {
+ return {
+ process: numeral(kpis?.processCount).format(defaultNumberFormat),
+ user: numeral(kpis?.userCount).format(defaultNumberFormat),
+ host: numeral(kpis?.hostCount).format(defaultNumberFormat),
+ sourceIp: numeral(kpis?.sourceIpCount).format(defaultNumberFormat),
+ destinationIp: numeral(kpis?.destinationIpCount).format(defaultNumberFormat),
+ };
+ }, [kpis, defaultNumberFormat]);
return (
-
+
+
+
-
+
+
+
-
+
+
+
-
+
+
+
-
+
+
+
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
index a87f486a9d5d1f..7dde3fbe4cd2a6 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/delete_timeline_modal/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiModal } from '@elastic/eui';
import React, { useCallback } from 'react';
import { createGlobalStyle } from 'styled-components';
@@ -46,16 +46,14 @@ export const DeleteTimelineModalOverlay = React.memo(
<>
{isModalOpen && }
{isModalOpen ? (
-
-
-
-
-
+
+
+
) : null}
>
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
index cde1b705be98e3..5ae5237421b541 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts
@@ -291,6 +291,13 @@ describe('helpers', () => {
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
description: '',
deletedEventIds: [],
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
@@ -394,6 +401,13 @@ describe('helpers', () => {
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
description: '',
deletedEventIds: [],
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
@@ -497,6 +511,13 @@ describe('helpers', () => {
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
description: '',
deletedEventIds: [],
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
@@ -598,6 +619,13 @@ describe('helpers', () => {
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
description: '',
deletedEventIds: [],
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
@@ -737,6 +765,13 @@ describe('helpers', () => {
dateRange: { start: '2020-07-07T08:20:18.966Z', end: '2020-07-08T08:20:18.966Z' },
description: '',
deletedEventIds: [],
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
@@ -865,6 +900,13 @@ describe('helpers', () => {
dataProviders: [],
description: '',
deletedEventIds: [],
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
@@ -1009,6 +1051,13 @@ describe('helpers', () => {
dateRange: { end: '2020-10-28T11:37:31.655Z', start: '2020-10-27T11:37:31.655Z' },
description: '',
deletedEventIds: [],
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
@@ -1112,6 +1161,13 @@ describe('helpers', () => {
dateRange: { end: '2020-07-08T08:20:18.966Z', start: '2020-07-07T08:20:18.966Z' },
description: '',
deletedEventIds: [],
+ eqlOptions: {
+ eventCategoryField: 'event.category',
+ tiebreakerField: 'event.sequence',
+ timestampField: '@timestamp',
+ query: '',
+ size: 100,
+ },
eventIdToNoteIds: {},
eventType: 'all',
excludedRowRendererIds: [],
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts
index b861e839908009..68f4d70c018f8c 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.ts
@@ -434,7 +434,7 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli
id,
filterQuery: {
kuery: {
- kind: 'kuery',
+ kind: timeline.kqlQuery.filterQuery.kuery.kind ?? 'kuery',
expression: timeline.kqlQuery.filterQuery.kuery.expression || '',
},
serializedQuery: timeline.kqlQuery.filterQuery.serializedQuery || '',
diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx
index 5b7fbcffd14ad7..c23cffa854514f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/open_timeline_modal/index.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { EuiModal, EuiOverlayMask } from '@elastic/eui';
+import { EuiModal } from '@elastic/eui';
import React from 'react';
import { TimelineModel } from '../../../../timelines/store/timeline/model';
@@ -26,22 +26,20 @@ const OPEN_TIMELINE_MODAL_WIDTH = 1100; // px
export const OpenTimelineModal = React.memo(
({ hideActions = [], modalTitle, onClose, onOpen }) => (
-
-
-
-
-
+
+
+
)
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
index 00c7e86b0e4a7c..65974a10c49c21 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/index.tsx
@@ -11,9 +11,12 @@ import { ExternalLinkIcon } from '../../../../common/components/external_link_ic
import { RowRendererId } from '../../../../../common/types/timeline';
import {
+ AlertsExample,
AuditdExample,
AuditdFileExample,
+ LibraryExample,
NetflowExample,
+ RegistryExample,
SuricataExample,
SystemExample,
SystemDnsExample,
@@ -47,6 +50,13 @@ export interface RowRendererOption {
}
export const renderers: RowRendererOption[] = [
+ {
+ id: RowRendererId.alerts,
+ name: i18n.ALERTS_NAME,
+ description: i18n.ALERTS_DESCRIPTION,
+ example: AlertsExample,
+ searchableDescription: i18n.ALERTS_DESCRIPTION,
+ },
{
id: RowRendererId.auditd,
name: i18n.AUDITD_NAME,
@@ -75,6 +85,13 @@ export const renderers: RowRendererOption[] = [
example: AuditdFileExample,
searchableDescription: `${i18n.AUDITD_FILE_NAME} ${i18n.AUDITD_FILE_DESCRIPTION_PART1}`,
},
+ {
+ id: RowRendererId.library,
+ name: i18n.LIBRARY_NAME,
+ description: i18n.LIBRARY_DESCRIPTION,
+ example: LibraryExample,
+ searchableDescription: i18n.LIBRARY_DESCRIPTION,
+ },
{
id: RowRendererId.system_security_event,
name: i18n.AUTHENTICATION_NAME,
@@ -140,6 +157,13 @@ export const renderers: RowRendererOption[] = [
example: SystemEndgameProcessExample,
searchableDescription: `${i18n.PROCESS_DESCRIPTION_PART1} ${i18n.PROCESS_DESCRIPTION_PART2}`,
},
+ {
+ id: RowRendererId.registry,
+ name: i18n.REGISTRY_NAME,
+ description: i18n.REGISTRY_DESCRIPTION,
+ example: RegistryExample,
+ searchableDescription: i18n.REGISTRY_DESCRIPTION,
+ },
{
id: RowRendererId.system_fim,
name: i18n.FIM_NAME,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
index ebfe178f14ac29..a0d6d4e121891f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/catalog/translations.ts
@@ -7,6 +7,17 @@
import { i18n } from '@kbn/i18n';
+export const ALERTS_NAME = i18n.translate('xpack.securitySolution.eventRenderers.alertsName', {
+ defaultMessage: 'Alerts',
+});
+
+export const ALERTS_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.alertsDescription',
+ {
+ defaultMessage: 'Alerts are displayed when malware or ransomware is prevented and detected',
+ }
+);
+
export const AUDITD_NAME = i18n.translate('xpack.securitySolution.eventRenderers.auditdName', {
defaultMessage: 'Auditd',
});
@@ -112,6 +123,18 @@ export const FLOW_DESCRIPTION_PART2 = i18n.translate(
}
);
+export const LIBRARY_NAME = i18n.translate('xpack.securitySolution.eventRenderers.libraryName', {
+ defaultMessage: 'Library',
+});
+
+export const LIBRARY_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.libraryDescription',
+ {
+ defaultMessage:
+ 'Library events display a Dynamically Linked Library (DLL) being loaded by a process',
+ }
+);
+
export const PROCESS = i18n.translate('xpack.securitySolution.eventRenderers.processName', {
defaultMessage: 'Process',
});
@@ -132,6 +155,17 @@ export const PROCESS_DESCRIPTION_PART2 = i18n.translate(
}
);
+export const REGISTRY_NAME = i18n.translate('xpack.securitySolution.eventRenderers.registryName', {
+ defaultMessage: 'Registry',
+});
+
+export const REGISTRY_DESCRIPTION = i18n.translate(
+ 'xpack.securitySolution.eventRenderers.registryDescription',
+ {
+ defaultMessage: 'Registry events show updates to the Windows Registry',
+ }
+);
+
export const SOCKET_NAME = i18n.translate('xpack.securitySolution.eventRenderers.socketName', {
defaultMessage: 'Socket (Network)',
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx
new file mode 100644
index 00000000000000..b0384155c5c10f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/alerts.tsx
@@ -0,0 +1,34 @@
+/*
+ * 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 React from 'react';
+
+import { mockEndpointProcessExecutionMalwarePreventionAlert } from '../../../../common/mock/mock_timeline_data';
+import { createEndpointAlertsRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const AlertsExampleComponent: React.FC = () => {
+ const alertsRowRenderer = createEndpointAlertsRowRenderer({
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS,
+ });
+
+ return (
+ <>
+ {alertsRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndpointProcessExecutionMalwarePreventionAlert,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const AlertsExample = React.memo(AlertsExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx
index 1b4e3b1723a2dc..6932ca01835cc0 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/index.tsx
@@ -5,9 +5,12 @@
* 2.0.
*/
+export * from './alerts';
export * from './auditd';
export * from './auditd_file';
+export * from './library';
export * from './netflow';
+export * from './registry';
export * from './suricata';
export * from './system';
export * from './system_dns';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx
new file mode 100644
index 00000000000000..6198225fcb87dd
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/library.tsx
@@ -0,0 +1,31 @@
+/*
+ * 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 React from 'react';
+
+import { mockEndpointLibraryLoadEvent } from '../../../../common/mock/mock_timeline_data';
+import { createEndpointLibraryRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { LOADED_LIBRARY } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const LibraryExampleComponent: React.FC = () => {
+ const libraryRowRenderer = createEndpointLibraryRowRenderer({
+ actionName: 'load',
+ text: LOADED_LIBRARY,
+ });
+
+ return (
+ <>
+ {libraryRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndpointLibraryLoadEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const LibraryExample = React.memo(LibraryExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx
new file mode 100644
index 00000000000000..f00db0d94eed82
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/row_renderers_browser/examples/registry.tsx
@@ -0,0 +1,31 @@
+/*
+ * 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 React from 'react';
+
+import { mockEndpointRegistryModificationEvent } from '../../../../common/mock/mock_timeline_data';
+import { createEndpointRegistryRowRenderer } from '../../timeline/body/renderers/system/generic_row_renderer';
+import { MODIFIED_REGISTRY_KEY } from '../../timeline/body/renderers/system/translations';
+import { ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID } from '../constants';
+
+const RegistryExampleComponent: React.FC = () => {
+ const registryRowRenderer = createEndpointRegistryRowRenderer({
+ actionName: 'modification',
+ text: MODIFIED_REGISTRY_KEY,
+ });
+
+ return (
+ <>
+ {registryRowRenderer.renderRow({
+ browserFields: {},
+ data: mockEndpointRegistryModificationEvent,
+ timelineId: ROW_RENDERER_BROWSER_EXAMPLE_TIMELINE_ID,
+ })}
+ >
+ );
+};
+export const RegistryExample = React.memo(RegistryExampleComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
index 124c8012fd533c..aece377ee4f2dc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/__snapshots__/index.test.tsx.snap
@@ -287,7 +287,7 @@ Array [
data-eui="EuiFocusTrap"
>
deepEqual(prevProps.browserFields, nextProps.browserFields) &&
deepEqual(prevProps.docValueFields, nextProps.docValueFields) &&
+ deepEqual(prevProps.expandedEvent, nextProps.expandedEvent) &&
prevProps.timelineId === nextProps.timelineId
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx
index e3cc982f9ea13f..18ead2490dee34 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx
@@ -168,28 +168,40 @@ const ColumnHeaderComponent: React.FC
= ({
handleClosePopOverTrigger();
},
},
- {
- disabled: !header.aggregatable,
- icon: ,
- name: i18n.SORT_AZ,
- onClick: () => {
- onColumnSort(Direction.asc);
- handleClosePopOverTrigger();
- },
- },
- {
- disabled: !header.aggregatable,
- icon: ,
- name: i18n.SORT_ZA,
- onClick: () => {
- onColumnSort(Direction.desc);
- handleClosePopOverTrigger();
- },
- },
+ ...(tabType !== TimelineTabs.eql
+ ? [
+ {
+ disabled: !header.aggregatable,
+ icon: ,
+ name: i18n.SORT_AZ,
+ onClick: () => {
+ onColumnSort(Direction.asc);
+ handleClosePopOverTrigger();
+ },
+ },
+ {
+ disabled: !header.aggregatable,
+ icon: ,
+ name: i18n.SORT_ZA,
+ onClick: () => {
+ onColumnSort(Direction.desc);
+ handleClosePopOverTrigger();
+ },
+ },
+ ]
+ : []),
],
},
],
- [dispatch, handleClosePopOverTrigger, header.aggregatable, header.id, onColumnSort, timelineId]
+ [
+ dispatch,
+ handleClosePopOverTrigger,
+ header.aggregatable,
+ header.id,
+ onColumnSort,
+ tabType,
+ timelineId,
+ ]
);
const headerButton = useMemo(
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap
index bd0ae9024df5bb..4cd2193f148a3e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap
@@ -14,6 +14,7 @@ exports[`Header renders correctly against snapshot 1`] = `
isLoading={false}
isResizing={false}
onClick={[Function]}
+ showSortingCapability={true}
sort={
Array [
Object {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx
index b28de9f2a1073c..393594c69bb816 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/header_content.tsx
@@ -22,6 +22,7 @@ interface HeaderContentProps {
isLoading: boolean;
isResizing: boolean;
onClick: () => void;
+ showSortingCapability: boolean;
sort: Sort[];
}
@@ -31,10 +32,11 @@ const HeaderContentComponent: React.FC = ({
isLoading,
isResizing,
onClick,
+ showSortingCapability,
sort,
}) => (
- {header.aggregatable ? (
+ {header.aggregatable && showSortingCapability ? (
{
return {
...original,
+ useSelector: jest.fn(),
useDispatch: () => mockDispatch,
};
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx
index 7680af3ee70afb..bec241e10d6137 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx
@@ -9,6 +9,7 @@ import { noop } from 'lodash/fp';
import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
+import { useShallowEqualSelector } from '../../../../../../common/hooks/use_selector';
import { timelineActions } from '../../../../../store/timeline';
import { ColumnHeaderOptions } from '../../../../../../timelines/store/timeline/model';
import { OnFilterChange } from '../../../events';
@@ -18,6 +19,7 @@ import { Filter } from '../filter';
import { getNewSortDirectionOnClick } from './helpers';
import { HeaderContent } from './header_content';
import { useManageTimeline } from '../../../../manage_timeline';
+import { isEqlOnSelector } from './selectors';
interface Props {
header: ColumnHeaderOptions;
@@ -33,6 +35,8 @@ export const HeaderComponent: React.FC = ({
timelineId,
}) => {
const dispatch = useDispatch();
+ const getIsEqlOn = useMemo(() => isEqlOnSelector(), []);
+ const isEqlOn = useShallowEqualSelector((state) => getIsEqlOn(state, timelineId));
const onColumnSort = useCallback(() => {
const columnId = header.id;
@@ -90,6 +94,7 @@ export const HeaderComponent: React.FC = ({
isLoading={isLoading}
isResizing={false}
onClick={onColumnSort}
+ showSortingCapability={!isEqlOn}
sort={sort}
>
+ createSelector(selectTimeline, (timeline) => timeline?.activeTab === TimelineTabs.eql);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
index 1c544d60440a43..99fb6c3dd89071 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.tsx
@@ -320,13 +320,15 @@ export const ColumnHeadersComponent = ({
-
-
-
- {ColumnSorting}
-
-
-
+ {tabType !== TimelineTabs.eql && (
+
+
+
+ {ColumnSorting}
+
+
+
+ )}
{showEventsSelect && (
@@ -363,5 +365,6 @@ export const ColumnHeaders = React.memo(
deepEqual(prevProps.sort, nextProps.sort) &&
prevProps.timelineId === nextProps.timelineId &&
deepEqual(prevProps.columnHeaders, nextProps.columnHeaders) &&
+ prevProps.tabType === nextProps.tabType &&
deepEqual(prevProps.browserFields, nextProps.browserFields)
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx
index 57131a64b58af9..d76b5834c233e6 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx
@@ -6,6 +6,7 @@
*/
import React from 'react';
+import { isEmpty } from 'lodash';
import { inputsModel } from '../../../../../common/store';
import { BrowserFields } from '../../../../../common/containers/source';
@@ -81,7 +82,9 @@ const EventsComponent: React.FC = ({
eventIdToNoteIds={eventIdToNoteIds}
isEventPinned={eventIsPinned({ eventId: event._id, pinnedEventIds })}
isEventViewer={isEventViewer}
- key={`${id}_${tabType}_${event._id}_${event._index}`}
+ key={`${id}_${tabType}_${event._id}_${event._index}_${
+ !isEmpty(event.ecs.eql?.sequenceNumber) ? event.ecs.eql?.sequenceNumber : ''
+ }`}
lastFocusedAriaColindex={lastFocusedAriaColindex}
loadingEventIds={loadingEventIds}
onRowSelected={onRowSelected}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx
index defa044f56b55b..45b10d635195bb 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx
@@ -25,7 +25,7 @@ import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers';
import { EventsTrGroup, EventsTrSupplement, EventsTrSupplementContainer } from '../../styles';
import { ColumnRenderer } from '../renderers/column_renderer';
import { RowRenderer } from '../renderers/row_renderer';
-import { isEventBuildingBlockType, getEventType } from '../helpers';
+import { isEventBuildingBlockType, getEventType, isEvenEqlSequence } from '../helpers';
import { NoteCards } from '../../../notes/note_cards';
import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context';
import { EventColumnView } from './event_column_view';
@@ -243,6 +243,7 @@ const StatefulEventComponent: React.FC = ({
data-test-subj="event"
eventType={getEventType(event.ecs)}
isBuildingBlockType={isEventBuildingBlockType(event.ecs)}
+ isEvenEqlSequence={isEvenEqlSequence(event.ecs)}
isExpanded={isDetailPanelExpanded}
ref={trGroupRef}
showLeftBorder={!isEventViewer}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx
index 389a50f9ddb4b3..32a01654bf9aff 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx
@@ -108,10 +108,23 @@ export const getEventIdToDataMapping = (
export const isEventBuildingBlockType = (event: Ecs): boolean =>
!isEmpty(event.signal?.rule?.building_block_type);
-/** Return eventType raw or signal */
+export const isEvenEqlSequence = (event: Ecs): boolean => {
+ if (!isEmpty(event.eql?.sequenceNumber)) {
+ try {
+ const sequenceNumber = (event.eql?.sequenceNumber ?? '').split('-')[0];
+ return parseInt(sequenceNumber, 10) % 2 === 0;
+ } catch {
+ return false;
+ }
+ }
+ return false;
+};
+/** Return eventType raw or signal or eql */
export const getEventType = (event: Ecs): Omit => {
if (!isEmpty(event.signal?.rule?.id)) {
return 'signal';
+ } else if (!isEmpty(event.eql?.parentId)) {
+ return 'eql';
}
return 'raw';
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
index e6d79c4ba53bc0..d7274f0774fc52 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.test.tsx
@@ -33,12 +33,15 @@ describe('FileDraggable', () => {
endgameFileName="[endgameFileName]"
endgameFilePath="[endgameFilePath]"
eventId="1"
+ fileExtOriginalPath="[fileExtOriginalPath]"
fileName="[fileName]"
filePath="[filePath]"
/>
);
- expect(wrapper.text()).toEqual('[fileName]in[filePath]');
+ expect(wrapper.text()).toEqual(
+ '[fileName]in[filePath]from its original path[fileExtOriginalPath]'
+ );
});
test('it returns an empty string when none of the files or paths are provided', () => {
@@ -49,6 +52,7 @@ describe('FileDraggable', () => {
endgameFileName={undefined}
endgameFilePath={undefined}
eventId="1"
+ fileExtOriginalPath={undefined}
fileName={undefined}
filePath={undefined}
/>
@@ -65,6 +69,7 @@ describe('FileDraggable', () => {
endgameFileName="[endgameFileName]"
endgameFilePath={undefined}
eventId="1"
+ fileExtOriginalPath={undefined}
fileName={undefined}
filePath={undefined}
/>
@@ -81,6 +86,7 @@ describe('FileDraggable', () => {
endgameFileName={undefined}
endgameFilePath="[endgameFilePath]"
eventId="1"
+ fileExtOriginalPath={undefined}
fileName={undefined}
filePath={undefined}
/>
@@ -97,6 +103,7 @@ describe('FileDraggable', () => {
endgameFileName={undefined}
endgameFilePath={undefined}
eventId="1"
+ fileExtOriginalPath={undefined}
fileName="[fileName]"
filePath={undefined}
/>
@@ -113,6 +120,7 @@ describe('FileDraggable', () => {
endgameFileName={undefined}
endgameFilePath={undefined}
eventId="1"
+ fileExtOriginalPath={undefined}
fileName={undefined}
filePath="[filePath]"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
index af118a1688ae37..703b38e627e550 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_draggable.tsx
@@ -19,10 +19,19 @@ interface Props {
eventId: string;
fileName: string | null | undefined;
filePath: string | null | undefined;
+ fileExtOriginalPath: string | null | undefined;
}
export const FileDraggable = React.memo(
- ({ contextId, endgameFileName, endgameFilePath, eventId, fileName, filePath }) => {
+ ({
+ contextId,
+ endgameFileName,
+ endgameFilePath,
+ eventId,
+ fileExtOriginalPath,
+ fileName,
+ filePath,
+ }) => {
if (
isNillEmptyOrNotFinite(fileName) &&
isNillEmptyOrNotFinite(endgameFileName) &&
@@ -86,6 +95,23 @@ export const FileDraggable = React.memo(
/>
) : null}
+
+ {!isNillEmptyOrNotFinite(fileExtOriginalPath) && (
+ <>
+
+ {i18n.FROM_ITS_ORIGINAL_PATH}
+
+
+
+
+ >
+ )}
>
);
}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx
new file mode 100644
index 00000000000000..e7e6274942beab
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.test.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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 React from 'react';
+
+import { TestProviders } from '../../../../../common/mock';
+import '../../../../../common/mock/match_media';
+import { useMountAppended } from '../../../../../common/utils/use_mount_appended';
+
+import { FileHash } from './file_hash';
+
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+ return {
+ ...original,
+ // eslint-disable-next-line react/display-name
+ EuiScreenReaderOnly: () => <>>,
+ };
+});
+
+describe('FileHash', () => {
+ const mount = useMountAppended();
+
+ const allProps = {
+ contextId: 'test',
+ eventId: '1',
+ fileHashSha256: undefined,
+ };
+
+ test('displays the fileHashSha256 when provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('[fileHashSha256]');
+ });
+
+ test('displays nothing when fileHashSha256 is null', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('');
+ });
+
+ test('displays nothing when fileHashSha256 is undefined', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('');
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx
new file mode 100644
index 00000000000000..9e624ba17c9212
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/file_hash.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 { EuiFlexGroup } from '@elastic/eui';
+import React from 'react';
+import styled from 'styled-components';
+
+import { DraggableBadge } from '../../../../../common/components/draggables';
+
+import { isNillEmptyOrNotFinite, TokensFlexItem } from './helpers';
+
+const HashFlexGroup = styled(EuiFlexGroup)`
+ margin: ${({ theme }) => theme.eui.euiSizeXS};
+`;
+
+interface Props {
+ contextId: string;
+ eventId: string;
+ fileHashSha256: string | null | undefined;
+}
+
+export const FileHash = React.memo(({ contextId, eventId, fileHashSha256 }) => {
+ if (isNillEmptyOrNotFinite(fileHashSha256)) {
+ return null;
+ }
+
+ return (
+
+
+
+
+
+ );
+});
+
+FileHash.displayName = 'FileHash';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.test.tsx
index d32ec728d229ba..6fe3726c328fae 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.test.tsx
@@ -193,7 +193,19 @@ describe('helpers', () => {
});
describe('valid values', () => {
- const validValues = ['file_create_event', 'created', 'file_delete_event', 'deleted'];
+ const validValues = [
+ 'created',
+ 'creation',
+ 'deleted',
+ 'deletion',
+ 'file_create_event',
+ 'file_delete_event',
+ 'files-encrypted',
+ 'load',
+ 'modification',
+ 'overwrite',
+ 'rename',
+ ];
validValues.forEach((eventAction) => {
test(`${eventAction} returns true`, () => {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.tsx
index ea84dc19908f03..e4644414fdc8ee 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/helpers.tsx
@@ -60,6 +60,26 @@ export const isProcessStoppedOrTerminationEvent = (
): boolean => ['process_stopped', 'termination_event'].includes(`${eventAction}`.toLowerCase());
export const showVia = (eventAction: string | null | undefined): boolean =>
- ['file_create_event', 'created', 'creation', 'file_delete_event', 'deleted', 'deletion'].includes(
- `${eventAction}`.toLowerCase()
- );
+ [
+ 'created',
+ 'creation',
+ 'deleted',
+ 'deletion',
+ 'file_create_event',
+ 'file_delete_event',
+ 'files-encrypted',
+ 'load',
+ 'modification',
+ 'overwrite',
+ 'rename',
+ ].includes(`${eventAction}`.toLowerCase());
+
+export const excludeFileNameAndPath = ({
+ eventAction,
+ eventCategory,
+ eventType,
+}: {
+ eventAction: string | null | undefined;
+ eventCategory: string | null | undefined;
+ eventType: string | null | undefined;
+}) => false;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
index 5dd30b84d2b749..9e90e061e94d58 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.test.tsx
@@ -22,81 +22,39 @@ jest.mock('@elastic/eui', () => {
};
});
+const allProps = {
+ contextId: 'test',
+ eventId: '1',
+ processHashSha256: undefined,
+};
+
describe('ProcessHash', () => {
const mount = useMountAppended();
- test('displays the processHashMd5, processHashSha1, and processHashSha256 when they are all provided', () => {
+ test('displays the processHashSha256 when provided', () => {
const wrapper = mount(
-
+
);
- expect(wrapper.text()).toEqual('[processHashSha256][processHashSha1][processHashMd5]');
+ expect(wrapper.text()).toEqual('[processHashSha256]');
});
- test('displays nothing when processHashMd5, processHashSha1, and processHashSha256 are all undefined', () => {
+ test('displays nothing when processHashSha256 is null', () => {
const wrapper = mount(
-
+
);
expect(wrapper.text()).toEqual('');
});
- test('displays just processHashMd5 when the other hashes are undefined', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.text()).toEqual('[processHashMd5]');
- });
-
- test('displays just processHashSha1 when the other hashes are undefined', () => {
+ test('displays nothing when processHashSha256 is undefined', () => {
const wrapper = mount(
-
+
);
- expect(wrapper.text()).toEqual('[processHashSha1]');
- });
-
- test('displays just processHashSha256 when the other hashes are undefined', () => {
- const wrapper = mount(
-
-
-
- );
- expect(wrapper.text()).toEqual('[processHashSha256]');
+ expect(wrapper.text()).toEqual('');
});
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
index 1af2daad7fc5a8..32432afbf205c0 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/process_hash.tsx
@@ -20,61 +20,27 @@ const HashFlexGroup = styled(EuiFlexGroup)`
interface Props {
contextId: string;
eventId: string;
- processHashMd5: string | null | undefined;
- processHashSha1: string | null | undefined;
processHashSha256: string | null | undefined;
}
-export const ProcessHash = React.memo(
- ({ contextId, eventId, processHashMd5, processHashSha1, processHashSha256 }) => {
- if (
- isNillEmptyOrNotFinite(processHashSha256) &&
- isNillEmptyOrNotFinite(processHashSha1) &&
- isNillEmptyOrNotFinite(processHashMd5)
- ) {
- return null;
- }
-
- return (
-
- {!isNillEmptyOrNotFinite(processHashSha256) && (
-
-
-
- )}
-
- {!isNillEmptyOrNotFinite(processHashSha1) && (
-
-
-
- )}
-
- {!isNillEmptyOrNotFinite(processHashMd5) && (
-
-
-
- )}
-
- );
+export const ProcessHash = React.memo(({ contextId, eventId, processHashSha256 }) => {
+ if (isNillEmptyOrNotFinite(processHashSha256)) {
+ return null;
}
-);
+
+ return (
+
+
+
+
+
+ );
+});
ProcessHash.displayName = 'ProcessHash';
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx
new file mode 100644
index 00000000000000..f37adef7e73cb0
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.test.tsx
@@ -0,0 +1,48 @@
+/*
+ * 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 React from 'react';
+
+import { mockBrowserFields } from '../../../../../../common/containers/source/mock';
+import {
+ mockEndpointRegistryModificationEvent,
+ TestProviders,
+} from '../../../../../../common/mock';
+import '../../../../../../common/mock/match_media';
+import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+import { MODIFIED_REGISTRY_KEY } from '../system/translations';
+
+import { RegistryEventDetails } from './registry_event_details';
+
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+ return {
+ ...original,
+ // eslint-disable-next-line react/display-name
+ EuiScreenReaderOnly: () => <>>,
+ };
+});
+
+describe('RegistryEventDetails', () => {
+ const mount = useMountAppended();
+
+ test('it renders the expected text given an Endpoint Registry modification event', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1modified registry keySOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentStatewith new valueHKLM\\SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState\\StateValueviaGoogleUpdate.exe(7408)'
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx
new file mode 100644
index 00000000000000..0bfb03168019a5
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details.tsx
@@ -0,0 +1,58 @@
+/*
+ * 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 { get } from 'lodash/fp';
+import React from 'react';
+
+import { BrowserFields } from '../../../../../../common/containers/source';
+import { Details, isNillEmptyOrNotFinite } from '../helpers';
+import { Ecs } from '../../../../../../../common/ecs';
+
+import { RegistryEventDetailsLine } from './registry_event_details_line';
+
+interface Props {
+ browserFields: BrowserFields;
+ contextId: string;
+ data: Ecs;
+ text: string;
+}
+
+const RegistryEventDetailsComponent: React.FC = ({ contextId, data, text }) => {
+ const hostName: string | null | undefined = get('host.name[0]', data);
+ const id = data._id;
+ const processName: string | null | undefined = get('process.name[0]', data);
+ const processPid: number | null | undefined = get('process.pid[0]', data);
+ const registryKey: string | null | undefined = get('registry.key[0]', data);
+ const registryPath: string | null | undefined = get('registry.path[0]', data);
+ const userDomain: string | null | undefined = get('user.domain[0]', data);
+ const userName: string | null | undefined = get('user.name[0]', data);
+
+ if (isNillEmptyOrNotFinite(registryKey)) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
+
+RegistryEventDetailsComponent.displayName = 'RegistryEventDetailsComponent';
+
+export const RegistryEventDetails = React.memo(RegistryEventDetailsComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx
new file mode 100644
index 00000000000000..6be1529152523f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.test.tsx
@@ -0,0 +1,135 @@
+/*
+ * 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 React from 'react';
+
+import { TestProviders } from '../../../../../../common/mock';
+import '../../../../../../common/mock/match_media';
+import { useMountAppended } from '../../../../../../common/utils/use_mount_appended';
+import { RegistryEventDetailsLine } from './registry_event_details_line';
+import { MODIFIED_REGISTRY_KEY } from '../system/translations';
+
+jest.mock('@elastic/eui', () => {
+ const original = jest.requireActual('@elastic/eui');
+ return {
+ ...original,
+ // eslint-disable-next-line react/display-name
+ EuiScreenReaderOnly: () => <>>,
+ };
+});
+
+describe('DnsRequestEventDetailsLine', () => {
+ const mount = useMountAppended();
+
+ const allProps = {
+ contextId: 'test',
+ hostName: '[hostName]',
+ id: '1',
+ processName: '[processName]',
+ processPid: 123,
+ registryKey: '[registryKey]',
+ registryPath: '[registryPath]',
+ text: MODIFIED_REGISTRY_KEY,
+ userDomain: '[userDomain]',
+ userName: '[userName]',
+ };
+
+ test('it renders the expected text when all properties are provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]@[hostName]modified registry key[registryKey]with new value[registryPath]via[processName](123)'
+ );
+ });
+
+ test('it returns an empty string when when registryKey is null', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('');
+ });
+
+ test('it returns an empty string when registryKey is undefined', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual('');
+ });
+
+ test('it renders the expected text when hostName is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]modified registry key[registryKey]with new value[registryPath]via[processName](123)'
+ );
+ });
+
+ test('it renders the expected text when processName is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]@[hostName]modified registry key[registryKey]with new value[registryPath]via(123)'
+ );
+ });
+
+ test('it renders the expected text when processPid is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]@[hostName]modified registry key[registryKey]with new value[registryPath]via[processName]'
+ );
+ });
+
+ test('it renders the expected text when registryPath is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]\\[userDomain]@[hostName]modified registry key[registryKey]via[processName](123)'
+ );
+ });
+
+ test('it renders the expected text when userDomain is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '[userName]@[hostName]modified registry key[registryKey]with new value[registryPath]via[processName](123)'
+ );
+ });
+
+ test('it renders the expected text when userName is NOT provided', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(wrapper.text()).toEqual(
+ '\\[userDomain][hostName]modified registry key[registryKey]with new value[registryPath]via[processName](123)'
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx
new file mode 100644
index 00000000000000..b85ae25ed25093
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/registry_event_details_line.tsx
@@ -0,0 +1,133 @@
+/*
+ * 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 { EuiFlexGroup } from '@elastic/eui';
+import React, { useMemo } from 'react';
+
+import { DraggableBadge } from '../../../../../../common/components/draggables';
+import { isNillEmptyOrNotFinite, TokensFlexItem } from '../helpers';
+import { ProcessDraggableWithNonExistentProcess } from '../process_draggable';
+import { UserHostWorkingDir } from '../user_host_working_dir';
+
+import * as i18n from './translations';
+
+interface Props {
+ contextId: string;
+ hostName: string | null | undefined;
+ id: string;
+ processName: string | null | undefined;
+ processPid: number | null | undefined;
+ registryKey: string | null | undefined;
+ registryPath: string | null | undefined;
+ text: string;
+ userDomain: string | null | undefined;
+ userName: string | null | undefined;
+}
+
+const RegistryEventDetailsLineComponent: React.FC = ({
+ contextId,
+ hostName,
+ id,
+ processName,
+ processPid,
+ registryKey,
+ registryPath,
+ text,
+ userDomain,
+ userName,
+}) => {
+ const registryKeyTooltipContent = useMemo(
+ () => (
+ <>
+ {'registry.key'}
+ {registryKey}
+ >
+ ),
+ [registryKey]
+ );
+
+ const registryPathTooltipContent = useMemo(
+ () => (
+ <>
+ {'registry.path'}
+ {registryPath}
+ >
+ ),
+ [registryPath]
+ );
+
+ if (isNillEmptyOrNotFinite(registryKey)) {
+ return null;
+ }
+
+ return (
+ <>
+
+
+
+ {!isNillEmptyOrNotFinite(registryKey) && (
+ <>
+
+ {text}
+
+
+
+
+ >
+ )}
+
+ {!isNillEmptyOrNotFinite(registryPath) && (
+ <>
+
+ {i18n.WITH_NEW_VALUE}
+
+
+
+
+ >
+ )}
+
+
+ {i18n.VIA}
+
+
+
+
+
+
+ >
+ );
+};
+
+export const RegistryEventDetailsLine = React.memo(RegistryEventDetailsLineComponent);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/translations.ts
new file mode 100644
index 00000000000000..f1c1c9487dfd20
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/registry/translations.ts
@@ -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 { i18n } from '@kbn/i18n';
+
+export const VIA = i18n.translate(
+ 'xpack.securitySolution.timeline.body.renderers.registry.viaDescription',
+ {
+ defaultMessage: 'via',
+ }
+);
+
+export const WITH_NEW_VALUE = i18n.translate(
+ 'xpack.securitySolution.timeline.body.renderers.registry.withNewValueDescription',
+ {
+ defaultMessage: 'with new value',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap
index 8045ce95ca2727..b33d77714adc9f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/__snapshots__/generic_file_details.test.tsx.snap
@@ -11,6 +11,8 @@ exports[`SystemGenericFileDetails rendering it renders the default SystemGeneric
outcome="failure"
processPid={6278}
showMessage={true}
+ skipRedundantFileDetails={false}
+ skipRedundantProcessDetails={false}
text="[generic-text-123]"
userName="Evan"
/>
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
index a3932fde44c1df..b660d823954eee 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.test.tsx
@@ -106,6 +106,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={789}
endgameProcessName="[endgameProcessName-123]"
eventAction="[eventAction-123]"
+ fileExtOriginalPath="[fileExtOriginalPath]"
+ fileHashSha256="[fileHashSha256-123]"
fileName="[fileName-123]"
filePath="[filePath-123]"
hostName="[hostname-123]"
@@ -118,8 +120,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable=123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -138,7 +138,7 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][generic-text-123][fileName-123]in[filePath-123][processName-123](123)[arg-1][arg-2][arg-3][some-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][generic-text-123][fileName-123]in[filePath-123]from its original path[fileExtOriginalPath][processName-123](123)[arg-1][arg-2][arg-3][some-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][fileHashSha256-123][processHashSha256-123][message-123]'
);
});
@@ -155,6 +155,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName={null}
@@ -166,8 +168,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -203,6 +203,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -214,8 +216,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -251,6 +251,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -262,8 +264,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -299,6 +299,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -310,8 +312,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -349,6 +349,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -360,8 +362,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -399,6 +399,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -410,8 +412,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={null}
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -449,6 +449,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -460,8 +462,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable={null}
processExitCode={null}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -499,6 +499,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -510,8 +512,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5={null}
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -536,7 +536,7 @@ describe('SystemGenericFileDetails', () => {
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode', () => {
const wrapper = mount(
@@ -549,6 +549,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -560,8 +562,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1={null}
processHashSha256={null}
processParentName={null}
processParentPid={null}
@@ -582,61 +582,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashMd5-123][message-123]'
- );
- });
-
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1', () => {
- const wrapper = mount(
-
-
-
-
-
- );
- expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256', () => {
const wrapper = mount(
@@ -649,6 +599,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -660,8 +612,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName={null}
processParentPid={null}
@@ -682,11 +632,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123]with exit code-1with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName', () => {
const wrapper = mount(
@@ -699,6 +649,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -710,8 +662,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={null}
@@ -732,11 +682,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1via parent process[processParentName-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123]with exit code-1via parent process[processParentName-123]with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid', () => {
const wrapper = mount(
@@ -749,6 +699,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -760,8 +712,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -782,11 +732,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123]with exit code-1via parent process[processParentName-123](789)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123]with exit code-1via parent process[processParentName-123](789)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid', () => {
const wrapper = mount(
@@ -799,6 +749,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -810,8 +762,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -832,11 +782,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processExecutable-123](123)with exit code-1via parent process[processParentName-123](789)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processExecutable-123](123)with exit code-1via parent process[processParentName-123](789)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName', () => {
+ test('it can return the host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName', () => {
const wrapper = mount(
@@ -849,6 +799,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction={null}
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -860,8 +812,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -882,11 +832,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processName-123](123)with exit code-1via parent process[processParentName-123](789)(456)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processName-123](123)with exit code-1via parent process[processParentName-123](789)(456)with result[outcome-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod', () => {
const wrapper = mount(
@@ -899,6 +849,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -910,8 +862,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -932,11 +882,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature', () => {
const wrapper = mount(
@@ -949,6 +899,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -960,8 +912,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -982,11 +932,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text', () => {
const wrapper = mount(
@@ -999,6 +949,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1010,8 +962,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1032,11 +982,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain', () => {
const wrapper = mount(
@@ -1049,6 +999,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1060,8 +1012,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1082,11 +1032,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '\\[userDomain-123][hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '\\[userDomain-123][hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username', () => {
const wrapper = mount(
@@ -1099,6 +1049,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1110,8 +1062,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1132,11 +1082,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory', () => {
const wrapper = mount(
@@ -1149,6 +1099,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1160,8 +1112,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1182,11 +1132,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title', () => {
const wrapper = mount(
@@ -1199,6 +1149,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1210,8 +1162,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1232,11 +1182,11 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[process-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[process-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
- test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashMd5, processHashSha1, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title, args', () => {
+ test('it can return the endgameExitCode, endgameParentProcessName, eventAction, host, message, outcome, packageName, pacakgeSummary, packageVersion, processExecutable, processExitCode, processHashSha256, processParentName, processParentPid, processPid, processPpid, processName, sshMethod, sshSignature, text, userDomain, username, working-directory, process-title, args', () => {
const wrapper = mount(
@@ -1249,6 +1199,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={null}
endgameProcessName={null}
eventAction="[eventAction-123]"
+ fileExtOriginalPath={null}
+ fileHashSha256={null}
fileName={null}
filePath={null}
hostName="[hostname-123]"
@@ -1260,8 +1212,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion="[packageVersion-123]"
processExecutable="[processExecutable-123]"
processExitCode={-1}
- processHashMd5="[processHashMd5-123]"
- processHashSha1="[processHashSha1-123]"
processHashSha256="[processHashSha256-123]"
processParentName="[processParentName-123]"
processParentPid={789}
@@ -1282,7 +1232,7 @@ describe('SystemGenericFileDetails', () => {
);
expect(wrapper.text()).toEqual(
- '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[arg-1][arg-2][arg-3][process-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][processHashSha1-123][processHashMd5-123][message-123]'
+ '[username-123]\\[userDomain-123]@[hostname-123]in[working-directory-123][text-123][processName-123](123)[arg-1][arg-2][arg-3][process-title-123]with exit code-1[endgameExitCode-123]via parent process[processParentName-123][endgameParentProcessName-123](789)(456)with result[outcome-123][sshSignature-123][sshMethod-123][packageName-123][packageVersion-123][packageSummary-123][processHashSha256-123][message-123]'
);
});
@@ -1299,6 +1249,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1310,8 +1262,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1347,6 +1297,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName="[fileName]"
filePath="[filePath]"
hostName={undefined}
@@ -1358,8 +1310,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1397,6 +1347,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1408,8 +1360,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1449,6 +1399,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1460,8 +1412,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1500,6 +1450,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1511,8 +1463,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1553,6 +1503,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1564,8 +1516,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1604,6 +1554,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={eventAction}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1615,8 +1567,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1653,6 +1603,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1664,8 +1616,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1702,6 +1652,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={undefined}
endgameProcessName={undefined}
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1713,8 +1665,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1751,6 +1701,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={789}
endgameProcessName="[endgameProcessName]"
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1762,8 +1714,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
@@ -1800,6 +1750,8 @@ describe('SystemGenericFileDetails', () => {
endgamePid={789}
endgameProcessName="[endgameProcessName]"
eventAction={undefined}
+ fileExtOriginalPath={undefined}
+ fileHashSha256={undefined}
fileName={undefined}
filePath={undefined}
hostName={undefined}
@@ -1811,8 +1763,6 @@ describe('SystemGenericFileDetails', () => {
packageVersion={undefined}
processExecutable={undefined}
processExitCode={undefined}
- processHashMd5={undefined}
- processHashSha1={undefined}
processHashSha256={undefined}
processParentName={undefined}
processParentPid={undefined}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
index 4026613ff7d0a9..6df583656ff2de 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_file_details.tsx
@@ -23,6 +23,7 @@ import { Args } from '../args';
import { AuthSsh } from './auth_ssh';
import { ExitCodeDraggable } from '../exit_code_draggable';
import { FileDraggable } from '../file_draggable';
+import { FileHash } from '../file_hash';
import { Package } from './package';
import { Badge } from '../../../../../../common/components/page';
import { ParentProcessDraggable } from '../parent_process_draggable';
@@ -38,6 +39,8 @@ interface Props {
endgamePid: number | null | undefined;
endgameProcessName: string | null | undefined;
eventAction: string | null | undefined;
+ fileExtOriginalPath: string | null | undefined;
+ fileHashSha256: string | null | undefined;
fileName: string | null | undefined;
filePath: string | null | undefined;
hostName: string | null | undefined;
@@ -54,11 +57,11 @@ interface Props {
processPid: number | null | undefined;
processPpid: number | null | undefined;
processExecutable: string | null | undefined;
- processHashMd5: string | null | undefined;
- processHashSha1: string | null | undefined;
processHashSha256: string | null | undefined;
processTitle: string | null | undefined;
showMessage: boolean;
+ skipRedundantFileDetails?: boolean;
+ skipRedundantProcessDetails?: boolean;
sshSignature: string | null | undefined;
sshMethod: string | null | undefined;
text: string | null | undefined;
@@ -78,6 +81,8 @@ export const SystemGenericFileLine = React.memo
(
endgamePid,
endgameProcessName,
eventAction,
+ fileExtOriginalPath,
+ fileHashSha256,
fileName,
filePath,
hostName,
@@ -91,14 +96,14 @@ export const SystemGenericFileLine = React.memo(
packageSummary,
packageVersion,
processExecutable,
- processHashMd5,
- processHashSha1,
processHashSha256,
processName,
processPid,
processPpid,
processTitle,
showMessage,
+ skipRedundantFileDetails = false,
+ skipRedundantProcessDetails = false,
sshSignature,
sshMethod,
text,
@@ -119,14 +124,18 @@ export const SystemGenericFileLine = React.memo(
{text}
-
+
+ {!skipRedundantFileDetails && (
+
+ )}
{showVia(eventAction) && (
{i18n.VIA}
@@ -190,13 +199,12 @@ export const SystemGenericFileLine = React.memo(
packageVersion={packageVersion}
/>
-
+ {!skipRedundantFileDetails && (
+
+ )}
+ {!skipRedundantProcessDetails && (
+
+ )}
{message != null && showMessage && (
<>
@@ -221,12 +229,22 @@ interface GenericDetailsProps {
data: Ecs;
contextId: string;
showMessage?: boolean;
+ skipRedundantFileDetails?: boolean;
+ skipRedundantProcessDetails?: boolean;
text: string;
timelineId: string;
}
export const SystemGenericFileDetails = React.memo(
- ({ data, contextId, showMessage = true, text, timelineId }) => {
+ ({
+ data,
+ contextId,
+ showMessage = true,
+ skipRedundantFileDetails = false,
+ skipRedundantProcessDetails = false,
+ text,
+ timelineId,
+ }) => {
const id = data._id;
const message: string | null = data.message != null ? data.message[0] : null;
const hostName: string | null | undefined = get('host.name[0]', data);
@@ -240,6 +258,8 @@ export const SystemGenericFileDetails = React.memo(
const endgamePid: number | null | undefined = get('endgame.pid[0]', data);
const endgameProcessName: string | null | undefined = get('endgame.process_name[0]', data);
const eventAction: string | null | undefined = get('event.action[0]', data);
+ const fileExtOriginalPath: string | null | undefined = get('file.Ext.original.path[0]', data);
+ const fileHashSha256: string | null | undefined = get('file.hash.sha256[0]', data);
const fileName: string | null | undefined = get('file.name[0]', data);
const filePath: string | null | undefined = get('file.path[0]', data);
const userDomain: string | null | undefined = get('user.domain[0]', data);
@@ -251,8 +271,6 @@ export const SystemGenericFileDetails = React.memo(
const processExitCode: number | null | undefined = get('process.exit_code[0]', data);
const processParentName: string | null | undefined = get('process.parent.name[0]', data);
const processParentPid: number | null | undefined = get('process.parent.pid[0]', data);
- const processHashMd5: string | null | undefined = get('process.hash.md5[0]', data);
- const processHashSha1: string | null | undefined = get('process.hash.sha1[0]', data);
const processHashSha256: string | null | undefined = get('process.hash.sha256[0]', data);
const processPid: number | null | undefined = get('process.pid[0]', data);
const processPpid: number | null | undefined = get('process.ppid[0]', data);
@@ -278,6 +296,8 @@ export const SystemGenericFileDetails = React.memo(
endgamePid={endgamePid}
endgameProcessName={endgameProcessName}
eventAction={eventAction}
+ fileExtOriginalPath={fileExtOriginalPath}
+ fileHashSha256={fileHashSha256}
fileName={fileName}
filePath={filePath}
userDomain={userDomain}
@@ -292,14 +312,14 @@ export const SystemGenericFileDetails = React.memo(
packageName={packageName}
packageSummary={packageSummary}
packageVersion={packageVersion}
- processHashMd5={processHashMd5}
- processHashSha1={processHashSha1}
processHashSha256={processHashSha256}
processName={processName}
processPid={processPid}
processPpid={processPpid}
processExecutable={processExecutable}
showMessage={showMessage}
+ skipRedundantFileDetails={skipRedundantFileDetails}
+ skipRedundantProcessDetails={skipRedundantProcessDetails}
sshSignature={sshSignature}
sshMethod={sshMethod}
outcome={outcome}
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
index 4de9bcbdd9c187..b7857e6bf4585e 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.test.tsx
@@ -15,6 +15,9 @@ import { mockBrowserFields } from '../../../../../../common/containers/source/mo
import { Ecs } from '../../../../../../../common/ecs';
import {
mockDnsEvent,
+ mockEndpointProcessExecutionMalwarePreventionAlert,
+ mockEndpointLibraryLoadEvent,
+ mockEndpointRegistryModificationEvent,
mockFimFileCreatedEvent,
mockFimFileDeletedEvent,
mockSocketClosedEvent,
@@ -38,10 +41,25 @@ import {
mockEndgameUserLogon,
mockEndpointDisconnectReceivedEvent,
mockEndpointFileCreationEvent,
+ mockEndpointFileCreationMalwarePreventionAlert,
+ mockEndpointFileCreationMalwareDetectionAlert,
+ mockEndpointFilesEncryptedRansomwarePreventionAlert,
+ mockEndpointFilesEncryptedRansomwareDetectionAlert,
+ mockEndpointFileModificationMalwarePreventionAlert,
+ mockEndpointFileModificationMalwareDetectionAlert,
+ mockEndpointFileRenameMalwarePreventionAlert,
+ mockEndpointFileRenameMalwareDetectionAlert,
mockEndpointFileDeletionEvent,
+ mockEndpointFileModificationEvent,
+ mockEndpointFileOverwriteEvent,
+ mockEndpointFileRenameEvent,
mockEndpointNetworkConnectionAcceptedEvent,
+ mockEndpointNetworkHttpRequestEvent,
mockEndpointNetworkLookupRequestedEvent,
mockEndpointNetworkLookupResultEvent,
+ mockEndpointProcessExecEvent,
+ mockEndpointProcessExecutionMalwareDetectionAlert,
+ mockEndpointProcessForkEvent,
mockEndpointProcessStartEvent,
mockEndpointProcessEndEvent,
mockEndpointSecurityLogOnSuccessEvent,
@@ -53,11 +71,15 @@ import { RowRenderer } from '../row_renderer';
import {
createDnsRowRenderer,
createEndgameProcessRowRenderer,
+ createEndpointAlertsRowRenderer,
+ createEndpointLibraryRowRenderer,
+ createEndpointRegistryRowRenderer,
createFimRowRenderer,
createGenericSystemRowRenderer,
createGenericFileRowRenderer,
createSecurityEventRowRenderer,
createSocketRowRenderer,
+ EndpointAlertCriteria,
} from './generic_row_renderer';
import * as i18n from './translations';
@@ -197,7 +219,341 @@ describe('GenericRowRenderer', () => {
});
});
+ describe('#createEndpointAlertsRowRenderer', () => {
+ test('it renders a Malware File Creation Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'creation',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_CREATING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(mockEndpointFileCreationMalwarePreventionAlert) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileCreationMalwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was prevented from creating a malicious file6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmpinC:\\Users\\sean\\Downloads\\6a5eabd6-1c79-4962-b411-a5e7d9e967d4.tmpviachrome.exe(8944)C:\\Program Files\\Google\\Chrome\\Application\\chrome.exevia parent processexplorer.exe(1008)with resultsuccess7cc42618e580f233fee47e82312cc5c3476cb5de9219ba3f9eb7f99ac0659c30'
+ );
+ });
+
+ test('it renders a Malware File Creation Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'creation',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_CREATING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(mockEndpointFileCreationMalwareDetectionAlert) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileCreationMalwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'DESKTOP-1was detected creating a malicious filemimikatz_write.exeinC:\\temp\\mimikatz_write.exeviapython.exe(4400)C:\\Python27\\python.exemain.py-a,execute-pc:\\tempvia parent processpythonservice.exe(2936)with resultsuccess263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'
+ );
+ });
+
+ test('it renders a Ransomware Files Encrypted Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'files-encrypted',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: i18n.RANSOMWARE_WAS_PREVENTED_FROM_ENCRYPTING_FILES,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointFilesEncryptedRansomwarePreventionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFilesEncryptedRansomwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'DESKTOP-1ransomware was prevented from encrypting filesviapowershell.exe(6056)powershell.exe-filemock_ransomware_v3.ps1via parent processcmd.exe(10680)with resultsuccesse9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7'
+ );
+ });
+
+ test('it renders a Ransomware Files Encrypted Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'files-encrypted',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantFileDetails: true,
+ text: i18n.RANSOMWARE_WAS_DETECTED_ENCRYPTING_FILES,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointFilesEncryptedRansomwareDetectionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFilesEncryptedRansomwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'DESKTOP-1ransomware was detected encrypting filesviapowershell.exe(4684)powershell.exe-filemock_ransomware_v3.ps1via parent processcmd.exe(8616)e9fa973eb5ad446e0be31c7b8ae02d48281319e7f492e1ddaadddfbdd5b480c7'
+ );
+ });
+
+ test('it renders a Malware File Modification Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'modification',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_MODIFYING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointFileModificationMalwarePreventionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileModificationMalwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was prevented from modifying a malicious filemimikatz - Copy.exeinC:\\Users\\sean\\Downloads\\mimikatz_trunk (1)\\x64\\mimikatz - Copy.exeviaexplorer.exe(1008)C:\\Windows\\Explorer.EXEvia parent processC:\\Windows\\System32\\userinit.exe(356)with resultsuccess31eb1de7e840a342fd468e558e5ab627bcb4c542a8fe01aec4d5ba01d539a0fc'
+ );
+ });
+
+ test('it renders a Malware File Modification Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'modification',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_MODIFYING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointFileModificationMalwareDetectionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileModificationMalwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'mac-1.localwas detected modifying a malicious fileaircrackin/private/var/root/write_malware/modules/write_malware/aircrackviaPython(5995)/usr/local/Cellar/python/2.7.14/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Pythonmain.py-amodifyvia parent processPython(97)with resultsuccessf0954d9673878b2223b00b7ec770c7b438d876a9bb44ec78457e5c618f31f52b'
+ );
+ });
+
+ test('it renders a Malware File Rename Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'rename',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_RENAMING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(mockEndpointFileRenameMalwarePreventionAlert) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileRenameMalwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was prevented from renaming a malicious file23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exeinC:\\Users\\sean\\Downloads\\23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exeviaexplorer.exe(1008)C:\\Windows\\Explorer.EXEvia parent processC:\\Windows\\System32\\userinit.exe(356)with resultsuccess23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97'
+ );
+ });
+
+ test('it renders a Malware File Rename Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'rename',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_RENAMING_A_MALICIOUS_FILE,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(mockEndpointFileRenameMalwareDetectionAlert) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileRenameMalwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was detected renaming a malicious file23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exeinC:\\Users\\sean\\Downloads\\23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97.exeviaexplorer.exe(1008)C:\\Windows\\Explorer.EXEvia parent processC:\\Windows\\System32\\userinit.exe(356)with resultsuccess23361f8f413dd9258545030e42056a352fe35f66bac376d49954551c9b4bcf97'
+ );
+ });
+
+ test('it renders a Malware Process Execution Prevented alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointProcessExecutionMalwarePreventionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointProcessExecutionMalwarePreventionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'win2019-endpoint-1was prevented from executing a malicious processC:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exe(6920)C:\\Users\\sean\\Downloads\\3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb.exewith resultsuccess3be13acde2f4dcded4fd8d518a513bfc9882407a6e384ffb17d12710db7d76fb'
+ );
+ });
+
+ test('it renders a Malware Process Execution Detected alert', () => {
+ const endpointAlertCriteria: EndpointAlertCriteria = {
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'allowed',
+ skipRedundantFileDetails: true,
+ text: i18n.WAS_DETECTED_EXECUTING_A_MALICIOUS_PROCESS,
+ };
+
+ const endpointAlertsRowRenderer = createEndpointAlertsRowRenderer(endpointAlertCriteria);
+
+ const wrapper = mount(
+
+ {endpointAlertsRowRenderer.isInstance(
+ mockEndpointProcessExecutionMalwareDetectionAlert
+ ) &&
+ endpointAlertsRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointProcessExecutionMalwareDetectionAlert,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'DESKTOP-1was detected executing a malicious processmimikatz_write.exe(8668)c:\\temp\\mimikatz_write.exevia parent processpython.exewith resultsuccess263f09eeee80e03aa27a2d19530e2451978e18bf733c5f1c64ff2389c5dc17b0'
+ );
+ });
+ });
+
describe('#createEndgameProcessRowRenderer', () => {
+ test('it renders an endpoint Process Exec event', () => {
+ const actionName = 'exec';
+ const text = i18n.EXECUTED_PROCESS;
+
+ const endpointProcessStartRowRenderer = createEndgameProcessRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointProcessStartRowRenderer.isInstance(mockEndpointProcessExecEvent) &&
+ endpointProcessStartRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointProcessExecEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'admin@test-mac.localexecuted processmdworker_shared(4454)/System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared-smdworker-cMDSImporterWorker-mcom.apple.mdworker.sharedvia parent processlaunchd(1)4bc018ac461706496302d1faab0a8bb39aad974eb432758665103165f3a2dd2b'
+ );
+ });
+
+ test('it renders an endpoint Process Fork event', () => {
+ const actionName = 'fork';
+ const text = i18n.FORKED_PROCESS;
+
+ const endpointProcessStartRowRenderer = createEndgameProcessRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointProcessStartRowRenderer.isInstance(mockEndpointProcessForkEvent) &&
+ endpointProcessStartRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointProcessForkEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'admin@test-mac.localforked processzoom.us(4042)/Applications/zoom.us.app/Contents/MacOS/zoom.usvia parent processzoom.us(3961)cbf3d059cc9f9c0adff5ef15bf331b95ab381837fa0adecd965a41b5846f4bd4'
+ );
+ });
+
test('it renders an endpoint process start event', () => {
const actionName = 'start';
const text = i18n.PROCESS_STARTED;
@@ -219,7 +575,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toEqual(
- 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1started processconhost.exe(3636)C:\\Windows\\system32\\conhost.exe,0xffffffff,-ForceV1697334c236cce7d4c9e223146ee683a1219adced9729d4ae771fd6a1502a6b63e19da2c35ba1c38adf12d1a472c1fcf1f1a811a71b0e9b5fcb62de0787235ecca560b610'
+ 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1started processconhost.exe(3636)C:\\Windows\\system32\\conhost.exe,0xffffffff,-ForceV1697334c236cce7d4c9e223146ee683a1219adced9729d4ae771fd6a1502a6b63'
);
});
@@ -247,7 +603,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toEqual(
- 'Arun\\Anvi-Acer@HD-obe-8bf77f54started processMicrosoft.Photos.exe(441684)C:\\Program Files\\WindowsApps\\Microsoft.Windows.Photos_2018.18091.17210.0_x64__8wekyb3d8bbwe\\Microsoft.Photos.exe-ServerName:App.AppXzst44mncqdg84v7sv6p7yznqwssy6f7f.mcavia parent processsvchost.exe(8)d4c97ed46046893141652e2ec0056a698f6445109949d7fcabbce331146889ee12563599116157778a22600d2a163d8112aed84562d06d7235b37895b68de56687895743'
+ 'Arun\\Anvi-Acer@HD-obe-8bf77f54started processMicrosoft.Photos.exe(441684)C:\\Program Files\\WindowsApps\\Microsoft.Windows.Photos_2018.18091.17210.0_x64__8wekyb3d8bbwe\\Microsoft.Photos.exe-ServerName:App.AppXzst44mncqdg84v7sv6p7yznqwssy6f7f.mcavia parent processsvchost.exe(8)d4c97ed46046893141652e2ec0056a698f6445109949d7fcabbce331146889ee'
);
});
@@ -272,7 +628,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toEqual(
- 'SYSTEM\\NT AUTHORITY@win2019-endpointterminated processsvchost.exe(10392)C:\\Windows\\System32\\svchost.exe,-k,netsvcs,-p,-s,NetSetupSvcwith exit code-1via parent processservices.exe7fd065bac18c5278777ae44908101cdfed72d26fa741367f0ad4d02020787ab6a1385ce20ad79f55df235effd9780c31442aa2348a0a29438052faed8a2532da50455756'
+ 'SYSTEM\\NT AUTHORITY@win2019-endpointterminated processsvchost.exe(10392)C:\\Windows\\System32\\svchost.exe,-k,netsvcs,-p,-s,NetSetupSvcwith exit code-1via parent processservices.exe7fd065bac18c5278777ae44908101cdfed72d26fa741367f0ad4d02020787ab6'
);
});
@@ -300,7 +656,7 @@ describe('GenericRowRenderer', () => {
);
expect(wrapper.text()).toEqual(
- 'Arun\\Anvi-Acer@HD-obe-8bf77f54terminated processRuntimeBroker.exe(442384)with exit code087976f3430cc99bc939e0694247c0759961a49832b87218f4313d6fc0bc3a776797255e72d5ed5c058d4785950eba7abaa057653bd4401441a21bf1abce6404f4231db4d'
+ 'Arun\\Anvi-Acer@HD-obe-8bf77f54terminated processRuntimeBroker.exe(442384)with exit code087976f3430cc99bc939e0694247c0759961a49832b87218f4313d6fc0bc3a776'
);
});
@@ -470,6 +826,81 @@ describe('GenericRowRenderer', () => {
);
});
+ test('it renders an endpoint File (FIM) Modification event', () => {
+ const actionName = 'modification';
+ const text = i18n.MODIFIED_FILE;
+
+ const endpointFileModificationRowRenderer = createFimRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointFileModificationRowRenderer.isInstance(mockEndpointFileModificationEvent) &&
+ endpointFileModificationRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileModificationEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'admin@test-Mac.localmodified a file.dat.nosync01a5.6hoWv1in/Users/admin/Library/Application Support/CrashReporter/.dat.nosync01a5.6hoWv1viadiagnostics_agent(421)'
+ );
+ });
+
+ test('it renders an endpoint File (FIM) Overwrite event', () => {
+ const actionName = 'overwrite';
+ const text = i18n.OVERWROTE_FILE;
+
+ const endpointFileOverwriteRowRenderer = createFimRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointFileOverwriteRowRenderer.isInstance(mockEndpointFileOverwriteEvent) &&
+ endpointFileOverwriteRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileOverwriteEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'LOCAL SERVICE\\NT AUTHORITY@windows-endpoint-1overwrote a filelastalive0.datinC:\\Windows\\ServiceState\\EventLog\\Data\\lastalive0.datviasvchost.exe(1228)'
+ );
+ });
+
+ test('it renders an endpoint File (FIM) Rename event', () => {
+ const actionName = 'rename';
+ const text = i18n.RENAMED_FILE;
+
+ const endpointFileRenameRowRenderer = createFimRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointFileRenameRowRenderer.isInstance(mockEndpointFileRenameEvent) &&
+ endpointFileRenameRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointFileRenameEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'LOCAL SERVICE\\NT AUTHORITY@windows-endpoint-1renamed a fileSRU.loginC:\\Windows\\System32\\sru\\SRU.logfrom its original pathC:\\Windows\\System32\\sru\\SRUtmp.logviasvchost.exe(1204)'
+ );
+ });
+
test('it renders an endgame file_delete_event', () => {
const actionName = 'file_delete_event';
const text = i18n.DELETED_FILE;
@@ -667,6 +1098,87 @@ describe('GenericRowRenderer', () => {
);
});
+ describe('#createEndpointRegistryRowRenderer', () => {
+ test('it renders an endpoint Registry Modification event', () => {
+ const actionName = 'modification';
+ const text = i18n.MODIFIED_REGISTRY_KEY;
+
+ const endpointRegistryModificationRowRenderer = createEndpointRegistryRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointRegistryModificationRowRenderer.isInstance(
+ mockEndpointRegistryModificationEvent
+ ) &&
+ endpointRegistryModificationRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointRegistryModificationEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1modified registry keySOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentStatewith new valueHKLM\\SOFTWARE\\WOW6432Node\\Google\\Update\\ClientState\\{430FD4D0-B729-4F61-AA34-91526481799D}\\CurrentState\\StateValueviaGoogleUpdate.exe(7408)'
+ );
+ });
+ });
+
+ describe('#createEndpointLibraryRowRenderer', () => {
+ test('it renders an endpoint Library Load event', () => {
+ const actionName = 'load';
+ const text = i18n.LOADED_LIBRARY;
+
+ const endpointLibraryLoadRowRenderer = createEndpointLibraryRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointLibraryLoadRowRenderer.isInstance(mockEndpointLibraryLoadEvent) &&
+ endpointLibraryLoadRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointLibraryLoadEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(wrapper.text()).toEqual(
+ 'SYSTEM\\NT AUTHORITY@win2019-endpoint-1loaded librarybcrypt.dllinC:\\Windows\\System32\\bcrypt.dllviasshd.exe(9644)e70f5d8f87aab14e3160227d38387889befbe37fa4f8f5adc59eff52804b35fd'
+ );
+ });
+ });
+
+ test('it renders an Endpoint network HTTP Request event', () => {
+ const actionName = 'http_request';
+ const text = i18n.MADE_A_HTTP_REQUEST_VIA;
+
+ const endpointHttpRequestEventRowRenderer = createSocketRowRenderer({
+ actionName,
+ text,
+ });
+
+ const wrapper = mount(
+
+ {endpointHttpRequestEventRowRenderer.isInstance(mockEndpointNetworkHttpRequestEvent) &&
+ endpointHttpRequestEventRowRenderer.renderRow({
+ browserFields: mockBrowserFields,
+ data: mockEndpointNetworkHttpRequestEvent,
+ timelineId: 'test',
+ })}
+
+ );
+
+ expect(removeExternalLinkText(wrapper.text())).toEqual(
+ 'NETWORK SERVICE\\NT AUTHORITY@win2019-endpoint-1made a http request viasvchost.exe(2232)Endpoint network eventoutgoinghttptcpSource10.1.2.3:51570Destination10.11.12.13:80North AmericaUnited States🇺🇸USArizonaPhoenix'
+ );
+ });
+
test('it renders an Endgame ipv4_connection_accept_event', () => {
const actionName = 'ipv4_connection_accept_event';
const text = i18n.ACCEPTED_A_CONNECTION_VIA;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
index 69a6317ebcd11a..211fa9152dc8d1 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/generic_row_renderer.tsx
@@ -15,6 +15,7 @@ import { RowRendererId } from '../../../../../../../common/types/timeline';
import { DnsRequestEventDetails } from '../dns/dns_request_event_details';
import { EndgameSecurityEventDetails } from '../endgame/endgame_security_event_details';
import { isFileEvent, isNillEmptyOrNotFinite } from '../helpers';
+import { RegistryEventDetails } from '../registry/registry_event_details';
import { RowRenderer, RowRendererContainer } from '../row_renderer';
import { SystemGenericDetails } from './generic_details';
@@ -115,6 +116,86 @@ export const createFimRowRenderer = ({
),
});
+export interface EndpointAlertCriteria {
+ eventAction: string;
+ eventCategory: string;
+ eventType: string;
+ skipRedundantFileDetails?: boolean;
+ skipRedundantProcessDetails?: boolean;
+ text: string;
+}
+
+export const createEndpointAlertsRowRenderer = ({
+ eventAction,
+ eventCategory,
+ eventType,
+ skipRedundantFileDetails = false,
+ skipRedundantProcessDetails = false,
+ text,
+}: EndpointAlertCriteria): RowRenderer => ({
+ id: RowRendererId.alerts,
+ isInstance: (ecs) => {
+ const action: string[] | null | undefined = get('event.action', ecs);
+ const category: string[] | null | undefined = get('event.category', ecs);
+ const dataset: string | null | undefined = get('event.dataset[0]', ecs);
+ const type: string[] | null | undefined = get('event.type', ecs);
+
+ const eventActionMatches = action?.includes(eventAction) ?? false;
+ const eventCategoryMatches = category?.includes(eventCategory) ?? false;
+ const eventTypeMatches = type?.includes(eventType) ?? false;
+
+ return (
+ dataset?.toLowerCase() === 'endpoint.alerts' &&
+ eventTypeMatches &&
+ eventCategoryMatches &&
+ eventActionMatches
+ );
+ },
+ renderRow: ({ browserFields, data, timelineId }) => (
+
+
+
+ ),
+});
+
+export const createEndpointLibraryRowRenderer = ({
+ actionName,
+ text,
+}: {
+ actionName: string;
+ text: string;
+}): RowRenderer => ({
+ id: RowRendererId.library,
+ isInstance: (ecs) => {
+ const action: string | null | undefined = get('event.action[0]', ecs);
+ const dataset: string | null | undefined = get('event.dataset[0]', ecs);
+ return (
+ dataset?.toLowerCase() === 'endpoint.events.library' && action?.toLowerCase() === actionName
+ );
+ },
+ renderRow: ({ browserFields, data, timelineId }) => (
+
+
+
+ ),
+});
+
export const createGenericFileRowRenderer = ({
actionName,
text,
@@ -218,6 +299,34 @@ export const createDnsRowRenderer = (): RowRenderer => ({
),
});
+export const createEndpointRegistryRowRenderer = ({
+ actionName,
+ text,
+}: {
+ actionName: string;
+ text: string;
+}): RowRenderer => ({
+ id: RowRendererId.registry,
+ isInstance: (ecs) => {
+ const action: string | null | undefined = get('event.action[0]', ecs);
+ const dataset: string | null | undefined = get('event.dataset[0]', ecs);
+
+ return (
+ dataset?.toLowerCase() === 'endpoint.events.registry' && action?.toLowerCase() === actionName
+ );
+ },
+ renderRow: ({ browserFields, data, timelineId }) => (
+
+
+
+ ),
+});
+
const systemLoginRowRenderer = createGenericSystemRowRenderer({
actionName: 'user_login',
text: i18n.ATTEMPTED_LOGIN,
@@ -238,6 +347,11 @@ const endpointProcessStartRowRenderer = createEndgameProcessRowRenderer({
text: i18n.PROCESS_STARTED,
});
+const endpointRegistryModificationRowRenderer = createEndpointRegistryRowRenderer({
+ actionName: 'modification',
+ text: i18n.MODIFIED_REGISTRY_KEY,
+});
+
const systemProcessStoppedRowRenderer = createGenericFileRowRenderer({
actionName: 'process_stopped',
text: i18n.PROCESS_STOPPED,
@@ -278,6 +392,21 @@ const endpointFileDeletionEventRowRenderer = createFimRowRenderer({
text: i18n.DELETED_FILE,
});
+const endpointModificationEventRowRenderer = createFimRowRenderer({
+ actionName: 'modification',
+ text: i18n.MODIFIED_FILE,
+});
+
+const endpointFileOverwriteEventRowRenderer = createFimRowRenderer({
+ actionName: 'overwrite',
+ text: i18n.OVERWROTE_FILE,
+});
+
+const endpointFileRenamedEventRowRenderer = createFimRowRenderer({
+ actionName: 'rename',
+ text: i18n.RENAMED_FILE,
+});
+
const fimFileDeletedEventRowRenderer = createFimRowRenderer({
actionName: 'deleted',
text: i18n.DELETED_FILE,
@@ -288,6 +417,88 @@ const systemExistingRowRenderer = createGenericFileRowRenderer({
text: i18n.EXISTING_PROCESS,
});
+const endpointAlertCriteria: EndpointAlertCriteria[] = [
+ {
+ eventAction: 'creation',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_CREATING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'creation',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_CREATING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'files-encrypted',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: i18n.RANSOMWARE_WAS_PREVENTED_FROM_ENCRYPTING_FILES,
+ },
+ {
+ eventAction: 'files-encrypted',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantFileDetails: true,
+ text: i18n.RANSOMWARE_WAS_DETECTED_ENCRYPTING_FILES,
+ },
+ {
+ eventAction: 'modification',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_MODIFYING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'modification',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_MODIFYING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'rename',
+ eventCategory: 'file',
+ eventType: 'denied',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_RENAMING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'rename',
+ eventCategory: 'file',
+ eventType: 'allowed',
+ skipRedundantProcessDetails: true,
+ text: i18n.WAS_DETECTED_RENAMING_A_MALICIOUS_FILE,
+ },
+ {
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'denied',
+ skipRedundantFileDetails: true,
+ text: i18n.WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS,
+ },
+ {
+ eventAction: 'execution',
+ eventCategory: 'process',
+ eventType: 'allowed',
+ skipRedundantFileDetails: true,
+ text: i18n.WAS_DETECTED_EXECUTING_A_MALICIOUS_PROCESS,
+ },
+];
+
+const endpointAlertsRowRenderers: RowRenderer[] = endpointAlertCriteria.map((criteria) =>
+ createEndpointAlertsRowRenderer(criteria)
+);
+
+const endpointLibraryLoadRowRenderer = createEndpointLibraryRowRenderer({
+ actionName: 'load',
+ text: i18n.LOADED_LIBRARY,
+});
+
const systemSocketOpenedRowRenderer = createSocketRowRenderer({
actionName: 'socket_opened',
text: i18n.SOCKET_OPENED,
@@ -308,6 +519,21 @@ const endpointConnectionAcceptedEventRowRenderer = createSocketRowRenderer({
text: i18n.ACCEPTED_A_CONNECTION_VIA,
});
+const endpointHttpRequestEventRowRenderer = createSocketRowRenderer({
+ actionName: 'http_request',
+ text: i18n.MADE_A_HTTP_REQUEST_VIA,
+});
+
+const endpointProcessExecRowRenderer = createEndgameProcessRowRenderer({
+ actionName: 'exec',
+ text: i18n.EXECUTED_PROCESS,
+});
+
+const endpointProcessForkRowRenderer = createEndgameProcessRowRenderer({
+ actionName: 'fork',
+ text: i18n.FORKED_PROCESS,
+});
+
const endgameIpv6ConnectionAcceptEventRowRenderer = createSocketRowRenderer({
actionName: 'ipv6_connection_accept_event',
text: i18n.ACCEPTED_A_CONNECTION_VIA,
@@ -448,8 +674,17 @@ export const systemRowRenderers: RowRenderer[] = [
endpointFileCreationEventRowRenderer,
endgameFileDeleteEventRowRenderer,
endpointFileDeletionEventRowRenderer,
+ endpointFileOverwriteEventRowRenderer,
+ endpointFileRenamedEventRowRenderer,
+ ...endpointAlertsRowRenderers,
+ endpointLibraryLoadRowRenderer,
+ endpointModificationEventRowRenderer,
+ endpointRegistryModificationRowRenderer,
endgameIpv4ConnectionAcceptEventRowRenderer,
endpointConnectionAcceptedEventRowRenderer,
+ endpointHttpRequestEventRowRenderer,
+ endpointProcessExecRowRenderer,
+ endpointProcessForkRowRenderer,
endgameIpv6ConnectionAcceptEventRowRenderer,
endgameIpv4DisconnectReceivedEventRowRenderer,
endpointDisconnectReceivedEventRowRenderer,
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/translations.ts
index e007746ea5cc3b..266579fbc228e2 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/system/translations.ts
@@ -91,6 +91,62 @@ export const DELETED_FILE = i18n.translate('xpack.securitySolution.system.delete
defaultMessage: 'deleted a file',
});
+export const EXECUTED_PROCESS = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.executedProcessDescription',
+ {
+ defaultMessage: 'executed process',
+ }
+);
+
+export const FORKED_PROCESS = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.forkedProcessDescription',
+ {
+ defaultMessage: 'forked process',
+ }
+);
+
+export const LOADED_LIBRARY = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.loadedLibraryDescription',
+ {
+ defaultMessage: 'loaded library',
+ }
+);
+
+export const MADE_A_HTTP_REQUEST_VIA = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.madeAHttpRequestViaDescription',
+ {
+ defaultMessage: 'made a http request via',
+ }
+);
+
+export const MODIFIED_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.modifiedFileDescription',
+ {
+ defaultMessage: 'modified a file',
+ }
+);
+
+export const MODIFIED_REGISTRY_KEY = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.modifiedRegistryKeyDescription',
+ {
+ defaultMessage: 'modified registry key',
+ }
+);
+
+export const OVERWROTE_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.overwroteFileDescription',
+ {
+ defaultMessage: 'overwrote a file',
+ }
+);
+
+export const RENAMED_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.renamedFileDescription',
+ {
+ defaultMessage: 'renamed a file',
+ }
+);
+
export const EXISTING_PROCESS = i18n.translate(
'xpack.securitySolution.system.existingProcessDescription',
{
@@ -207,6 +263,76 @@ export const VIA_PARENT_PROCESS = i18n.translate(
}
);
+export const RANSOMWARE_WAS_PREVENTED_FROM_ENCRYPTING_FILES = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.ransomwareWasPreventedFromeEcryptingFilesDescription',
+ {
+ defaultMessage: 'ransomware was prevented from encrypting files',
+ }
+);
+
+export const RANSOMWARE_WAS_DETECTED_ENCRYPTING_FILES = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.ransomwareWasDetectedEcryptingFilesDescription',
+ {
+ defaultMessage: 'ransomware was detected encrypting files',
+ }
+);
+
+export const WAS_DETECTED_CREATING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasDetectedCreatingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was detected creating a malicious file',
+ }
+);
+
+export const WAS_PREVENTED_FROM_CREATING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasPreventedFromCreatingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was prevented from creating a malicious file',
+ }
+);
+
+export const WAS_DETECTED_MODIFYING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasDetectedModifyingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was detected modifying a malicious file',
+ }
+);
+
+export const WAS_PREVENTED_FROM_MODIFYING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasPreventedFromModifyingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was prevented from modifying a malicious file',
+ }
+);
+
+export const WAS_DETECTED_RENAMING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasDetectedRenamingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was detected renaming a malicious file',
+ }
+);
+
+export const WAS_PREVENTED_FROM_RENAMING_A_MALICIOUS_FILE = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasPreventedFromRenamingAMaliciousFileDescription',
+ {
+ defaultMessage: 'was prevented from renaming a malicious file',
+ }
+);
+
+export const WAS_DETECTED_EXECUTING_A_MALICIOUS_PROCESS = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasDetectedExecutingAMaliciousProcessDescription',
+ {
+ defaultMessage: 'was detected executing a malicious process',
+ }
+);
+
+export const WAS_PREVENTED_FROM_EXECUTING_A_MALICIOUS_PROCESS = i18n.translate(
+ 'xpack.securitySolution.rowRenderer.wasPreventedFromExecutingAMaliciousProcessDescription',
+ {
+ defaultMessage: 'was prevented from executing a malicious process',
+ }
+);
+
export const WITH_EXIT_CODE = i18n.translate(
'xpack.securitySolution.system.withExitCodeDescription',
{
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
index 132501a33d7247..476fd77ce06476 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/translations.ts
@@ -15,6 +15,13 @@ export const DESTINATION = i18n.translate('xpack.securitySolution.timeline.desti
defaultMessage: 'Destination',
});
+export const FROM_ITS_ORIGINAL_PATH = i18n.translate(
+ 'xpack.securitySolution.timeline.file.fromOriginalPathDescription',
+ {
+ defaultMessage: 'from its original path',
+ }
+);
+
export const PROTOCOL = i18n.translate('xpack.securitySolution.timeline.protocol', {
defaultMessage: 'Protocol',
});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap
new file mode 100644
index 00000000000000..2595f29144b805
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap
@@ -0,0 +1,149 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Timeline rendering renders correctly against snapshot 1`] = `
+
+`;
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx
new file mode 100644
index 00000000000000..7b77a915f2f057
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.test.tsx
@@ -0,0 +1,235 @@
+/*
+ * 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 { shallow } from 'enzyme';
+import React from 'react';
+import useResizeObserver from 'use-resize-observer/polyfilled';
+
+import { defaultHeaders, mockTimelineData } from '../../../../common/mock';
+import '../../../../common/mock/match_media';
+import { TestProviders } from '../../../../common/mock/test_providers';
+
+import { EqlTabContentComponent, Props as EqlTabContentComponentProps } from './index';
+import { useMountAppended } from '../../../../common/utils/use_mount_appended';
+import { TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
+import { useTimelineEvents } from '../../../containers/index';
+import { useTimelineEventsDetails } from '../../../containers/details/index';
+import { useSourcererScope } from '../../../../common/containers/sourcerer';
+import { mockSourcererScope } from '../../../../common/containers/sourcerer/mocks';
+
+jest.mock('../../../containers/index', () => ({
+ useTimelineEvents: jest.fn(),
+}));
+jest.mock('../../../containers/details/index', () => ({
+ useTimelineEventsDetails: jest.fn(),
+}));
+jest.mock('../body/events/index', () => ({
+ // eslint-disable-next-line react/display-name
+ Events: () => <>>,
+}));
+
+jest.mock('../../../../common/containers/sourcerer');
+
+const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
+jest.mock('use-resize-observer/polyfilled');
+mockUseResizeObserver.mockImplementation(() => ({}));
+
+jest.mock('../../../../common/lib/kibana', () => {
+ const originalModule = jest.requireActual('../../../../common/lib/kibana');
+ return {
+ ...originalModule,
+ useKibana: jest.fn().mockReturnValue({
+ services: {
+ application: {
+ navigateToApp: jest.fn(),
+ getUrlForApp: jest.fn(),
+ },
+ docLinks: { links: { query: { eql: 'url-eql_doc' } } },
+ uiSettings: {
+ get: jest.fn(),
+ },
+ savedObjects: {
+ client: {},
+ },
+ },
+ }),
+ useGetUserSavedObjectPermissions: jest.fn(),
+ };
+});
+
+describe('Timeline', () => {
+ let props = {} as EqlTabContentComponentProps;
+ const startDate = '2018-03-23T18:49:23.132Z';
+ const endDate = '2018-03-24T03:33:52.253Z';
+
+ const mount = useMountAppended();
+
+ beforeEach(() => {
+ (useTimelineEvents as jest.Mock).mockReturnValue([
+ false,
+ {
+ events: mockTimelineData,
+ pageInfo: {
+ activePage: 0,
+ totalPages: 10,
+ },
+ },
+ ]);
+ (useTimelineEventsDetails as jest.Mock).mockReturnValue([false, {}]);
+
+ (useSourcererScope as jest.Mock).mockReturnValue(mockSourcererScope);
+
+ props = {
+ columns: defaultHeaders,
+ end: endDate,
+ eqlOptions: {},
+ expandedDetail: {},
+ eventType: 'all',
+ timelineId: TimelineId.test,
+ isLive: false,
+ itemsPerPage: 5,
+ itemsPerPageOptions: [5, 10, 20],
+ onEventClosed: jest.fn(),
+ showExpandedDetails: false,
+ start: startDate,
+ timerangeKind: 'absolute',
+ updateEventTypeAndIndexesName: jest.fn(),
+ activeTab: TimelineTabs.eql,
+ };
+ });
+
+ describe('rendering', () => {
+ test('renders correctly against snapshot', () => {
+ const wrapper = shallow(
+
+
+
+ );
+
+ expect(wrapper.find('EqlTabContentComponent')).toMatchSnapshot();
+ });
+
+ test('it renders the timeline header', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="timelineHeader"]').exists()).toEqual(true);
+ });
+
+ test('it renders the timeline table', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual(
+ true
+ );
+ });
+
+ test('it renders the timeline column headers', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(
+ wrapper
+ .find(
+ `[data-test-subj="${TimelineTabs.eql}-events-table"] [data-test-subj="column-headers"]`
+ )
+ .exists()
+ ).toEqual(true);
+ });
+
+ test('it does NOT renders the timeline global sorting icon in headers', () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(
+ wrapper
+ .find(
+ `[data-test-subj="${TimelineTabs.eql}-events-table"] [data-test-subj="column-headers"] [data-test-subj="timeline-sorting-fields"]`
+ )
+ .exists()
+ ).toEqual(false);
+ });
+
+ test('it does render the timeline table when the source is loading with no events', () => {
+ (useSourcererScope as jest.Mock).mockReturnValue({
+ browserFields: {},
+ docValueFields: [],
+ loading: true,
+ indexPattern: {},
+ selectedPatterns: [],
+ });
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual(
+ true
+ );
+ expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false);
+ });
+
+ test('it does NOT render the timeline table when start is empty', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual(
+ true
+ );
+ expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false);
+ });
+
+ test('it does NOT render the timeline table when end is empty', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find(`[data-test-subj="${TimelineTabs.eql}-events-table"]`).exists()).toEqual(
+ true
+ );
+ expect(wrapper.find('[data-test-subj="events"]').exists()).toEqual(false);
+ });
+
+ it('it does NOT render the timeline footer when query is empty', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="timeline-footer"]').exists()).toEqual(false);
+ });
+
+ it('it shows the timeline footer when query is non-empty', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find('[data-test-subj="timeline-footer"]').exists()).toEqual(true);
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx
new file mode 100644
index 00000000000000..984587c9f06d9e
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/index.tsx
@@ -0,0 +1,401 @@
+/*
+ * 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 {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyoutHeader,
+ EuiFlyoutBody,
+ EuiFlyoutFooter,
+ EuiSpacer,
+ EuiBadge,
+} from '@elastic/eui';
+import { isEmpty } from 'lodash/fp';
+import React, { useEffect, useCallback } from 'react';
+import styled from 'styled-components';
+import { Dispatch } from 'redux';
+import { connect, ConnectedProps } from 'react-redux';
+import deepEqual from 'fast-deep-equal';
+import { InPortal } from 'react-reverse-portal';
+
+import { timelineActions, timelineSelectors } from '../../../store/timeline';
+import { TimelineItem } from '../../../../../common/search_strategy';
+import { useTimelineEvents } from '../../../containers/index';
+import { defaultHeaders } from '../body/column_headers/default_headers';
+import { StatefulBody } from '../body';
+import { Footer, footerHeight } from '../footer';
+import { calculateTotalPages } from '../helpers';
+import { TimelineRefetch } from '../refetch_timeline';
+import { useManageTimeline } from '../../manage_timeline';
+import { TimelineEventsType, TimelineId, TimelineTabs } from '../../../../../common/types/timeline';
+import { requiredFieldsForActions } from '../../../../detections/components/alerts_table/default_config';
+import { SuperDatePicker } from '../../../../common/components/super_date_picker';
+import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context';
+import { PickEventType } from '../search_or_filter/pick_events';
+import { inputsModel, inputsSelectors, State } from '../../../../common/store';
+import { sourcererActions } from '../../../../common/store/sourcerer';
+import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
+import { timelineDefaults } from '../../../../timelines/store/timeline/defaults';
+import { useSourcererScope } from '../../../../common/containers/sourcerer';
+import { useEqlEventsCountPortal } from '../../../../common/hooks/use_timeline_events_count';
+import { TimelineModel } from '../../../../timelines/store/timeline/model';
+import { TimelineDatePickerLock } from '../date_picker_lock';
+import { HideShowContainer } from '../styles';
+import { useTimelineFullScreen } from '../../../../common/containers/use_full_screen';
+import { activeTimeline } from '../../../containers/active_timeline_context';
+import { ToggleDetailPanel } from '../../../store/timeline/actions';
+import { DetailsPanel } from '../../side_panel';
+import { EqlQueryBarTimeline } from '../query_bar/eql';
+import { Sort } from '../body/sort';
+
+const TimelineHeaderContainer = styled.div`
+ margin-top: 6px;
+ width: 100%;
+`;
+
+TimelineHeaderContainer.displayName = 'TimelineHeaderContainer';
+
+const StyledEuiFlyoutHeader = styled(EuiFlyoutHeader)`
+ align-items: stretch;
+ box-shadow: none;
+ display: flex;
+ flex-direction: column;
+ padding: 0;
+`;
+
+const StyledEuiFlyoutBody = styled(EuiFlyoutBody)`
+ overflow-y: hidden;
+ flex: 1;
+
+ .euiFlyoutBody__overflow {
+ overflow: hidden;
+ mask-image: none;
+ }
+
+ .euiFlyoutBody__overflowContent {
+ padding: 0;
+ height: 100%;
+ display: flex;
+ }
+`;
+
+const StyledEuiFlyoutFooter = styled(EuiFlyoutFooter)`
+ background: none;
+ padding: 0;
+`;
+
+const FullWidthFlexGroup = styled(EuiFlexGroup)`
+ margin: 0;
+ width: 100%;
+ overflow: hidden;
+`;
+
+const ScrollableFlexItem = styled(EuiFlexItem)`
+ overflow: hidden;
+`;
+
+const DatePicker = styled(EuiFlexItem)`
+ .euiSuperDatePicker__flexWrapper {
+ max-width: none;
+ width: auto;
+ }
+`;
+
+DatePicker.displayName = 'DatePicker';
+
+const VerticalRule = styled.div`
+ width: 2px;
+ height: 100%;
+ background: ${({ theme }) => theme.eui.euiColorLightShade};
+`;
+
+VerticalRule.displayName = 'VerticalRule';
+
+const EventsCountBadge = styled(EuiBadge)`
+ margin-left: ${({ theme }) => theme.eui.paddingSizes.s};
+`;
+
+const isTimerangeSame = (prevProps: Props, nextProps: Props) =>
+ prevProps.end === nextProps.end &&
+ prevProps.start === nextProps.start &&
+ prevProps.timerangeKind === nextProps.timerangeKind;
+
+interface OwnProps {
+ timelineId: string;
+}
+
+const EMPTY_EVENTS: TimelineItem[] = [];
+
+export type Props = OwnProps & PropsFromRedux;
+
+const NO_SORTING: Sort[] = [];
+
+export const EqlTabContentComponent: React.FC = ({
+ activeTab,
+ columns,
+ end,
+ eqlOptions,
+ eventType,
+ expandedDetail,
+ timelineId,
+ isLive,
+ itemsPerPage,
+ itemsPerPageOptions,
+ onEventClosed,
+ showExpandedDetails,
+ start,
+ timerangeKind,
+ updateEventTypeAndIndexesName,
+}) => {
+ const { query: eqlQuery = '', ...restEqlOption } = eqlOptions;
+ const { portalNode: eqlEventsCountPortalNode } = useEqlEventsCountPortal();
+ const { timelineFullScreen } = useTimelineFullScreen();
+ const {
+ browserFields,
+ docValueFields,
+ loading: loadingSourcerer,
+ selectedPatterns,
+ } = useSourcererScope(SourcererScopeName.timeline);
+
+ const isBlankTimeline: boolean = isEmpty(eqlQuery);
+
+ const canQueryTimeline = () =>
+ loadingSourcerer != null &&
+ !loadingSourcerer &&
+ !isEmpty(start) &&
+ !isEmpty(end) &&
+ !isBlankTimeline;
+
+ const getTimelineQueryFields = () => {
+ const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
+ const columnFields = columnsHeader.map((c) => c.id);
+
+ return [...columnFields, ...requiredFieldsForActions];
+ };
+
+ const { initializeTimeline, setIsTimelineLoading } = useManageTimeline();
+ useEffect(() => {
+ initializeTimeline({
+ id: timelineId,
+ });
+ }, [initializeTimeline, timelineId]);
+
+ const [
+ isQueryLoading,
+ { events, inspect, totalCount, pageInfo, loadPage, updatedAt, refetch },
+ ] = useTimelineEvents({
+ docValueFields,
+ endDate: end,
+ eqlOptions: restEqlOption,
+ id: timelineId,
+ indexNames: selectedPatterns,
+ fields: getTimelineQueryFields(),
+ language: 'eql',
+ limit: itemsPerPage,
+ filterQuery: eqlQuery ?? '',
+ startDate: start,
+ skip: !canQueryTimeline(),
+ timerangeKind,
+ });
+
+ const handleOnPanelClosed = useCallback(() => {
+ onEventClosed({ tabType: TimelineTabs.eql, timelineId });
+
+ if (
+ expandedDetail[TimelineTabs.eql]?.panelView &&
+ timelineId === TimelineId.active &&
+ showExpandedDetails
+ ) {
+ activeTimeline.toggleExpandedDetail({});
+ }
+ }, [onEventClosed, timelineId, expandedDetail, showExpandedDetails]);
+
+ useEffect(() => {
+ setIsTimelineLoading({ id: timelineId, isLoading: isQueryLoading || loadingSourcerer });
+ }, [loadingSourcerer, timelineId, isQueryLoading, setIsTimelineLoading]);
+
+ return (
+ <>
+
+ {totalCount >= 0 ? {totalCount} : null}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {!isBlankTimeline && (
+
+ )}
+
+
+
+ {showExpandedDetails && (
+ <>
+
+
+
+
+ >
+ )}
+
+ >
+ );
+};
+
+const makeMapStateToProps = () => {
+ const getTimeline = timelineSelectors.getTimelineByIdSelector();
+ const getInputsTimeline = inputsSelectors.getTimelineSelector();
+ const mapStateToProps = (state: State, { timelineId }: OwnProps) => {
+ const timeline: TimelineModel = getTimeline(state, timelineId) ?? timelineDefaults;
+ const input: inputsModel.InputsRange = getInputsTimeline(state);
+ const {
+ activeTab,
+ columns,
+ eqlOptions,
+ eventType,
+ expandedDetail,
+ itemsPerPage,
+ itemsPerPageOptions,
+ } = timeline;
+
+ return {
+ activeTab,
+ columns,
+ eqlOptions,
+ eventType: eventType ?? 'raw',
+ end: input.timerange.to,
+ expandedDetail,
+ timelineId,
+ isLive: input.policy.kind === 'interval',
+ itemsPerPage,
+ itemsPerPageOptions,
+ showExpandedDetails:
+ !!expandedDetail[TimelineTabs.eql] && !!expandedDetail[TimelineTabs.eql]?.panelView,
+
+ start: input.timerange.from,
+ timerangeKind: input.timerange.kind,
+ };
+ };
+ return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({
+ updateEventTypeAndIndexesName: (newEventType: TimelineEventsType, newIndexNames: string[]) => {
+ dispatch(timelineActions.updateEventType({ id: timelineId, eventType: newEventType }));
+ dispatch(timelineActions.updateIndexNames({ id: timelineId, indexNames: newIndexNames }));
+ dispatch(
+ sourcererActions.setSelectedIndexPatterns({
+ id: SourcererScopeName.timeline,
+ selectedPatterns: newIndexNames,
+ })
+ );
+ },
+ onEventClosed: (args: ToggleDetailPanel) => {
+ dispatch(timelineActions.toggleDetailPanel(args));
+ },
+});
+
+const connector = connect(makeMapStateToProps, mapDispatchToProps);
+
+type PropsFromRedux = ConnectedProps;
+
+const EqlTabContent = connector(
+ React.memo(
+ EqlTabContentComponent,
+ (prevProps, nextProps) =>
+ prevProps.activeTab === nextProps.activeTab &&
+ isTimerangeSame(prevProps, nextProps) &&
+ deepEqual(prevProps.eqlOptions, nextProps.eqlOptions) &&
+ prevProps.eventType === nextProps.eventType &&
+ prevProps.isLive === nextProps.isLive &&
+ prevProps.itemsPerPage === nextProps.itemsPerPage &&
+ prevProps.onEventClosed === nextProps.onEventClosed &&
+ prevProps.showExpandedDetails === nextProps.showExpandedDetails &&
+ prevProps.timelineId === nextProps.timelineId &&
+ prevProps.updateEventTypeAndIndexesName === nextProps.updateEventTypeAndIndexesName &&
+ deepEqual(prevProps.columns, nextProps.columns) &&
+ deepEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions)
+ )
+);
+
+// eslint-disable-next-line import/no-default-export
+export { EqlTabContent as default };
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx
index 5e178b48461bef..8db68706576cb5 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/header/title_and_description.tsx
@@ -10,7 +10,6 @@ import {
EuiButton,
EuiFlexGroup,
EuiFlexItem,
- EuiOverlayMask,
EuiModal,
EuiModalBody,
EuiModalHeader,
@@ -177,83 +176,76 @@ export const TimelineTitleAndDescription = React.memo
-
- {isSaving && (
-
+
+ {isSaving && (
+
+ )}
+ {modalHeader}
+
+
+ {showWarning && (
+
+
+
+
)}
- {modalHeader}
-
-
- {showWarning && (
-
-
-
-
- )}
-
-
-
-
+
+
+
);
}
);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx
index 09b32b8f6140d7..a216280ef11285 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/index.tsx
@@ -120,7 +120,11 @@ const StatefulTimelineComponent: React.FC = ({ timelineId }) => {
-
+
);
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx
new file mode 100644
index 00000000000000..4ae9e79fac4200
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/index.tsx
@@ -0,0 +1,197 @@
+/*
+ * 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 { isEmpty, isEqual } from 'lodash';
+import React, { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { useDispatch } from 'react-redux';
+import styled from 'styled-components';
+
+import { FieldsEqlOptions } from '../../../../../../common/search_strategy';
+import { useSourcererScope } from '../../../../../common/containers/sourcerer';
+import { useDeepEqualSelector } from '../../../../../common/hooks/use_selector';
+import { SourcererScopeName } from '../../../../../common/store/sourcerer/model';
+import { EqlQueryBar } from '../../../../../detections/components/rules/eql_query_bar';
+
+import {
+ debounceAsync,
+ eqlValidator,
+} from '../../../../../detections/components/rules/eql_query_bar/validators';
+import { FieldValueQueryBar } from '../../../../../detections/components/rules/query_bar';
+
+import { Form, FormSchema, UseField, useForm, useFormData } from '../../../../../shared_imports';
+import { timelineActions } from '../../../../store/timeline';
+import * as i18n from '../translations';
+import { getEqlOptions } from './selectors';
+
+interface TimelineEqlQueryBar {
+ index: string[];
+ eqlQueryBar: FieldValueQueryBar;
+}
+
+const defaultValues = {
+ index: [],
+ eqlQueryBar: {
+ query: { query: '', language: 'eql' },
+ filters: [],
+ saved_id: undefined,
+ },
+};
+
+const schema: FormSchema = {
+ index: {
+ fieldsToValidateOnChange: ['index', 'eqlQueryBar'],
+ validations: [],
+ },
+ eqlQueryBar: {
+ validations: [
+ {
+ validator: debounceAsync(eqlValidator, 300),
+ },
+ ],
+ },
+};
+
+const HiddenUseField = styled(UseField)`
+ display: none;
+`;
+
+export const EqlQueryBarTimeline = memo(({ timelineId }: { timelineId: string }) => {
+ const dispatch = useDispatch();
+ const isInit = useRef(true);
+ const [isQueryBarValid, setIsQueryBarValid] = useState(false);
+ const [isQueryBarValidating, setIsQueryBarValidating] = useState(false);
+ const getOptionsSelected = useMemo(() => getEqlOptions(), []);
+ const optionsSelected = useDeepEqualSelector((state) => getOptionsSelected(state, timelineId));
+
+ const { loading: indexPatternsLoading, indexPattern, selectedPatterns } = useSourcererScope(
+ SourcererScopeName.timeline
+ );
+
+ const initialState = {
+ ...defaultValues,
+ index: selectedPatterns.sort(),
+ eqlQueryBar: {
+ ...defaultValues.eqlQueryBar,
+ query: { query: optionsSelected.query ?? '', language: 'eql' },
+ },
+ };
+
+ const { form } = useForm({
+ defaultValue: initialState,
+ options: { stripEmptyFields: false },
+ schema,
+ });
+ const { getFields } = form;
+
+ const onOptionsChange = useCallback(
+ (field: FieldsEqlOptions, value: string | null) =>
+ dispatch(
+ timelineActions.updateEqlOptions({
+ id: timelineId,
+ field,
+ value,
+ })
+ ),
+ [dispatch, timelineId]
+ );
+
+ const [{ eqlQueryBar: formEqlQueryBar }] = useFormData({
+ form,
+ watch: ['eqlQueryBar'],
+ });
+
+ const optionsData = useMemo(
+ () =>
+ isEmpty(indexPattern.fields)
+ ? {
+ keywordFields: [],
+ dateFields: [],
+ nonDateFields: [],
+ }
+ : {
+ keywordFields: indexPattern.fields
+ .filter((f) => f.esTypes?.includes('keyword'))
+ .map((f) => ({ label: f.name })),
+ dateFields: indexPattern.fields
+ .filter((f) => f.type === 'date')
+ .map((f) => ({ label: f.name })),
+ nonDateFields: indexPattern.fields
+ .filter((f) => f.type !== 'date')
+ .map((f) => ({ label: f.name })),
+ },
+ [indexPattern]
+ );
+
+ useEffect(() => {
+ const { index: indexField } = getFields();
+ const newIndexValue = selectedPatterns.sort();
+ const indexFieldValue = (indexField.value as string[]).sort();
+ if (!isEqual(indexFieldValue, newIndexValue)) {
+ indexField.setValue(newIndexValue);
+ }
+ }, [getFields, selectedPatterns]);
+
+ useEffect(() => {
+ const { eqlQueryBar } = getFields();
+ if (isInit.current) {
+ isInit.current = false;
+ setIsQueryBarValidating(true);
+ eqlQueryBar.setValue({
+ ...defaultValues.eqlQueryBar,
+ query: { query: optionsSelected.query ?? '', language: 'eql' },
+ });
+ }
+ return () => {
+ isInit.current = true;
+ };
+ }, [getFields, optionsSelected.query]);
+
+ useEffect(() => {
+ if (
+ formEqlQueryBar != null &&
+ !isEmpty(formEqlQueryBar.query.query) &&
+ isQueryBarValid &&
+ !isQueryBarValidating
+ ) {
+ dispatch(
+ timelineActions.updateEqlOptions({
+ id: timelineId,
+ field: 'query',
+ value: `${formEqlQueryBar.query.query}`,
+ })
+ );
+ setIsQueryBarValid(false);
+ setIsQueryBarValidating(false);
+ }
+ }, [dispatch, formEqlQueryBar, isQueryBarValid, isQueryBarValidating, timelineId]);
+
+ return (
+
+ );
+});
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx
new file mode 100644
index 00000000000000..83418d5f2561f5
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/eql/selectors.tsx
@@ -0,0 +1,30 @@
+/*
+ * 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 { createSelector } from 'reselect';
+import { selectTimeline } from '../../../../store/timeline/selectors';
+
+export const getEqlOptions = () =>
+ createSelector(
+ selectTimeline,
+ (timeline) =>
+ timeline?.eqlOptions ?? {
+ eventCategoryField: [{ label: 'event.category' }],
+ tiebreakerField: [
+ {
+ label: 'event.sequence',
+ },
+ ],
+ timestampField: [
+ {
+ label: '@timestamp',
+ },
+ ],
+ size: 100,
+ query: '',
+ }
+ );
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/translations.ts
new file mode 100644
index 00000000000000..4445030fd4f7be
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_bar/translations.ts
@@ -0,0 +1,15 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+
+export const EQL_QUERY_BAR_LABEL = i18n.translate(
+ 'xpack.securitySolution.timeline.EqlQueryBarLabel',
+ {
+ defaultMessage: 'EQL query',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
index c61be4951db76f..75053c6b91477f 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx
@@ -39,7 +39,12 @@ import { requiredFieldsForActions } from '../../../../detections/components/aler
import { SuperDatePicker } from '../../../../common/components/super_date_picker';
import { EventDetailsWidthProvider } from '../../../../common/components/events_viewer/event_details_width_context';
import { PickEventType } from '../search_or_filter/pick_events';
-import { inputsModel, inputsSelectors, State } from '../../../../common/store';
+import {
+ inputsModel,
+ inputsSelectors,
+ KueryFilterQueryKind,
+ State,
+} from '../../../../common/store';
import { sourcererActions } from '../../../../common/store/sourcerer';
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
import { timelineDefaults } from '../../../../timelines/store/timeline/defaults';
@@ -125,6 +130,11 @@ const isTimerangeSame = (prevProps: Props, nextProps: Props) =>
prevProps.start === nextProps.start &&
prevProps.timerangeKind === nextProps.timerangeKind;
+const compareQueryProps = (prevProps: Props, nextProps: Props) =>
+ prevProps.kqlMode === nextProps.kqlMode &&
+ prevProps.kqlQueryExpression === nextProps.kqlQueryExpression &&
+ deepEqual(prevProps.filters, nextProps.filters);
+
interface OwnProps {
timelineId: string;
}
@@ -157,7 +167,7 @@ export const QueryTabContentComponent: React.FC = ({
timerangeKind,
updateEventTypeAndIndexesName,
}) => {
- const { timelineEventsCountPortalNode } = useTimelineEventsCountPortal();
+ const { portalNode: timelineEventsCountPortalNode } = useTimelineEventsCountPortal();
const { timelineFullScreen } = useTimelineFullScreen();
const {
browserFields,
@@ -170,55 +180,43 @@ export const QueryTabContentComponent: React.FC = ({
const { uiSettings } = useKibana().services;
const [filterManager] = useState(new FilterManager(uiSettings));
const esQueryConfig = useMemo(() => esQuery.getEsQueryConfig(uiSettings), [uiSettings]);
- const kqlQuery = useMemo(() => ({ query: kqlQueryExpression, language: 'kuery' }), [
- kqlQueryExpression,
- ]);
-
- const combinedQueries = useMemo(
- () =>
- combineQueries({
- config: esQueryConfig,
- dataProviders,
- indexPattern,
- browserFields,
- filters,
- kqlQuery,
- kqlMode,
- }),
- [browserFields, dataProviders, esQueryConfig, filters, indexPattern, kqlMode, kqlQuery]
- );
+ const kqlQuery: {
+ query: string;
+ language: KueryFilterQueryKind;
+ } = { query: kqlQueryExpression, language: 'kuery' };
+
+ const combinedQueries = combineQueries({
+ config: esQueryConfig,
+ dataProviders,
+ indexPattern,
+ browserFields,
+ filters,
+ kqlQuery,
+ kqlMode,
+ });
- const isBlankTimeline: boolean = useMemo(
- () => isEmpty(dataProviders) && isEmpty(filters) && isEmpty(kqlQuery.query),
- [dataProviders, filters, kqlQuery]
- );
+ const isBlankTimeline: boolean =
+ isEmpty(dataProviders) && isEmpty(filters) && isEmpty(kqlQuery.query);
- const canQueryTimeline = useMemo(
- () =>
- combinedQueries != null &&
- loadingSourcerer != null &&
- !loadingSourcerer &&
- !isEmpty(start) &&
- !isEmpty(end),
- [loadingSourcerer, combinedQueries, start, end]
- );
+ const canQueryTimeline = () =>
+ combinedQueries != null &&
+ loadingSourcerer != null &&
+ !loadingSourcerer &&
+ !isEmpty(start) &&
+ !isEmpty(end);
- const timelineQueryFields = useMemo(() => {
+ const getTimelineQueryFields = () => {
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
const columnFields = columnsHeader.map((c) => c.id);
return [...columnFields, ...requiredFieldsForActions];
- }, [columns]);
-
- const timelineQuerySortField = useMemo(
- () =>
- sort.map(({ columnId, columnType, sortDirection }) => ({
- field: columnId,
- direction: sortDirection as Direction,
- type: columnType,
- })),
- [sort]
- );
+ };
+
+ const timelineQuerySortField = sort.map(({ columnId, columnType, sortDirection }) => ({
+ field: columnId,
+ direction: sortDirection as Direction,
+ type: columnType,
+ }));
const { initializeTimeline, setIsTimelineLoading } = useManageTimeline();
useEffect(() => {
@@ -236,11 +234,12 @@ export const QueryTabContentComponent: React.FC = ({
endDate: end,
id: timelineId,
indexNames: selectedPatterns,
- fields: timelineQueryFields,
+ fields: getTimelineQueryFields(),
+ language: kqlQuery.language,
limit: itemsPerPage,
filterQuery: combinedQueries?.filterQuery ?? '',
startDate: start,
- skip: !canQueryTimeline,
+ skip: !canQueryTimeline(),
sort: timelineQuerySortField,
timerangeKind,
});
@@ -267,7 +266,7 @@ export const QueryTabContentComponent: React.FC = ({
{totalCount >= 0 ? {totalCount} : null}
= ({
const makeMapStateToProps = () => {
const getShowCallOutUnauthorizedMsg = timelineSelectors.getShowCallOutUnauthorizedMsg();
const getTimeline = timelineSelectors.getTimelineByIdSelector();
- const getKqlQueryTimeline = timelineSelectors.getKqlFilterQuerySelector();
+ const getKqlQueryTimeline = timelineSelectors.getKqlFilterKuerySelector();
const getInputsTimeline = inputsSelectors.getTimelineSelector();
const mapStateToProps = (state: State, { timelineId }: OwnProps) => {
const timeline: TimelineModel = getTimeline(state, timelineId) ?? timelineDefaults;
@@ -398,9 +397,12 @@ const makeMapStateToProps = () => {
// return events on empty search
const kqlQueryExpression =
- isEmpty(dataProviders) && isEmpty(kqlQueryTimeline) && timelineType === 'template'
+ isEmpty(dataProviders) &&
+ isEmpty(kqlQueryTimeline?.expression ?? '') &&
+ timelineType === 'template'
? ' '
- : kqlQueryTimeline;
+ : kqlQueryTimeline?.expression ?? '';
+
return {
activeTab,
columns,
@@ -452,13 +454,12 @@ const QueryTabContent = connector(
React.memo(
QueryTabContentComponent,
(prevProps, nextProps) =>
+ compareQueryProps(prevProps, nextProps) &&
prevProps.activeTab === nextProps.activeTab &&
isTimerangeSame(prevProps, nextProps) &&
prevProps.eventType === nextProps.eventType &&
prevProps.isLive === nextProps.isLive &&
prevProps.itemsPerPage === nextProps.itemsPerPage &&
- prevProps.kqlMode === nextProps.kqlMode &&
- prevProps.kqlQueryExpression === nextProps.kqlQueryExpression &&
prevProps.onEventClosed === nextProps.onEventClosed &&
prevProps.show === nextProps.show &&
prevProps.showCallOutUnauthorizedMsg === nextProps.showCallOutUnauthorizedMsg &&
@@ -468,7 +469,6 @@ const QueryTabContent = connector(
prevProps.updateEventTypeAndIndexesName === nextProps.updateEventTypeAndIndexesName &&
deepEqual(prevProps.columns, nextProps.columns) &&
deepEqual(prevProps.dataProviders, nextProps.dataProviders) &&
- deepEqual(prevProps.filters, nextProps.filters) &&
deepEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) &&
deepEqual(prevProps.sort, nextProps.sort)
)
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx
index 56d7379b935f03..43421a85555ffc 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/styles.tsx
@@ -216,20 +216,35 @@ export const EventsTrGroup = styled.div.attrs(
)<{
className?: string;
eventType: Omit;
- isExpanded: boolean;
+ isEvenEqlSequence: boolean;
isBuildingBlockType: boolean;
+ isExpanded: boolean;
showLeftBorder: boolean;
}>`
border-bottom: ${({ theme }) => theme.eui.euiBorderWidthThin} solid
${({ theme }) => theme.eui.euiColorLightShade};
- ${({ theme, eventType, showLeftBorder }) =>
+ ${({ theme, eventType, isEvenEqlSequence, showLeftBorder }) =>
showLeftBorder
? `border-left: 4px solid
- ${eventType === 'raw' ? theme.eui.euiColorLightShade : theme.eui.euiColorWarning}`
+ ${
+ eventType === 'raw'
+ ? theme.eui.euiColorLightShade
+ : eventType === 'eql' && isEvenEqlSequence
+ ? theme.eui.euiColorPrimary
+ : eventType === 'eql' && !isEvenEqlSequence
+ ? theme.eui.euiColorAccent
+ : theme.eui.euiColorWarning
+ }`
: ''};
${({ isBuildingBlockType }) =>
isBuildingBlockType
- ? `background: repeating-linear-gradient(127deg, rgba(245, 167, 0, 0.2), rgba(245, 167, 0, 0.2) 1px, rgba(245, 167, 0, 0.05) 2px, rgba(245, 167, 0, 0.05) 10px);`
+ ? 'background: repeating-linear-gradient(127deg, rgba(245, 167, 0, 0.2), rgba(245, 167, 0, 0.2) 1px, rgba(245, 167, 0, 0.05) 2px, rgba(245, 167, 0, 0.05) 10px);'
+ : ''};
+ ${({ eventType, isEvenEqlSequence }) =>
+ eventType === 'eql'
+ ? isEvenEqlSequence
+ ? 'background: repeating-linear-gradient(127deg, rgba(0, 107, 180, 0.2), rgba(0, 107, 180, 0.2) 1px, rgba(0, 107, 180, 0.05) 2px, rgba(0, 107, 180, 0.05) 10px);'
+ : 'background: repeating-linear-gradient(127deg, rgba(221, 10, 115, 0.2), rgba(221, 10, 115, 0.2) 1px, rgba(221, 10, 115, 0.05) 2px, rgba(221, 10, 115, 0.05) 10px);'
: ''};
&:hover {
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx
index ca70e4ae646869..b6f959c61dbb2b 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx
@@ -9,13 +9,16 @@ import { EuiBadge, EuiLoadingContent, EuiTabs, EuiTab } from '@elastic/eui';
import React, { lazy, memo, Suspense, useCallback, useEffect, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
-import { TimelineTabs, TimelineId } from '../../../../../common/types/timeline';
+import { TimelineTabs, TimelineId, TimelineType } from '../../../../../common/types/timeline';
import {
useShallowEqualSelector,
useDeepEqualSelector,
} from '../../../../common/hooks/use_selector';
-import { TimelineEventsCountBadge } from '../../../../common/hooks/use_timeline_events_count';
+import {
+ EqlEventsCountBadge,
+ TimelineEventsCountBadge,
+} from '../../../../common/hooks/use_timeline_events_count';
import { timelineActions } from '../../../store/timeline';
import {
getActiveTabSelector,
@@ -37,37 +40,46 @@ const HideShowContainer = styled.div.attrs<{ $isVisible: boolean }>(({ $isVisibl
`;
const QueryTabContent = lazy(() => import('../query_tab_content'));
+const EqlTabContent = lazy(() => import('../eql_tab_content'));
const GraphTabContent = lazy(() => import('../graph_tab_content'));
const NotesTabContent = lazy(() => import('../notes_tab_content'));
const PinnedTabContent = lazy(() => import('../pinned_tab_content'));
interface BasicTimelineTab {
timelineId: TimelineId;
+ timelineType: TimelineType;
graphEventId?: string;
}
-const QueryTab: React.FC = memo(({ timelineId }) => (
+const QueryTab: React.FC<{ timelineId: TimelineId }> = memo(({ timelineId }) => (
}>
));
QueryTab.displayName = 'QueryTab';
-const GraphTab: React.FC = memo(({ timelineId }) => (
+const EqlTab: React.FC<{ timelineId: TimelineId }> = memo(({ timelineId }) => (
+ }>
+
+
+));
+EqlTab.displayName = 'EqlTab';
+
+const GraphTab: React.FC<{ timelineId: TimelineId }> = memo(({ timelineId }) => (
}>
));
GraphTab.displayName = 'GraphTab';
-const NotesTab: React.FC = memo(({ timelineId }) => (
+const NotesTab: React.FC<{ timelineId: TimelineId }> = memo(({ timelineId }) => (
}>
));
NotesTab.displayName = 'NotesTab';
-const PinnedTab: React.FC = memo(({ timelineId }) => (
+const PinnedTab: React.FC<{ timelineId: TimelineId }> = memo(({ timelineId }) => (
}>
@@ -76,45 +88,52 @@ PinnedTab.displayName = 'PinnedTab';
type ActiveTimelineTabProps = BasicTimelineTab & { activeTimelineTab: TimelineTabs };
-const ActiveTimelineTab = memo(({ activeTimelineTab, timelineId }) => {
- const getTab = useCallback(
- (tab: TimelineTabs) => {
- switch (tab) {
- case TimelineTabs.graph:
- return ;
- case TimelineTabs.notes:
- return ;
- default:
- return null;
- }
- },
- [timelineId]
- );
+const ActiveTimelineTab = memo(
+ ({ activeTimelineTab, timelineId, timelineType }) => {
+ const getTab = useCallback(
+ (tab: TimelineTabs) => {
+ switch (tab) {
+ case TimelineTabs.graph:
+ return ;
+ case TimelineTabs.notes:
+ return ;
+ default:
+ return null;
+ }
+ },
+ [timelineId]
+ );
- const isGraphOrNotesTabs = useMemo(
- () => [TimelineTabs.graph, TimelineTabs.notes].includes(activeTimelineTab),
- [activeTimelineTab]
- );
+ const isGraphOrNotesTabs = useMemo(
+ () => [TimelineTabs.graph, TimelineTabs.notes].includes(activeTimelineTab),
+ [activeTimelineTab]
+ );
- /* Future developer -> why are we doing that
- * It is really expansive to re-render the QueryTab because the drag/drop
- * Therefore, we are only hiding its dom when switching to another tab
- * to avoid mounting/un-mounting === re-render
- */
- return (
- <>
-
-
-
-
-
-
-
- {isGraphOrNotesTabs && getTab(activeTimelineTab)}
-
- >
- );
-});
+ /* Future developer -> why are we doing that
+ * It is really expansive to re-render the QueryTab because the drag/drop
+ * Therefore, we are only hiding its dom when switching to another tab
+ * to avoid mounting/un-mounting === re-render
+ */
+ return (
+ <>
+
+
+
+
+
+
+ {timelineType === TimelineType.default && (
+
+
+
+ )}
+
+ {isGraphOrNotesTabs && getTab(activeTimelineTab)}
+
+ >
+ );
+ }
+);
ActiveTimelineTab.displayName = 'ActiveTimelineTab';
@@ -138,7 +157,11 @@ const StyledEuiTab = styled(EuiTab)`
}
`;
-const TabsContentComponent: React.FC = ({ timelineId, graphEventId }) => {
+const TabsContentComponent: React.FC = ({
+ timelineId,
+ timelineType,
+ graphEventId,
+}) => {
const dispatch = useDispatch();
const getActiveTab = useMemo(() => getActiveTabSelector(), []);
const getShowTimeline = useMemo(() => getShowTimelineSelector(), []);
@@ -179,6 +202,10 @@ const TabsContentComponent: React.FC = ({ timelineId, graphEve
);
}, [dispatch, timelineId]);
+ const setEqlAsActiveTab = useCallback(() => {
+ dispatch(timelineActions.setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.eql }));
+ }, [dispatch, timelineId]);
+
const setGraphAsActiveTab = useCallback(() => {
dispatch(
timelineActions.setActiveTabTimeline({ id: timelineId, activeTab: TimelineTabs.graph })
@@ -216,6 +243,18 @@ const TabsContentComponent: React.FC = ({ timelineId, graphEve
{i18n.QUERY_TAB}
{showTimeline && }
+ {timelineType === TimelineType.default && (
+
+ {i18n.EQL_TAB}
+ {showTimeline && }
+
+ )}
= ({ timelineId, graphEve
)}
-
+
>
);
};
diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/translations.ts
index 91f6cfd96a3e4e..6e58beaca82091 100644
--- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/translations.ts
+++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/translations.ts
@@ -14,6 +14,10 @@ export const QUERY_TAB = i18n.translate(
}
);
+export const EQL_TAB = i18n.translate('xpack.securitySolution.timeline.tabs.eqlTabTimelineTitle', {
+ defaultMessage: 'Correlation',
+});
+
export const ANALYZER_TAB = i18n.translate(
'xpack.securitySolution.timeline.tabs.analyserTabTimelineTitle',
{
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts b/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts
index 93e53fa544bbc0..8dd4b0b6a75e6b 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts
+++ b/x-pack/plugins/security_solution/public/timelines/containers/active_timeline_context.ts
@@ -10,7 +10,10 @@ import {
TimelineExpandedDetailType,
TimelineTabs,
} from '../../../common/types/timeline';
-import { TimelineEventsAllRequestOptions } from '../../../common/search_strategy/timeline';
+import {
+ TimelineEqlRequestOptions,
+ TimelineEventsAllRequestOptions,
+} from '../../../common/search_strategy/timeline';
import { TimelineArgs } from '.';
/*
@@ -30,6 +33,8 @@ class ActiveTimelineEvents {
private _pageName: string = '';
private _request: TimelineEventsAllRequestOptions | null = null;
private _response: TimelineArgs | null = null;
+ private _eqlRequest: TimelineEqlRequestOptions | null = null;
+ private _eqlResponse: TimelineArgs | null = null;
getActivePage() {
return this._activePage;
@@ -98,6 +103,22 @@ class ActiveTimelineEvents {
setResponse(resp: TimelineArgs | null) {
this._response = resp;
}
+
+ getEqlRequest() {
+ return this._eqlRequest;
+ }
+
+ setEqlRequest(req: TimelineEqlRequestOptions) {
+ this._eqlRequest = req;
+ }
+
+ getEqlResponse() {
+ return this._eqlResponse;
+ }
+
+ setEqlResponse(resp: TimelineArgs | null) {
+ this._eqlResponse = resp;
+ }
}
export const activeTimeline = new ActiveTimelineEvents();
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx
index 0d53d01fa71315..e8db51a8e9e024 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx
+++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx
@@ -12,7 +12,7 @@ import { useDispatch } from 'react-redux';
import { ESQuery } from '../../../common/typed_json';
import { isCompleteResponse, isErrorResponse } from '../../../../../../src/plugins/data/public';
-import { inputsModel } from '../../common/store';
+import { inputsModel, KueryFilterQueryKind } from '../../common/store';
import { useKibana } from '../../common/lib/kibana';
import { createFilter } from '../../common/containers/helpers';
import { DocValueFields } from '../../common/containers/query_template';
@@ -34,6 +34,11 @@ import * as i18n from './translations';
import { TimelineId } from '../../../common/types/timeline';
import { useRouteSpy } from '../../common/utils/route/use_route_spy';
import { activeTimeline } from './active_timeline_context';
+import {
+ EqlOptionsSelected,
+ TimelineEqlRequestOptions,
+ TimelineEqlResponse,
+} from '../../../common/search_strategy/timeline/events/eql';
export interface TimelineArgs {
events: TimelineItem[];
@@ -48,16 +53,34 @@ export interface TimelineArgs {
type LoadPage = (newActivePage: number) => void;
+type TimelineRequest = T extends 'kuery'
+ ? TimelineEventsAllRequestOptions
+ : T extends 'lucene'
+ ? TimelineEventsAllRequestOptions
+ : T extends 'eql'
+ ? TimelineEqlRequestOptions
+ : TimelineEventsAllRequestOptions;
+
+type TimelineResponse = T extends 'kuery'
+ ? TimelineEventsAllStrategyResponse
+ : T extends 'lucene'
+ ? TimelineEventsAllStrategyResponse
+ : T extends 'eql'
+ ? TimelineEqlResponse
+ : TimelineEventsAllStrategyResponse;
+
export interface UseTimelineEventsProps {
docValueFields?: DocValueFields[];
filterQuery?: ESQuery | string;
skip?: boolean;
endDate: string;
+ eqlOptions?: EqlOptionsSelected;
id: string;
fields: string[];
indexNames: string[];
+ language?: KueryFilterQueryKind;
limit: number;
- sort: TimelineRequestSortField[];
+ sort?: TimelineRequestSortField[];
startDate: string;
timerangeKind?: 'absolute' | 'relative';
}
@@ -77,11 +100,13 @@ export const initSortDefault = [
export const useTimelineEvents = ({
docValueFields,
endDate,
+ eqlOptions = undefined,
id = ID,
indexNames,
fields,
filterQuery,
startDate,
+ language = 'kuery',
limit,
sort = initSortDefault,
skip = false,
@@ -96,10 +121,10 @@ export const useTimelineEvents = ({
const [activePage, setActivePage] = useState(
id === TimelineId.active ? activeTimeline.getActivePage() : 0
);
- const [timelineRequest, setTimelineRequest] = useState(
+ const [timelineRequest, setTimelineRequest] = useState | null>(
null
);
- const prevTimelineRequest = useRef(null);
+ const prevTimelineRequest = useRef | null>(null);
const clearSignalsState = useCallback(() => {
if (id != null && detectionsTimelineIds.some((timelineId) => timelineId === id)) {
@@ -147,7 +172,7 @@ export const useTimelineEvents = ({
});
const timelineSearch = useCallback(
- (request: TimelineEventsAllRequestOptions | null) => {
+ (request: TimelineRequest | null) => {
if (request == null || pageName === '' || skip) {
return;
}
@@ -157,8 +182,11 @@ export const useTimelineEvents = ({
abortCtrl.current = new AbortController();
setLoading(true);
const searchSubscription$ = data.search
- .search(request, {
- strategy: 'securitySolutionTimelineSearchStrategy',
+ .search, TimelineResponse>(request, {
+ strategy:
+ request.language === 'eql'
+ ? 'securitySolutionTimelineEqlSearchStrategy'
+ : 'securitySolutionTimelineSearchStrategy',
abortSignal: abortCtrl.current.signal,
})
.subscribe({
@@ -180,8 +208,13 @@ export const useTimelineEvents = ({
if (id === TimelineId.active) {
activeTimeline.setExpandedDetail({});
activeTimeline.setPageName(pageName);
- activeTimeline.setRequest(request);
- activeTimeline.setResponse(newTimelineResponse);
+ if (request.language === 'eql') {
+ activeTimeline.setEqlRequest(request as TimelineEqlRequestOptions);
+ activeTimeline.setEqlResponse(newTimelineResponse);
+ } else {
+ activeTimeline.setRequest(request);
+ activeTimeline.setResponse(newTimelineResponse);
+ }
}
return newTimelineResponse;
});
@@ -217,10 +250,20 @@ export const useTimelineEvents = ({
activeTimeline.setPageName(pageName);
abortCtrl.current.abort();
setLoading(false);
- prevTimelineRequest.current = activeTimeline.getRequest();
- refetch.current = asyncSearch.bind(null, activeTimeline.getRequest());
+
+ if (request.language === 'eql') {
+ prevTimelineRequest.current = activeTimeline.getEqlRequest();
+ refetch.current = asyncSearch.bind(null, activeTimeline.getEqlRequest());
+ } else {
+ prevTimelineRequest.current = activeTimeline.getRequest();
+ refetch.current = asyncSearch.bind(null, activeTimeline.getRequest());
+ }
+
setTimelineResponse((prevResp) => {
- const resp = activeTimeline.getResponse();
+ const resp =
+ request.language === 'eql'
+ ? activeTimeline.getEqlResponse()
+ : activeTimeline.getResponse();
if (resp != null) {
return {
...resp,
@@ -230,7 +273,9 @@ export const useTimelineEvents = ({
}
return prevResp;
});
- if (activeTimeline.getResponse() != null) {
+ if (request.language !== 'eql' && activeTimeline.getResponse() != null) {
+ return;
+ } else if (request.language === 'eql' && activeTimeline.getEqlResponse() != null) {
return;
}
}
@@ -253,12 +298,33 @@ export const useTimelineEvents = ({
}
setTimelineRequest((prevRequest) => {
+ const prevEqlRequest = prevRequest as TimelineEqlRequestOptions;
const prevSearchParameters = {
defaultIndex: prevRequest?.defaultIndex ?? [],
filterQuery: prevRequest?.filterQuery ?? '',
querySize: prevRequest?.pagination.querySize ?? 0,
sort: prevRequest?.sort ?? initSortDefault,
timerange: prevRequest?.timerange ?? {},
+ ...(prevEqlRequest?.eventCategoryField
+ ? {
+ eventCategoryField: prevEqlRequest?.eventCategoryField,
+ }
+ : {}),
+ ...(prevEqlRequest?.size
+ ? {
+ size: prevEqlRequest?.size,
+ }
+ : {}),
+ ...(prevEqlRequest?.tiebreakerField
+ ? {
+ tiebreakerField: prevEqlRequest?.tiebreakerField,
+ }
+ : {}),
+ ...(prevEqlRequest?.timestampField
+ ? {
+ timestampField: prevEqlRequest?.timestampField,
+ }
+ : {}),
};
const currentSearchParameters = {
@@ -271,6 +337,7 @@ export const useTimelineEvents = ({
from: startDate,
to: endDate,
},
+ ...(eqlOptions ? eqlOptions : {}),
};
const newActivePage = deepEqual(prevSearchParameters, currentSearchParameters)
@@ -288,12 +355,14 @@ export const useTimelineEvents = ({
activePage: newActivePage,
querySize: limit,
},
+ language,
sort,
timerange: {
interval: '12h',
from: startDate,
to: endDate,
},
+ ...(eqlOptions ? eqlOptions : {}),
};
if (activePage !== newActivePage) {
@@ -313,8 +382,10 @@ export const useTimelineEvents = ({
activePage,
docValueFields,
endDate,
+ eqlOptions,
filterQuery,
id,
+ language,
limit,
startDate,
sort,
@@ -325,11 +396,11 @@ export const useTimelineEvents = ({
if (
id !== TimelineId.active ||
timerangeKind === 'absolute' ||
- !deepEqual(prevTimelineRequest, timelineRequest)
+ !deepEqual(prevTimelineRequest.current, timelineRequest)
) {
timelineSearch(timelineRequest);
}
- }, [id, prevTimelineRequest, timelineRequest, timelineSearch, timerangeKind]);
+ }, [id, timelineRequest, timelineSearch, timerangeKind]);
/*
cleanup timeline events response when the filters were removed completely
diff --git a/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts b/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts
index 7b95387873fd39..97bae5717c7d63 100644
--- a/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts
+++ b/x-pack/plugins/security_solution/public/timelines/containers/one/index.gql_query.ts
@@ -58,6 +58,13 @@ export const oneTimelineQuery = gql`
end
}
description
+ eqlOptions {
+ eventCategoryField
+ tiebreakerField
+ timestampField
+ query
+ size
+ }
eventType
eventIdToNoteIds {
eventId
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
index c9e3c8305a30de..11e9a625d05d04 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/actions.ts
@@ -17,7 +17,7 @@ import {
import { SerializedFilterQuery } from '../../../common/store/types';
import { KqlMode, TimelineModel, ColumnHeaderOptions } from './model';
-import { TimelineNonEcsData } from '../../../../common/search_strategy/timeline';
+import { FieldsEqlOptions, TimelineNonEcsData } from '../../../../common/search_strategy/timeline';
import {
TimelineEventsType,
TimelineExpandedDetail,
@@ -289,3 +289,9 @@ export const toggleModalSaveTimeline = actionCreator<{
id: string;
showModalSaveTimeline: boolean;
}>('TOGGLE_MODAL_SAVE_TIMELINE');
+
+export const updateEqlOptions = actionCreator<{
+ id: string;
+ field: FieldsEqlOptions;
+ value: string | null;
+}>('UPDATE_EQL_OPTIONS_TIMELINE');
diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
index 44a5c05e398f16..77d5b5d3caed86 100644
--- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
+++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts
@@ -15,13 +15,21 @@ import { SubsetTimelineModel, TimelineModel } from './model';
// normalizeTimeRange uses getTimeRangeSettings which cannot be used outside Kibana context if the uiSettings is not false
const { from: start, to: end } = normalizeTimeRange({ from: '', to: '' }, false);
-export const timelineDefaults: SubsetTimelineModel & Pick