mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Event Log] add rel=primary to saved objects for query targets (#64615)
resolves https://github.com/elastic/kibana/issues/62668 Adds a property named `rel` to the nested saved objects in the event documents, whose value should not be set, or set to `primary`. The query by saved object function changes to only match event documents with that saved objects if it has the `rel: primary` value. This is used to limit searching alerting's executeAction event document with only the alert saved object, and not the action saved object (this document has an alert and action saved object). The alert saved object has the `rel: primary` field set, and the action does not. Previously, those documents were returned with a query of the action saved object.
This commit is contained in:
parent
4e58d7ae0a
commit
f85b3898f6
17 changed files with 135 additions and 12 deletions
|
@ -17,7 +17,7 @@ import {
|
|||
import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server';
|
||||
import { SpacesServiceSetup } from '../../../spaces/server';
|
||||
import { EVENT_LOG_ACTIONS } from '../plugin';
|
||||
import { IEvent, IEventLogger } from '../../../event_log/server';
|
||||
import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
|
||||
|
||||
export interface ActionExecutorContext {
|
||||
logger: Logger;
|
||||
|
@ -110,7 +110,16 @@ export class ActionExecutor {
|
|||
const actionLabel = `${actionTypeId}:${actionId}: ${name}`;
|
||||
const event: IEvent = {
|
||||
event: { action: EVENT_LOG_ACTIONS.execute },
|
||||
kibana: { saved_objects: [{ type: 'action', id: actionId, ...namespace }] },
|
||||
kibana: {
|
||||
saved_objects: [
|
||||
{
|
||||
rel: SAVED_OBJECT_REL_PRIMARY,
|
||||
type: 'action',
|
||||
id: actionId,
|
||||
...namespace,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
eventLogger.startTiming(event);
|
||||
|
|
|
@ -95,6 +95,7 @@ test('calls actionsPlugin.execute per selected action', async () => {
|
|||
"saved_objects": Array [
|
||||
Object {
|
||||
"id": "1",
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
},
|
||||
Object {
|
||||
|
|
|
@ -9,7 +9,7 @@ import { AlertAction, State, Context, AlertType } from '../types';
|
|||
import { Logger } from '../../../../../src/core/server';
|
||||
import { transformActionParams } from './transform_action_params';
|
||||
import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server';
|
||||
import { IEventLogger, IEvent } from '../../../event_log/server';
|
||||
import { IEventLogger, IEvent, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
|
||||
import { EVENT_LOG_ACTIONS } from '../plugin';
|
||||
|
||||
interface CreateExecutionHandlerOptions {
|
||||
|
@ -96,7 +96,7 @@ export function createExecutionHandler({
|
|||
instance_id: alertInstanceId,
|
||||
},
|
||||
saved_objects: [
|
||||
{ type: 'alert', id: alertId, ...namespace },
|
||||
{ rel: SAVED_OBJECT_REL_PRIMARY, type: 'alert', id: alertId, ...namespace },
|
||||
{ type: 'action', id: action.id, ...namespace },
|
||||
],
|
||||
},
|
||||
|
|
|
@ -172,6 +172,7 @@ describe('Task Runner', () => {
|
|||
Object {
|
||||
"id": "1",
|
||||
"namespace": undefined,
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
},
|
||||
],
|
||||
|
@ -234,6 +235,7 @@ describe('Task Runner', () => {
|
|||
Object {
|
||||
"id": "1",
|
||||
"namespace": undefined,
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
},
|
||||
],
|
||||
|
@ -254,6 +256,7 @@ describe('Task Runner', () => {
|
|||
Object {
|
||||
"id": "1",
|
||||
"namespace": undefined,
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
},
|
||||
],
|
||||
|
@ -274,6 +277,7 @@ describe('Task Runner', () => {
|
|||
Object {
|
||||
"id": "1",
|
||||
"namespace": undefined,
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
},
|
||||
Object {
|
||||
|
@ -351,6 +355,7 @@ describe('Task Runner', () => {
|
|||
Object {
|
||||
"id": "1",
|
||||
"namespace": undefined,
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
},
|
||||
],
|
||||
|
@ -371,6 +376,7 @@ describe('Task Runner', () => {
|
|||
Object {
|
||||
"id": "1",
|
||||
"namespace": undefined,
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
},
|
||||
],
|
||||
|
@ -568,6 +574,7 @@ describe('Task Runner', () => {
|
|||
Object {
|
||||
"id": "1",
|
||||
"namespace": undefined,
|
||||
"rel": "primary",
|
||||
"type": "alert",
|
||||
},
|
||||
],
|
||||
|
|
|
@ -25,7 +25,7 @@ import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/
|
|||
import { taskInstanceToAlertTaskInstance } from './alert_task_instance';
|
||||
import { AlertInstances } from '../alert_instance/alert_instance';
|
||||
import { EVENT_LOG_ACTIONS } from '../plugin';
|
||||
import { IEvent, IEventLogger } from '../../../event_log/server';
|
||||
import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
|
||||
import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error';
|
||||
|
||||
const FALLBACK_RETRY_INTERVAL: IntervalSchedule = { interval: '5m' };
|
||||
|
@ -174,7 +174,16 @@ export class TaskRunner {
|
|||
const alertLabel = `${this.alertType.id}:${alertId}: '${name}'`;
|
||||
const event: IEvent = {
|
||||
event: { action: EVENT_LOG_ACTIONS.execute },
|
||||
kibana: { saved_objects: [{ type: 'alert', id: alertId, namespace }] },
|
||||
kibana: {
|
||||
saved_objects: [
|
||||
{
|
||||
rel: SAVED_OBJECT_REL_PRIMARY,
|
||||
type: 'alert',
|
||||
id: alertId,
|
||||
namespace,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
eventLogger.startTiming(event);
|
||||
|
||||
|
@ -393,7 +402,14 @@ function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInst
|
|||
alerting: {
|
||||
instance_id: id,
|
||||
},
|
||||
saved_objects: [{ type: 'alert', id: params.alertId, namespace: params.namespace }],
|
||||
saved_objects: [
|
||||
{
|
||||
rel: SAVED_OBJECT_REL_PRIMARY,
|
||||
type: 'alert',
|
||||
id: params.alertId,
|
||||
namespace: params.namespace,
|
||||
},
|
||||
],
|
||||
},
|
||||
message,
|
||||
};
|
||||
|
|
|
@ -86,6 +86,10 @@
|
|||
},
|
||||
"saved_objects": {
|
||||
"properties": {
|
||||
"rel": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 1024
|
||||
},
|
||||
"namespace": {
|
||||
"type": "keyword",
|
||||
"ignore_above": 1024
|
||||
|
|
|
@ -65,6 +65,7 @@ export const EventSchema = schema.maybe(
|
|||
saved_objects: schema.maybe(
|
||||
schema.arrayOf(
|
||||
schema.object({
|
||||
rel: ecsString(),
|
||||
namespace: ecsString(),
|
||||
id: ecsString(),
|
||||
type: ecsString(),
|
||||
|
|
|
@ -24,6 +24,11 @@ exports.EcsKibanaExtensionsMappings = {
|
|||
saved_objects: {
|
||||
type: 'nested',
|
||||
properties: {
|
||||
// relation; currently only supports "primary" or not set
|
||||
rel: {
|
||||
type: 'keyword',
|
||||
ignore_above: 1024,
|
||||
},
|
||||
// relevant kibana space
|
||||
namespace: {
|
||||
type: 'keyword',
|
||||
|
@ -58,6 +63,7 @@ exports.EcsEventLogProperties = [
|
|||
'user.name',
|
||||
'kibana.server_uuid',
|
||||
'kibana.alerting.instance_id',
|
||||
'kibana.saved_objects.rel',
|
||||
'kibana.saved_objects.namespace',
|
||||
'kibana.saved_objects.id',
|
||||
'kibana.saved_objects.name',
|
||||
|
|
|
@ -236,6 +236,13 @@ describe('queryEventsBySavedObject', () => {
|
|||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.rel': {
|
||||
value: 'primary',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.type': {
|
||||
|
@ -319,6 +326,13 @@ describe('queryEventsBySavedObject', () => {
|
|||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.rel': {
|
||||
value: 'primary',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.type': {
|
||||
|
@ -388,6 +402,13 @@ describe('queryEventsBySavedObject', () => {
|
|||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.rel': {
|
||||
value: 'primary',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.type': {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { reject, isUndefined } from 'lodash';
|
||||
import { SearchResponse, Client } from 'elasticsearch';
|
||||
import { Logger, ClusterClient } from '../../../../../src/core/server';
|
||||
import { IEvent } from '../types';
|
||||
import { IEvent, SAVED_OBJECT_REL_PRIMARY } from '../types';
|
||||
import { FindOptionsType } from '../event_log_client';
|
||||
|
||||
export type EsClusterClient = Pick<ClusterClient, 'callAsInternalUser' | 'asScoped'>;
|
||||
|
@ -155,6 +155,13 @@ export class ClusterClientAdapter {
|
|||
query: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.rel': {
|
||||
value: SAVED_OBJECT_REL_PRIMARY,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.saved_objects.type': {
|
||||
|
|
|
@ -150,6 +150,35 @@ describe('EventLogger', () => {
|
|||
message = await waitForLogMessage(systemLogger);
|
||||
expect(message).toMatch(/invalid event logged.*action.*undefined.*/);
|
||||
});
|
||||
|
||||
test('logs warnings when writing invalid events', async () => {
|
||||
service.registerProviderActions('provider', ['action-a']);
|
||||
eventLogger = service.getLogger({});
|
||||
|
||||
eventLogger.logEvent(({ event: { PROVIDER: 'provider' } } as unknown) as IEvent);
|
||||
let message = await waitForLogMessage(systemLogger);
|
||||
expect(message).toMatch(/invalid event logged.*provider.*undefined.*/);
|
||||
|
||||
const event: IEvent = {
|
||||
event: {
|
||||
provider: 'provider',
|
||||
action: 'action-a',
|
||||
},
|
||||
kibana: {
|
||||
saved_objects: [
|
||||
{
|
||||
rel: 'ZZZ-primary',
|
||||
namespace: 'default',
|
||||
type: 'event_log_test',
|
||||
id: '123',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
eventLogger.logEvent(event);
|
||||
message = await waitForLogMessage(systemLogger);
|
||||
expect(message).toMatch(/invalid rel property.*ZZZ-primary.*/);
|
||||
});
|
||||
});
|
||||
|
||||
// return the next logged event; throw if not an event
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
ECS_VERSION,
|
||||
EventSchema,
|
||||
} from './types';
|
||||
import { SAVED_OBJECT_REL_PRIMARY } from './types';
|
||||
|
||||
type SystemLogger = Plugin['systemLogger'];
|
||||
|
||||
|
@ -118,6 +119,8 @@ const RequiredEventSchema = schema.object({
|
|||
action: schema.string({ minLength: 1 }),
|
||||
});
|
||||
|
||||
const ValidSavedObjectRels = new Set([undefined, SAVED_OBJECT_REL_PRIMARY]);
|
||||
|
||||
function validateEvent(eventLogService: IEventLogService, event: IEvent): IValidatedEvent {
|
||||
if (event?.event == null) {
|
||||
throw new Error(`no "event" property`);
|
||||
|
@ -137,7 +140,17 @@ function validateEvent(eventLogService: IEventLogService, event: IEvent): IValid
|
|||
}
|
||||
|
||||
// could throw an error
|
||||
return EventSchema.validate(event);
|
||||
const result = EventSchema.validate(event);
|
||||
|
||||
if (result?.kibana?.saved_objects?.length) {
|
||||
for (const so of result?.kibana?.saved_objects) {
|
||||
if (!ValidSavedObjectRels.has(so.rel)) {
|
||||
throw new Error(`invalid rel property in saved_objects: "${so.rel}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export const EVENT_LOGGED_PREFIX = `event logged: `;
|
||||
|
|
|
@ -8,6 +8,12 @@ import { PluginInitializerContext } from 'src/core/server';
|
|||
import { ConfigSchema } from './types';
|
||||
import { Plugin } from './plugin';
|
||||
|
||||
export { IEventLogService, IEventLogger, IEventLogClientService, IEvent } from './types';
|
||||
export {
|
||||
IEventLogService,
|
||||
IEventLogger,
|
||||
IEventLogClientService,
|
||||
IEvent,
|
||||
SAVED_OBJECT_REL_PRIMARY,
|
||||
} from './types';
|
||||
export const config = { schema: ConfigSchema };
|
||||
export const plugin = (context: PluginInitializerContext) => new Plugin(context);
|
||||
|
|
|
@ -13,6 +13,8 @@ import { IEvent } from '../generated/schemas';
|
|||
import { FindOptionsType } from './event_log_client';
|
||||
import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter';
|
||||
|
||||
export const SAVED_OBJECT_REL_PRIMARY = 'primary';
|
||||
|
||||
export const ConfigSchema = schema.object({
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
logEntries: schema.boolean({ defaultValue: false }),
|
||||
|
|
|
@ -40,7 +40,7 @@ export const logEventRoute = (router: IRouter, eventLogger: IEventLogger, logger
|
|||
} catch (ex) {
|
||||
logger.info(`log event error: ${ex}`);
|
||||
await context.core.savedObjects.client.create('event_log_test', {}, { id });
|
||||
logger.info(`created saved object`);
|
||||
logger.info(`created saved object ${id}`);
|
||||
}
|
||||
eventLogger.logEvent(event);
|
||||
logger.info(`logged`);
|
||||
|
|
|
@ -205,6 +205,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
kibana: {
|
||||
saved_objects: [
|
||||
{
|
||||
rel: 'primary',
|
||||
namespace: 'default',
|
||||
type: 'event_log_test',
|
||||
id,
|
||||
|
|
|
@ -101,7 +101,7 @@ export default function({ getService }: FtrProviderContext) {
|
|||
const eventId = uuid.v4();
|
||||
const event: IEvent = {
|
||||
event: { action: 'action1', provider: 'provider4' },
|
||||
kibana: { saved_objects: [{ type: 'event_log_test', id: eventId }] },
|
||||
kibana: { saved_objects: [{ rel: 'primary', type: 'event_log_test', id: eventId }] },
|
||||
};
|
||||
await logTestEvent(eventId, event);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue