mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Event Log] Populated rule.* ECS fields for alert events. (#101132)
* [Event Log] Populated rule.* ECS fields for alert events. * added mappings * changed the params passing * fixed tests * fixed type checks * used kibanaVersion for version event rule * fixed typos * fixed tests * fixed tests * fixed tests * fixed tests * fixed jest tests * removed references * removed not populated fields * fixed tests * fixed tests * fixed tests
This commit is contained in:
parent
befd30ff6c
commit
e55a93ce58
10 changed files with 696 additions and 37 deletions
|
@ -67,7 +67,7 @@ const createExecutionHandlerParams: jest.Mocked<
|
|||
>
|
||||
> = {
|
||||
actionsPlugin: mockActionsPlugin,
|
||||
spaceId: 'default',
|
||||
spaceId: 'test1',
|
||||
alertId: '1',
|
||||
alertName: 'name-of-alert',
|
||||
tags: ['tag-A', 'tag-B'],
|
||||
|
@ -130,7 +130,7 @@ test('enqueues execution per selected action', async () => {
|
|||
"apiKey": "MTIzOmFiYw==",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
"alertVal": "My 1 name-of-alert default tag-A,tag-B 2 goes here",
|
||||
"alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here",
|
||||
"contextVal": "My goes here",
|
||||
"foo": true,
|
||||
"stateVal": "My goes here",
|
||||
|
@ -142,7 +142,7 @@ test('enqueues execution per selected action', async () => {
|
|||
},
|
||||
"type": "SAVED_OBJECT",
|
||||
},
|
||||
"spaceId": "default",
|
||||
"spaceId": "test1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
@ -154,6 +154,10 @@ test('enqueues execution per selected action', async () => {
|
|||
Object {
|
||||
"event": Object {
|
||||
"action": "execute-action",
|
||||
"category": Array [
|
||||
"alerts",
|
||||
],
|
||||
"kind": "alert",
|
||||
},
|
||||
"kibana": Object {
|
||||
"alerting": Object {
|
||||
|
@ -164,18 +168,28 @@ test('enqueues execution per selected action', async () => {
|
|||
"saved_objects": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"namespace": "test1",
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
"type_id": "test",
|
||||
},
|
||||
Object {
|
||||
"id": "1",
|
||||
"namespace": "test1",
|
||||
"type": "action",
|
||||
"type_id": "test",
|
||||
},
|
||||
],
|
||||
},
|
||||
"message": "alert: test:1: 'name-of-alert' instanceId: '2' scheduled actionGroup: 'default' action: test:1",
|
||||
"rule": Object {
|
||||
"category": "test",
|
||||
"id": "1",
|
||||
"license": "basic",
|
||||
"name": "name-of-alert",
|
||||
"namespace": "test1",
|
||||
"ruleset": "alerts",
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
|
@ -183,10 +197,10 @@ test('enqueues execution per selected action', async () => {
|
|||
|
||||
expect(jest.requireMock('./inject_action_params').injectActionParams).toHaveBeenCalledWith({
|
||||
ruleId: '1',
|
||||
spaceId: 'default',
|
||||
spaceId: 'test1',
|
||||
actionTypeId: 'test',
|
||||
actionParams: {
|
||||
alertVal: 'My 1 name-of-alert default tag-A,tag-B 2 goes here',
|
||||
alertVal: 'My 1 name-of-alert test1 tag-A,tag-B 2 goes here',
|
||||
contextVal: 'My goes here',
|
||||
foo: true,
|
||||
stateVal: 'My goes here',
|
||||
|
@ -233,7 +247,7 @@ test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () =>
|
|||
id: '1',
|
||||
type: 'alert',
|
||||
}),
|
||||
spaceId: 'default',
|
||||
spaceId: 'test1',
|
||||
apiKey: createExecutionHandlerParams.apiKey,
|
||||
});
|
||||
});
|
||||
|
@ -308,7 +322,7 @@ test('context attribute gets parameterized', async () => {
|
|||
"apiKey": "MTIzOmFiYw==",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
"alertVal": "My 1 name-of-alert default tag-A,tag-B 2 goes here",
|
||||
"alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here",
|
||||
"contextVal": "My context-val goes here",
|
||||
"foo": true,
|
||||
"stateVal": "My goes here",
|
||||
|
@ -320,7 +334,7 @@ test('context attribute gets parameterized', async () => {
|
|||
},
|
||||
"type": "SAVED_OBJECT",
|
||||
},
|
||||
"spaceId": "default",
|
||||
"spaceId": "test1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
@ -341,7 +355,7 @@ test('state attribute gets parameterized', async () => {
|
|||
"apiKey": "MTIzOmFiYw==",
|
||||
"id": "1",
|
||||
"params": Object {
|
||||
"alertVal": "My 1 name-of-alert default tag-A,tag-B 2 goes here",
|
||||
"alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here",
|
||||
"contextVal": "My goes here",
|
||||
"foo": true,
|
||||
"stateVal": "My state-val goes here",
|
||||
|
@ -353,7 +367,7 @@ test('state attribute gets parameterized', async () => {
|
|||
},
|
||||
"type": "SAVED_OBJECT",
|
||||
},
|
||||
"spaceId": "default",
|
||||
"spaceId": "test1",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
|
|
@ -174,7 +174,11 @@ export function createExecutionHandler<
|
|||
const namespace = spaceId === 'default' ? {} : { namespace: spaceId };
|
||||
|
||||
const event: IEvent = {
|
||||
event: { action: EVENT_LOG_ACTIONS.executeAction },
|
||||
event: {
|
||||
action: EVENT_LOG_ACTIONS.executeAction,
|
||||
kind: 'alert',
|
||||
category: [alertType.producer],
|
||||
},
|
||||
kibana: {
|
||||
alerting: {
|
||||
instance_id: alertInstanceId,
|
||||
|
@ -192,6 +196,14 @@ export function createExecutionHandler<
|
|||
{ type: 'action', id: action.id, type_id: action.actionTypeId, ...namespace },
|
||||
],
|
||||
},
|
||||
rule: {
|
||||
id: alertId,
|
||||
license: alertType.minimumLicenseRequired,
|
||||
category: alertType.id,
|
||||
ruleset: alertType.producer,
|
||||
...namespace,
|
||||
name: alertName,
|
||||
},
|
||||
};
|
||||
|
||||
event.message = `alert: ${alertLabel} instanceId: '${alertInstanceId}' scheduled ${
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -303,6 +303,10 @@ export class TaskRunner<
|
|||
event.message = `alert executed: ${alertLabel}`;
|
||||
event.event = event.event || {};
|
||||
event.event.outcome = 'success';
|
||||
event.rule = {
|
||||
...event.rule,
|
||||
name: alert.name,
|
||||
};
|
||||
|
||||
// Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object
|
||||
const instancesWithScheduledActions = pickBy(
|
||||
|
@ -337,7 +341,8 @@ export class TaskRunner<
|
|||
alertId,
|
||||
alertLabel,
|
||||
namespace,
|
||||
ruleTypeId: alert.alertTypeId,
|
||||
ruleType: alertType,
|
||||
rule: alert,
|
||||
});
|
||||
|
||||
if (!muteAll) {
|
||||
|
@ -493,7 +498,11 @@ export class TaskRunner<
|
|||
// explicitly set execute timestamp so it will be before other events
|
||||
// generated here (new-instance, schedule-action, etc)
|
||||
'@timestamp': runDate,
|
||||
event: { action: EVENT_LOG_ACTIONS.execute },
|
||||
event: {
|
||||
action: EVENT_LOG_ACTIONS.execute,
|
||||
kind: 'alert',
|
||||
category: [this.alertType.producer],
|
||||
},
|
||||
kibana: {
|
||||
saved_objects: [
|
||||
{
|
||||
|
@ -505,6 +514,13 @@ export class TaskRunner<
|
|||
},
|
||||
],
|
||||
},
|
||||
rule: {
|
||||
id: alertId,
|
||||
license: this.alertType.minimumLicenseRequired,
|
||||
category: this.alertType.id,
|
||||
ruleset: this.alertType.producer,
|
||||
namespace,
|
||||
},
|
||||
};
|
||||
eventLogger.startTiming(event);
|
||||
|
||||
|
@ -665,7 +681,19 @@ interface GenerateNewAndRecoveredInstanceEventsParams<
|
|||
alertId: string;
|
||||
alertLabel: string;
|
||||
namespace: string | undefined;
|
||||
ruleTypeId: string;
|
||||
ruleType: NormalizedAlertType<
|
||||
AlertTypeParams,
|
||||
AlertTypeState,
|
||||
{
|
||||
[x: string]: unknown;
|
||||
},
|
||||
{
|
||||
[x: string]: unknown;
|
||||
},
|
||||
string,
|
||||
string
|
||||
>;
|
||||
rule: SanitizedAlert<AlertTypeParams>;
|
||||
}
|
||||
|
||||
function generateNewAndRecoveredInstanceEvents<
|
||||
|
@ -679,7 +707,8 @@ function generateNewAndRecoveredInstanceEvents<
|
|||
currentAlertInstances,
|
||||
originalAlertInstances,
|
||||
recoveredAlertInstances,
|
||||
ruleTypeId,
|
||||
rule,
|
||||
ruleType,
|
||||
} = params;
|
||||
const originalAlertInstanceIds = Object.keys(originalAlertInstances);
|
||||
const currentAlertInstanceIds = Object.keys(currentAlertInstances);
|
||||
|
@ -746,6 +775,8 @@ function generateNewAndRecoveredInstanceEvents<
|
|||
const event: IEvent = {
|
||||
event: {
|
||||
action,
|
||||
kind: 'alert',
|
||||
category: [ruleType.producer],
|
||||
...(state?.start ? { start: state.start as string } : {}),
|
||||
...(state?.end ? { end: state.end as string } : {}),
|
||||
...(state?.duration !== undefined ? { duration: state.duration as number } : {}),
|
||||
|
@ -761,12 +792,20 @@ function generateNewAndRecoveredInstanceEvents<
|
|||
rel: SAVED_OBJECT_REL_PRIMARY,
|
||||
type: 'alert',
|
||||
id: alertId,
|
||||
type_id: ruleTypeId,
|
||||
type_id: ruleType.id,
|
||||
namespace,
|
||||
},
|
||||
],
|
||||
},
|
||||
message,
|
||||
rule: {
|
||||
id: rule.id,
|
||||
license: ruleType.minimumLicenseRequired,
|
||||
category: ruleType.id,
|
||||
ruleset: ruleType.producer,
|
||||
namespace,
|
||||
name: rule.name,
|
||||
},
|
||||
};
|
||||
eventLogger.logEvent(event);
|
||||
}
|
||||
|
|
|
@ -101,11 +101,20 @@ Below is a document in the expected structure, with descriptions of the fields:
|
|||
logger: "name of the logger",
|
||||
},
|
||||
|
||||
// Rule fields. All of them are supported.
|
||||
// Rule fields.
|
||||
// https://www.elastic.co/guide/en/ecs/current/ecs-rule.html
|
||||
rule: {
|
||||
// Fields currently are populated:
|
||||
id: "a823fd56-5467-4727-acb1-66809737d943", // rule id
|
||||
category: "test", // rule type id
|
||||
license: "basic", // rule type minimumLicenseRequired
|
||||
name: "rule-name", //
|
||||
ruleset: "alerts", // rule type producer
|
||||
// Fields currently are not populated:
|
||||
author: ["Elastic"],
|
||||
id: "a823fd56-5467-4727-acb1-66809737d943",
|
||||
description: "Some rule description",
|
||||
version: '1',
|
||||
uuid: "uuid"
|
||||
// etc
|
||||
},
|
||||
|
||||
|
|
|
@ -214,6 +214,10 @@
|
|||
"version": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"namespace": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -91,6 +91,7 @@ export const EventSchema = schema.maybe(
|
|||
ruleset: ecsString(),
|
||||
uuid: ecsString(),
|
||||
version: ecsString(),
|
||||
namespace: ecsString(),
|
||||
})
|
||||
),
|
||||
user: schema.maybe(
|
||||
|
|
|
@ -217,6 +217,7 @@ instanceStateValue: true
|
|||
ruleTypeId: 'test.always-firing',
|
||||
outcome: 'success',
|
||||
message: `alert executed: test.always-firing:${alertId}: 'abc'`,
|
||||
ruleObject: alertSearchResultWithoutDates,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
@ -1249,10 +1250,11 @@ instanceStateValue: true
|
|||
outcome: string;
|
||||
message: string;
|
||||
errorMessage?: string;
|
||||
ruleObject: any;
|
||||
}
|
||||
|
||||
async function validateEventLog(params: ValidateEventLogParams): Promise<void> {
|
||||
const { spaceId, alertId, ruleTypeId, outcome, message, errorMessage } = params;
|
||||
const { spaceId, alertId, outcome, message, errorMessage, ruleObject } = params;
|
||||
|
||||
const events: IValidatedEvent[] = await retry.try(async () => {
|
||||
return await getEventLog({
|
||||
|
@ -1293,10 +1295,19 @@ instanceStateValue: true
|
|||
type: 'alert',
|
||||
id: alertId,
|
||||
namespace: spaceId,
|
||||
type_id: ruleTypeId,
|
||||
type_id: ruleObject.alertInfo.ruleTypeId,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(event?.rule).to.eql({
|
||||
id: alertId,
|
||||
license: 'basic',
|
||||
category: ruleObject.alertInfo.ruleTypeId,
|
||||
ruleset: ruleObject.alertInfo.producer,
|
||||
namespace: spaceId,
|
||||
name: ruleObject.alertInfo.name,
|
||||
});
|
||||
|
||||
expect(event?.message).to.eql(message);
|
||||
|
||||
if (errorMessage) {
|
||||
|
|
|
@ -81,6 +81,13 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
errorMessage: 'Unable to decrypt attribute "apiKey"',
|
||||
status: 'error',
|
||||
reason: 'decrypt',
|
||||
rule: {
|
||||
id: alertId,
|
||||
category: response.body.rule_type_id,
|
||||
license: 'basic',
|
||||
ruleset: 'alertsFixture',
|
||||
namespace: spaceId,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -134,6 +134,14 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
outcome: 'success',
|
||||
message: `alert executed: test.patternFiring:${alertId}: 'abc'`,
|
||||
status: executeStatuses[executeCount++],
|
||||
rule: {
|
||||
id: alertId,
|
||||
category: response.body.rule_type_id,
|
||||
license: 'basic',
|
||||
ruleset: 'alertsFixture',
|
||||
namespace: Spaces.space1.id,
|
||||
name: response.body.name,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'execute-action':
|
||||
|
@ -146,6 +154,14 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
message: `alert: test.patternFiring:${alertId}: 'abc' instanceId: 'instance' scheduled actionGroup: 'default' action: test.noop:${createdAction.id}`,
|
||||
instanceId: 'instance',
|
||||
actionGroupId: 'default',
|
||||
rule: {
|
||||
id: alertId,
|
||||
category: response.body.rule_type_id,
|
||||
license: 'basic',
|
||||
ruleset: 'alertsFixture',
|
||||
namespace: Spaces.space1.id,
|
||||
name: response.body.name,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'new-instance':
|
||||
|
@ -181,6 +197,14 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
instanceId: 'instance',
|
||||
actionGroupId: 'default',
|
||||
shouldHaveEventEnd,
|
||||
rule: {
|
||||
id: alertId,
|
||||
category: response.body.rule_type_id,
|
||||
license: 'basic',
|
||||
ruleset: 'alertsFixture',
|
||||
namespace: Spaces.space1.id,
|
||||
name: response.body.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -279,6 +303,14 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
outcome: 'success',
|
||||
message: `alert executed: test.patternFiring:${alertId}: 'abc'`,
|
||||
status: executeStatuses[executeCount++],
|
||||
rule: {
|
||||
id: alertId,
|
||||
category: response.body.rule_type_id,
|
||||
license: 'basic',
|
||||
ruleset: 'alertsFixture',
|
||||
namespace: Spaces.space1.id,
|
||||
name: response.body.name,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'execute-action':
|
||||
|
@ -294,6 +326,14 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
message: `alert: test.patternFiring:${alertId}: 'abc' instanceId: 'instance' scheduled actionGroup(subgroup): 'default(${event?.kibana?.alerting?.action_subgroup})' action: test.noop:${createdAction.id}`,
|
||||
instanceId: 'instance',
|
||||
actionGroupId: 'default',
|
||||
rule: {
|
||||
id: alertId,
|
||||
category: response.body.rule_type_id,
|
||||
license: 'basic',
|
||||
ruleset: 'alertsFixture',
|
||||
namespace: Spaces.space1.id,
|
||||
name: response.body.name,
|
||||
},
|
||||
});
|
||||
break;
|
||||
case 'new-instance':
|
||||
|
@ -332,6 +372,14 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
instanceId: 'instance',
|
||||
actionGroupId: 'default',
|
||||
shouldHaveEventEnd,
|
||||
rule: {
|
||||
id: alertId,
|
||||
category: response.body.rule_type_id,
|
||||
license: 'basic',
|
||||
ruleset: 'alertsFixture',
|
||||
namespace: Spaces.space1.id,
|
||||
name: response.body.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -374,6 +422,13 @@ export default function eventLogTests({ getService }: FtrProviderContext) {
|
|||
errorMessage: 'this alert is intended to fail',
|
||||
status: 'error',
|
||||
reason: 'execute',
|
||||
rule: {
|
||||
id: alertId,
|
||||
category: response.body.rule_type_id,
|
||||
license: 'basic',
|
||||
ruleset: 'alertsFixture',
|
||||
namespace: Spaces.space1.id,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -397,10 +452,21 @@ interface ValidateEventLogParams {
|
|||
actionGroupId?: string;
|
||||
instanceId?: string;
|
||||
reason?: string;
|
||||
rule: {
|
||||
id: string;
|
||||
name?: string;
|
||||
version?: string;
|
||||
category?: string;
|
||||
reference?: string;
|
||||
author?: string[];
|
||||
license?: string;
|
||||
ruleset?: string;
|
||||
namespace?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function validateEvent(event: IValidatedEvent, params: ValidateEventLogParams): void {
|
||||
const { spaceId, savedObjects, outcome, message, errorMessage } = params;
|
||||
const { spaceId, savedObjects, outcome, message, errorMessage, rule } = params;
|
||||
const { status, actionGroupId, instanceId, reason, shouldHaveEventEnd } = params;
|
||||
|
||||
if (status) {
|
||||
|
@ -456,6 +522,8 @@ export function validateEvent(event: IValidatedEvent, params: ValidateEventLogPa
|
|||
|
||||
expect(event?.message).to.eql(message);
|
||||
|
||||
expect(event?.rule).to.eql(rule);
|
||||
|
||||
if (errorMessage) {
|
||||
expect(event?.error?.message).to.eql(errorMessage);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue