mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
## Summary Main ticket ([Internal link](https://github.com/elastic/security-team/issues/12474)) These changes add attack discovery specific action variables which can be used within the action's message body. ### For each alert The list of fields that user will have access to for the `For each alert` action frequency within the action form: * `context.attack.alertIds` * `context.attack.detailsMarkdown` * `context.attack.summaryMarkdown` * `context.attack.title` * `context.attack.timestamp` * `context.attack.entitySummaryMarkdown` * `context.attack.mitreAttackTactics` <img width="1266" alt="Image" src="https://github.com/user-attachments/assets/39698e07-0a88-4b45-822a-b1f0b94da314" /> ### Summary of alerts The user has access to all alerts via `alerts.all.data`. Example of iterating over each generated attack discovery and report title with the connector name and ID: ``` Attacks: {{#alerts.all.data}} - *"{{kibana.alert.attack_discovery.title}}"* by *"{{kibana.alert.attack_discovery.api_config.name}}"* (with *id*: {{_id}}) {{/alerts.all.data}} ``` Which will result in: ``` Rule Demo 2 generated 2 alerts Attacks: - *"Credential Dumping and Malware Execution"* by *"GPT-4o (Azure OpenAI)"* (with *id*: aca8227d-c346-49d5-91c4-0fc0fe9efed3) - *"Suspicious Network Activity"* by *"GPT-4o (Azure OpenAI)"* (with *id*: bb3a113d-9172-48ea-80e0-0acc618264c8) ``` <img width="1264" alt="Image" src="https://github.com/user-attachments/assets/be784bc6-32b6-486a-9b4d-6e1ee942c80b" /> ## NOTES The feature is hidden behind the feature flag (in `kibana.dev.yml`): ``` feature_flags.overrides: securitySolution.assistantAttackDiscoverySchedulingEnabled: true ```
This commit is contained in:
parent
f00e51aefc
commit
e73c8ddcb9
10 changed files with 167 additions and 27 deletions
|
@ -180,6 +180,7 @@ describe('attackDiscoveryScheduleExecutor', () => {
|
|||
|
||||
await attackDiscoveryScheduleExecutor({ logger: mockLogger, options });
|
||||
|
||||
const { id, ...restDiscovery } = mockAttackDiscoveries[0];
|
||||
expect(services.alertsClient.report).toHaveBeenCalledWith({
|
||||
id: expect.anything(),
|
||||
actionGroup: 'default',
|
||||
|
@ -230,6 +231,7 @@ describe('attackDiscoveryScheduleExecutor', () => {
|
|||
'kibana.alert.attack_discovery.title_with_replacements':
|
||||
'Critical Malware and Phishing Alerts on host Test-Host-1',
|
||||
},
|
||||
context: { attack: restDiscovery },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -131,7 +131,13 @@ export const attackDiscoveryScheduleExecutor = async ({
|
|||
replacements,
|
||||
}),
|
||||
};
|
||||
alertsClient.report({ id: uuidv4(), actionGroup: 'default', payload });
|
||||
const { id, ...restAttack } = attack;
|
||||
alertsClient.report({
|
||||
id: uuidv4(),
|
||||
actionGroup: 'default',
|
||||
payload,
|
||||
context: { attack: restAttack },
|
||||
});
|
||||
});
|
||||
|
||||
return { state: {} };
|
||||
|
|
|
@ -95,7 +95,7 @@ export const CreateFlyout: React.FC<Props> = React.memo(({ onClose }) => {
|
|||
onClose={onClose}
|
||||
paddingSize="m"
|
||||
side="right"
|
||||
size="s"
|
||||
size="m"
|
||||
type="overlay"
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
|
|
|
@ -14,6 +14,8 @@ import * as i18n from './translations';
|
|||
import { useKibana } from '../../../../../../common/lib/kibana';
|
||||
import { useFetchScheduleRuleType } from '../../logic/use_fetch_schedule_rule_type';
|
||||
|
||||
const css = { minHeight: 600 };
|
||||
|
||||
interface Props {
|
||||
schedule: AttackDiscoverySchedule;
|
||||
}
|
||||
|
@ -31,11 +33,7 @@ export const ScheduleExecutionLogs: React.FC<Props> = React.memo(({ schedule })
|
|||
<h3>{i18n.EXECUTION_LOGS_TITLE}</h3>
|
||||
</EuiTitle>
|
||||
<EuiHorizontalRule />
|
||||
<EuiFlexGroup
|
||||
css={{ minHeight: 600 }}
|
||||
direction={'column'}
|
||||
data-test-subj={'executionEventLogs'}
|
||||
>
|
||||
<EuiFlexGroup css={css} direction={'column'} data-test-subj={'executionEventLogs'}>
|
||||
<EuiFlexItem>
|
||||
{scheduleRuleType && (
|
||||
<RuleEventLogList ruleId={schedule.id} ruleType={scheduleRuleType} />
|
||||
|
|
|
@ -194,7 +194,7 @@ export const DetailsFlyout: React.FC<Props> = React.memo(({ scheduleId, onClose
|
|||
outsideClickCloses={!isEditing}
|
||||
paddingSize="m"
|
||||
side="right"
|
||||
size="s"
|
||||
size="m"
|
||||
type="overlay"
|
||||
>
|
||||
<EuiFlyoutHeader hasBorder>
|
||||
|
|
|
@ -1,8 +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.
|
||||
*/
|
||||
|
||||
// TODO: implement editing flyout component
|
|
@ -26,6 +26,7 @@ import {
|
|||
useForm,
|
||||
useFormData,
|
||||
} from '../../../../../shared_imports';
|
||||
import { getMessageVariables } from './message_variables';
|
||||
|
||||
const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
|
@ -52,7 +53,7 @@ export const EditForm: React.FC<FormProps> = React.memo((props) => {
|
|||
schema: getSchema({ actionTypeRegistry }),
|
||||
});
|
||||
|
||||
const [{ value }] = useFormData({ form });
|
||||
const [{ value }] = useFormData<{ value: AttackDiscoveryScheduleSchema }>({ form });
|
||||
const { isValid, setFieldValue, submit } = form;
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -97,11 +98,7 @@ export const EditForm: React.FC<FormProps> = React.memo((props) => {
|
|||
});
|
||||
|
||||
const messageVariables = useMemo(() => {
|
||||
return {
|
||||
state: [],
|
||||
params: [],
|
||||
context: [],
|
||||
};
|
||||
return getMessageVariables();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -141,7 +138,6 @@ export const EditForm: React.FC<FormProps> = React.memo((props) => {
|
|||
component={RuleActionsField}
|
||||
componentProps={{
|
||||
messageVariables,
|
||||
summaryMessageVariables: messageVariables,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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 { getMessageVariables } from './message_variables';
|
||||
|
||||
describe('getMessageVariables', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return `context.attack.alertIds` action variable', () => {
|
||||
const variables = getMessageVariables().context;
|
||||
expect(variables).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: 'attack.alertIds' })])
|
||||
);
|
||||
});
|
||||
|
||||
it('should return `context.attack.detailsMarkdown` action variable', () => {
|
||||
const variables = getMessageVariables().context;
|
||||
expect(variables).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: 'attack.detailsMarkdown' })])
|
||||
);
|
||||
});
|
||||
|
||||
it('should return `context.attack.summaryMarkdown` action variable', () => {
|
||||
const variables = getMessageVariables().context;
|
||||
expect(variables).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: 'attack.summaryMarkdown' })])
|
||||
);
|
||||
});
|
||||
|
||||
it('should return `context.attack.title` action variable', () => {
|
||||
const variables = getMessageVariables().context;
|
||||
expect(variables).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: 'attack.title' })])
|
||||
);
|
||||
});
|
||||
|
||||
it('should return `context.attack.timestamp` action variable', () => {
|
||||
const variables = getMessageVariables().context;
|
||||
expect(variables).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: 'attack.timestamp' })])
|
||||
);
|
||||
});
|
||||
|
||||
it('should return `context.attack.entitySummaryMarkdown` action variable', () => {
|
||||
const variables = getMessageVariables().context;
|
||||
expect(variables).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: 'attack.entitySummaryMarkdown' })])
|
||||
);
|
||||
});
|
||||
|
||||
it('should return `context.attack.mitreAttackTactics` action variable', () => {
|
||||
const variables = getMessageVariables().context;
|
||||
expect(variables).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ name: 'attack.mitreAttackTactics' })])
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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';
|
||||
import type { ActionVariables } from '@kbn/triggers-actions-ui-types';
|
||||
|
||||
export const getMessageVariables = (): ActionVariables => {
|
||||
return {
|
||||
state: [],
|
||||
params: [],
|
||||
context: [
|
||||
{
|
||||
name: 'attack.alertIds',
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.schedule.messageVariable.attack.alertIds',
|
||||
{
|
||||
defaultMessage: 'The alert IDs that the attack discovery is based on',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'attack.detailsMarkdown',
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.schedule.messageVariable.attack.detailsMarkdown',
|
||||
{
|
||||
defaultMessage:
|
||||
'Details of the attack with bulleted markdown that always uses special syntax for field names and values from the source data',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'attack.summaryMarkdown',
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.schedule.messageVariable.attack.summaryMarkdown',
|
||||
{
|
||||
defaultMessage: 'A markdown summary of attack discovery, using the same syntax',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'attack.title',
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.schedule.messageVariable.attack.title',
|
||||
{
|
||||
defaultMessage: 'A title for the attack discovery, in plain text',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'attack.timestamp',
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.schedule.messageVariable.attack.timestamp',
|
||||
{
|
||||
defaultMessage: 'The time the attack discovery was generated',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'attack.entitySummaryMarkdown',
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.schedule.messageVariable.attack.entitySummaryMarkdown',
|
||||
{
|
||||
defaultMessage:
|
||||
'A short (no more than a sentence) summary of the attack discovery featuring only the host.name and user.name fields (when they are applicable), using the same syntax',
|
||||
}
|
||||
),
|
||||
},
|
||||
{
|
||||
name: 'attack.mitreAttackTactics',
|
||||
description: i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.schedule.messageVariable.attack.mitreAttackTactics',
|
||||
{
|
||||
defaultMessage: 'An array of MITRE ATT&CK tactic for the attack discovery',
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
|
@ -8,28 +8,28 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const STATUS_SUCCESS = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.settingsFlyout.schedule.tableColumn.status.successLabel',
|
||||
'xpack.securitySolution.attackDiscovery.settingsFlyout.schedule.utils.status.successLabel',
|
||||
{
|
||||
defaultMessage: 'Success',
|
||||
}
|
||||
);
|
||||
|
||||
export const STATUS_FAILED = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.settingsFlyout.schedule.tableColumn.status.failedLabel',
|
||||
'xpack.securitySolution.attackDiscovery.settingsFlyout.schedule.utils.status.failedLabel',
|
||||
{
|
||||
defaultMessage: 'Failed',
|
||||
}
|
||||
);
|
||||
|
||||
export const STATUS_WARNING = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.settingsFlyout.schedule.tableColumn.status.warningLabel',
|
||||
'xpack.securitySolution.attackDiscovery.settingsFlyout.schedule.utils.status.warningLabel',
|
||||
{
|
||||
defaultMessage: 'Warning',
|
||||
}
|
||||
);
|
||||
|
||||
export const STATUS_UNKNOWN = i18n.translate(
|
||||
'xpack.securitySolution.attackDiscovery.settingsFlyout.schedule.tableColumn.status.unknownLabel',
|
||||
'xpack.securitySolution.attackDiscovery.settingsFlyout.schedule.utils.status.unknownLabel',
|
||||
{
|
||||
defaultMessage: 'Unknown',
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue