Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"data",
"dataEnhanced",
"embeddable",
"eventLog",
"features",
"taskManager",
"inspector",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 { IEvent as IEventLogEvent } from '../../../../../../event_log/server';

// https://www.elastic.co/guide/en/ecs/1.9/ecs-guidelines.html
// https://www.elastic.co/guide/en/ecs/1.9/ecs-category-field-values-reference.html
// https://www.elastic.co/guide/en/ecs/1.9/ecs-field-reference.html

export type IEcsEvent = IEventLogEvent & IEcsAdditionalFields;

interface IEcsAdditionalFields {
// https://www.elastic.co/guide/en/ecs/1.9/ecs-event.html
event?: {
dataset?: string;
created?: string;
kind?: string;
type?: string[];
severity?: number;
sequence?: number;
};

// https://www.elastic.co/guide/en/ecs/1.9/ecs-log.html
log?: {
logger?: string;
level?: string;
};

// https://www.elastic.co/guide/en/ecs/1.9/ecs-rule.html
rule?: {
id?: string;
};

// custom fields
kibana?: {
detection_engine?: {
rule_status?: string;
rule_status_severity?: number;
};
};
}

export type EcsEventKey = keyof IEcsEvent;
export type EcsEventBaseKey = '@timestamp' | 'message' | 'tags';
export type EcsEventObjectKey = Exclude<EcsEventKey, EcsEventBaseKey>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* 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 { EcsEventObjectKey, IEcsEvent } from './ecs_event';
import { RuleExecutionEventLevel, getLevelSeverity } from './rule_execution_event_levels';
import { RuleExecutionStatus, getStatusSeverity } from './rule_execution_statuses';

const EVENT_LOG_PROVIDER = 'detection-engine'; // TODO: "siem", "siem-detection-engine", "security-solution", other?
const EVENT_LOG_NAME = 'rule-execution-log'; // TODO: A more generic rule-log? A separate rule-management (rule-audit) log?

export class EcsEventBuilder {
private _result: IEcsEvent = {};

constructor() {
// TODO: Which version does event_log use? Should it be specified here or inside the event log itself?
this.ecs('1.9.0');
this.logger(EVENT_LOG_PROVIDER, EVENT_LOG_NAME);
}

/**
* Sets "@timestamp", message.
* https://www.elastic.co/guide/en/ecs/1.9/ecs-base.html
* @param eventDate When the event happened (not captured or created). Example: new Date().
* @param eventMessage Example: "Machine learning job is not started".
*/
public baseFields(eventDate: Date, eventMessage: string): EcsEventBuilder {
return this.base({
'@timestamp': eventDate.toISOString(),
message: eventMessage,
});
}

/**
* Sets event.provider, event.dataset, log.logger.
* https://www.elastic.co/guide/en/ecs/1.9/ecs-event.html
* https://www.elastic.co/guide/en/ecs/1.9/ecs-log.html
* @param logProvider 1st-level category (plugin, subsystem). Example: "detection-engine".
* @param logName 2nd-level category (feature, module). Example: "rule-execution-log".
*/
public logger(logProvider: string, logName: string): EcsEventBuilder {
return this.nested('event', {
provider: logProvider,
dataset: `${logProvider}.${logName}`,
}).nested('log', {
logger: `${logProvider}.${logName}`,
});
}

/**
* Sets log.level, event.severity.
* https://www.elastic.co/guide/en/ecs/1.9/ecs-event.html
* https://www.elastic.co/guide/en/ecs/1.9/ecs-log.html
* @param eventLevel Mapped to log.level. Example: "info", "error".
*/
public level(eventLevel: RuleExecutionEventLevel): EcsEventBuilder {
return this.nested('event', {
severity: getLevelSeverity(eventLevel),
}).nested('log', {
level: eventLevel,
});
}

/**
* Sets categorization fields: event.kind, event.type, event.action.
* https://www.elastic.co/guide/en/ecs/1.9/ecs-category-field-values-reference.html
* https://www.elastic.co/guide/en/ecs/1.9/ecs-event.html
* @param eventAction Actual event type. Example: "status-changed".
*/
public typeChange(eventAction: string): EcsEventBuilder {
return this.nested('event', {
kind: 'event',
type: ['change'],
action: eventAction,
});
}

/**
* Sets categorization fields: event.kind, event.type, event.action.
* https://www.elastic.co/guide/en/ecs/1.9/ecs-category-field-values-reference.html
* https://www.elastic.co/guide/en/ecs/1.9/ecs-event.html
* @param eventAction Actual event type. Example: "metric-search-duration-max", "metric-indexing-lookback".
*/
public typeMetric(eventAction: string): EcsEventBuilder {
return this.nested('event', {
kind: 'metric',
type: ['info'],
action: eventAction,
});
}

/**
* Sets any of the event.* fields.
* https://www.elastic.co/guide/en/ecs/1.9/ecs-event.html
*/
public event(fields: NonNullable<IEcsEvent['event']>): EcsEventBuilder {
return this.nested('event', fields);
}

/**
* https://www.elastic.co/guide/en/ecs/1.9/ecs-rule.html
* @param ruleId Dynamic rule id (alert id in the Alerting framework terminology).
* @param spaceId Kibana space id.
*/
public rule(ruleId: string, spaceId?: string): EcsEventBuilder {
const existingSavedObjectRefs = this._result.kibana?.saved_objects ?? [];
const newSavedObjectRefs = existingSavedObjectRefs.concat({
type: 'alert',
id: ruleId,
namespace: spaceId,
});

return this.nested('rule', {
id: ruleId, // TODO: "id" or "uuid"?
}).nested('kibana', {
saved_objects: newSavedObjectRefs,
});
}

/**
* Sets custom fields representing rule execution status:
* kibana.detection_engine.{rule_status, rule_status_severity}
* @param status Execution status of the rule.
*/
public ruleStatus(status: RuleExecutionStatus): EcsEventBuilder {
return this.nested('kibana', {
detection_engine: {
rule_status: status,
rule_status_severity: getStatusSeverity(status),
},
});
}

/**
* Sets ecs.version.
* https://www.elastic.co/guide/en/ecs/1.9/ecs-ecs.html
* @param version Example: 1.7.0
*/
public ecs(version: string): EcsEventBuilder {
return this.nested('ecs', {
version,
});
}

/**
* Builds and returns the final ECS event.
*/
public build(): IEcsEvent {
this.event({
created: new Date().toISOString(), // TODO: del or use eventDate?
});
return this._result;
}

private base(fields: IEcsEvent): EcsEventBuilder {
this._result = { ...this._result, ...fields };
return this;
}

private nested<K extends EcsEventObjectKey, V extends IEcsEvent[K]>(
key: K,
fields: V
): EcsEventBuilder {
this._result[key] = {
...this._result[key],
...fields,
};
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 * from './ecs_event';
export * from './ecs_event_builder';
export * from './rule_execution_event_levels';
export * from './rule_execution_statuses';
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.
*/

// -----------------------------------------------------------------------------
// Levels

export const RuleExecutionEventLevel = {
INFO: 'info',
WARNING: 'warning',
ERROR: 'error',
} as const;

export type RuleExecutionEventLevel = typeof RuleExecutionEventLevel[keyof typeof RuleExecutionEventLevel];

// -----------------------------------------------------------------------------
// Level severities

type LevelMappingTo<TValue> = Readonly<Record<RuleExecutionEventLevel, TValue>>;

const levelSeverityByLevel: LevelMappingTo<number> = Object.freeze({
[RuleExecutionEventLevel.INFO]: 10,
[RuleExecutionEventLevel.WARNING]: 20,
[RuleExecutionEventLevel.ERROR]: 30,
});

export const getLevelSeverity = (level: RuleExecutionEventLevel): number => {
return levelSeverityByLevel[level] ?? 0;
};
Original file line number Diff line number Diff line change
@@ -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 { JobStatus } from '../../../../../common/detection_engine/schemas/common/schemas';

export type RuleExecutionStatus = JobStatus;

type StatusMappingTo<TValue> = Readonly<Record<RuleExecutionStatus, TValue>>;

const statusSeverityByStatus: StatusMappingTo<number> = Object.freeze({
succeeded: 0,
'going to run': 10,
warning: 20,
'partial failure': 20,
failed: 30,
});

export const getStatusSeverity = (status: RuleExecutionStatus): number => {
return statusSeverityByStatus[status] ?? 0;
};
Loading