diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsmigrationlogger.error.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsmigrationlogger.error.md index 7536cd2b07ae6..16fbc8f4eaea3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsmigrationlogger.error.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsmigrationlogger.error.md @@ -7,5 +7,5 @@ Signature: ```typescript -error: (msg: string, meta: LogMeta) => void; +error: (msg: string, meta: Meta) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsmigrationlogger.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsmigrationlogger.md index 1b691ee8cb16d..697f8823c4966 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsmigrationlogger.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsmigrationlogger.md @@ -16,7 +16,7 @@ export interface SavedObjectsMigrationLogger | Property | Type | Description | | --- | --- | --- | | [debug](./kibana-plugin-core-server.savedobjectsmigrationlogger.debug.md) | (msg: string) => void | | -| [error](./kibana-plugin-core-server.savedobjectsmigrationlogger.error.md) | (msg: string, meta: LogMeta) => void | | +| [error](./kibana-plugin-core-server.savedobjectsmigrationlogger.error.md) | <Meta extends LogMeta = LogMeta>(msg: string, meta: Meta) => void | | | [info](./kibana-plugin-core-server.savedobjectsmigrationlogger.info.md) | (msg: string) => void | | | [warn](./kibana-plugin-core-server.savedobjectsmigrationlogger.warn.md) | (msg: string) => void | | | [warning](./kibana-plugin-core-server.savedobjectsmigrationlogger.warning.md) | (msg: string) => void | | diff --git a/packages/kbn-logging/src/ecs/agent.ts b/packages/kbn-logging/src/ecs/agent.ts new file mode 100644 index 0000000000000..0c2e7f7bbe44f --- /dev/null +++ b/packages/kbn-logging/src/ecs/agent.ts @@ -0,0 +1,21 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-agent.html + * + * @internal + */ +export interface EcsAgent { + build?: { original: string }; + ephemeral_id?: string; + id?: string; + name?: string; + type?: string; + version?: string; +} diff --git a/packages/kbn-logging/src/ecs/autonomous_system.ts b/packages/kbn-logging/src/ecs/autonomous_system.ts new file mode 100644 index 0000000000000..85569b7dbabe1 --- /dev/null +++ b/packages/kbn-logging/src/ecs/autonomous_system.ts @@ -0,0 +1,17 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-as.html + * + * @internal + */ +export interface EcsAutonomousSystem { + number?: number; + organization?: { name: string }; +} diff --git a/packages/kbn-logging/src/ecs/base.ts b/packages/kbn-logging/src/ecs/base.ts new file mode 100644 index 0000000000000..cf12cf0ea6e53 --- /dev/null +++ b/packages/kbn-logging/src/ecs/base.ts @@ -0,0 +1,19 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-base.html + * + * @internal + */ +export interface EcsBase { + ['@timestamp']: string; + labels?: Record; + message?: string; + tags?: string[]; +} diff --git a/packages/kbn-logging/src/ecs/client.ts b/packages/kbn-logging/src/ecs/client.ts new file mode 100644 index 0000000000000..ebee7826104a5 --- /dev/null +++ b/packages/kbn-logging/src/ecs/client.ts @@ -0,0 +1,36 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsAutonomousSystem } from './autonomous_system'; +import { EcsGeo } from './geo'; +import { EcsNestedUser } from './user'; + +interface NestedFields { + as?: EcsAutonomousSystem; + geo?: EcsGeo; + user?: EcsNestedUser; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-client.html + * + * @internal + */ +export interface EcsClient extends NestedFields { + address?: string; + bytes?: number; + domain?: string; + ip?: string; + mac?: string; + nat?: { ip?: string; port?: number }; + packets?: number; + port?: number; + registered_domain?: string; + subdomain?: string; + top_level_domain?: string; +} diff --git a/packages/kbn-logging/src/ecs/cloud.ts b/packages/kbn-logging/src/ecs/cloud.ts new file mode 100644 index 0000000000000..8ef15d40f5529 --- /dev/null +++ b/packages/kbn-logging/src/ecs/cloud.ts @@ -0,0 +1,23 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-cloud.html + * + * @internal + */ +export interface EcsCloud { + account?: { id?: string; name?: string }; + availability_zone?: string; + instance?: { id?: string; name?: string }; + machine?: { type: string }; + project?: { id?: string; name?: string }; + provider?: string; + region?: string; + service?: { name: string }; +} diff --git a/packages/kbn-logging/src/ecs/code_signature.ts b/packages/kbn-logging/src/ecs/code_signature.ts new file mode 100644 index 0000000000000..277c3901a4f8b --- /dev/null +++ b/packages/kbn-logging/src/ecs/code_signature.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-code_signature.html + * + * @internal + */ +export interface EcsCodeSignature { + exists?: boolean; + signing_id?: string; + status?: string; + subject_name?: string; + team_id?: string; + trusted?: boolean; + valid?: boolean; +} diff --git a/packages/kbn-logging/src/ecs/container.ts b/packages/kbn-logging/src/ecs/container.ts new file mode 100644 index 0000000000000..6c5c85e7107e3 --- /dev/null +++ b/packages/kbn-logging/src/ecs/container.ts @@ -0,0 +1,20 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-container.html + * + * @internal + */ +export interface EcsContainer { + id?: string; + image?: { name?: string; tag?: string[] }; + labels?: Record; + name?: string; + runtime?: string; +} diff --git a/packages/kbn-logging/src/ecs/destination.ts b/packages/kbn-logging/src/ecs/destination.ts new file mode 100644 index 0000000000000..6d2dbc8f431c9 --- /dev/null +++ b/packages/kbn-logging/src/ecs/destination.ts @@ -0,0 +1,36 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsAutonomousSystem } from './autonomous_system'; +import { EcsGeo } from './geo'; +import { EcsNestedUser } from './user'; + +interface NestedFields { + as?: EcsAutonomousSystem; + geo?: EcsGeo; + user?: EcsNestedUser; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-destination.html + * + * @internal + */ +export interface EcsDestination extends NestedFields { + address?: string; + bytes?: number; + domain?: string; + ip?: string; + mac?: string; + nat?: { ip?: string; port?: number }; + packets?: number; + port?: number; + registered_domain?: string; + subdomain?: string; + top_level_domain?: string; +} diff --git a/packages/kbn-logging/src/ecs/dll.ts b/packages/kbn-logging/src/ecs/dll.ts new file mode 100644 index 0000000000000..d9ffa68b3f1a5 --- /dev/null +++ b/packages/kbn-logging/src/ecs/dll.ts @@ -0,0 +1,27 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsCodeSignature } from './code_signature'; +import { EcsHash } from './hash'; +import { EcsPe } from './pe'; + +interface NestedFields { + code_signature?: EcsCodeSignature; + hash?: EcsHash; + pe?: EcsPe; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-dll.html + * + * @internal + */ +export interface EcsDll extends NestedFields { + name?: string; + path?: string; +} diff --git a/packages/kbn-logging/src/ecs/dns.ts b/packages/kbn-logging/src/ecs/dns.ts new file mode 100644 index 0000000000000..c7a0e7983376c --- /dev/null +++ b/packages/kbn-logging/src/ecs/dns.ts @@ -0,0 +1,40 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-dns.html + * + * @internal + */ +export interface EcsDns { + answers?: Answer[]; + header_flags?: string[]; + id?: number; + op_code?: string; + question?: Question; + resolved_ip?: string[]; + response_code?: string; + type?: string; +} + +interface Answer { + data: string; + class?: string; + name?: string; + ttl?: number; + type?: string; +} + +interface Question { + class?: string; + name?: string; + registered_domain?: string; + subdomain?: string; + top_level_domain?: string; + type?: string; +} diff --git a/packages/kbn-logging/src/ecs/error.ts b/packages/kbn-logging/src/ecs/error.ts new file mode 100644 index 0000000000000..aee010748ddf2 --- /dev/null +++ b/packages/kbn-logging/src/ecs/error.ts @@ -0,0 +1,20 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-error.html + * + * @internal + */ +export interface EcsError { + code?: string; + id?: string; + message?: string; + stack_trace?: string; + type?: string; +} diff --git a/packages/kbn-logging/src/ecs/event.ts b/packages/kbn-logging/src/ecs/event.ts new file mode 100644 index 0000000000000..bf711410a9dd7 --- /dev/null +++ b/packages/kbn-logging/src/ecs/event.ts @@ -0,0 +1,91 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-event.html + * + * @internal + */ +export interface EcsEvent { + action?: string; + category?: EcsEventCategory[]; + code?: string; + created?: string; + dataset?: string; + duration?: number; + end?: string; + hash?: string; + id?: string; + ingested?: string; + kind?: EcsEventKind; + module?: string; + original?: string; + outcome?: EcsEventOutcome; + provider?: string; + reason?: string; + reference?: string; + risk_score?: number; + risk_score_norm?: number; + sequence?: number; + severity?: number; + start?: string; + timezone?: string; + type?: EcsEventType[]; + url?: string; +} + +/** + * @public + */ +export type EcsEventCategory = + | 'authentication' + | 'configuration' + | 'database' + | 'driver' + | 'file' + | 'host' + | 'iam' + | 'intrusion_detection' + | 'malware' + | 'network' + | 'package' + | 'process' + | 'registry' + | 'session' + | 'web'; + +/** + * @public + */ +export type EcsEventKind = 'alert' | 'event' | 'metric' | 'state' | 'pipeline_error' | 'signal'; + +/** + * @public + */ +export type EcsEventOutcome = 'failure' | 'success' | 'unknown'; + +/** + * @public + */ +export type EcsEventType = + | 'access' + | 'admin' + | 'allowed' + | 'change' + | 'connection' + | 'creation' + | 'deletion' + | 'denied' + | 'end' + | 'error' + | 'group' + | 'info' + | 'installation' + | 'protocol' + | 'start' + | 'user'; diff --git a/packages/kbn-logging/src/ecs/file.ts b/packages/kbn-logging/src/ecs/file.ts new file mode 100644 index 0000000000000..c09121607e0a4 --- /dev/null +++ b/packages/kbn-logging/src/ecs/file.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsCodeSignature } from './code_signature'; +import { EcsHash } from './hash'; +import { EcsPe } from './pe'; +import { EcsX509 } from './x509'; + +interface NestedFields { + code_signature?: EcsCodeSignature; + hash?: EcsHash; + pe?: EcsPe; + x509?: EcsX509; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-file.html + * + * @internal + */ +export interface EcsFile extends NestedFields { + accessed?: string; + attributes?: string[]; + created?: string; + ctime?: string; + device?: string; + directory?: string; + drive_letter?: string; + extension?: string; + gid?: string; + group?: string; + inode?: string; + // Technically this is a known list, but it's massive, so we'll just accept a string for now :) + // https://www.iana.org/assignments/media-types/media-types.xhtml + mime_type?: string; + mode?: string; + mtime?: string; + name?: string; + owner?: string; + path?: string; + 'path.text'?: string; + size?: number; + target_path?: string; + 'target_path.text'?: string; + type?: string; + uid?: string; +} diff --git a/packages/kbn-logging/src/ecs/geo.ts b/packages/kbn-logging/src/ecs/geo.ts new file mode 100644 index 0000000000000..85d45ca803aee --- /dev/null +++ b/packages/kbn-logging/src/ecs/geo.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-geo.html + * + * @internal + */ +export interface EcsGeo { + city_name?: string; + continent_code?: string; + continent_name?: string; + country_iso_code?: string; + country_name?: string; + location?: GeoPoint; + name?: string; + postal_code?: string; + region_iso_code?: string; + region_name?: string; + timezone?: string; +} + +interface GeoPoint { + lat: number; + lon: number; +} diff --git a/packages/kbn-logging/src/ecs/group.ts b/packages/kbn-logging/src/ecs/group.ts new file mode 100644 index 0000000000000..e1bc339964fc0 --- /dev/null +++ b/packages/kbn-logging/src/ecs/group.ts @@ -0,0 +1,18 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-group.html + * + * @internal + */ +export interface EcsGroup { + domain?: string; + id?: string; + name?: string; +} diff --git a/packages/kbn-logging/src/ecs/hash.ts b/packages/kbn-logging/src/ecs/hash.ts new file mode 100644 index 0000000000000..2ecd49f1ca092 --- /dev/null +++ b/packages/kbn-logging/src/ecs/hash.ts @@ -0,0 +1,20 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-hash.html + * + * @internal + */ +export interface EcsHash { + md5?: string; + sha1?: string; + sha256?: string; + sha512?: string; + ssdeep?: string; +} diff --git a/packages/kbn-logging/src/ecs/host.ts b/packages/kbn-logging/src/ecs/host.ts new file mode 100644 index 0000000000000..085db30e13e7e --- /dev/null +++ b/packages/kbn-logging/src/ecs/host.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsGeo } from './geo'; +import { EcsOs } from './os'; +import { EcsNestedUser } from './user'; + +interface NestedFields { + geo?: EcsGeo; + os?: EcsOs; + /** @deprecated */ + user?: EcsNestedUser; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-host.html + * + * @internal + */ +export interface EcsHost extends NestedFields { + architecture?: string; + cpu?: { usage: number }; + disk?: Disk; + domain?: string; + hostname?: string; + id?: string; + ip?: string[]; + mac?: string[]; + name?: string; + network?: Network; + type?: string; + uptime?: number; +} + +interface Disk { + read?: { bytes: number }; + write?: { bytes: number }; +} + +interface Network { + egress?: { bytes?: number; packets?: number }; + ingress?: { bytes?: number; packets?: number }; +} diff --git a/packages/kbn-logging/src/ecs/http.ts b/packages/kbn-logging/src/ecs/http.ts new file mode 100644 index 0000000000000..c734c93318f5c --- /dev/null +++ b/packages/kbn-logging/src/ecs/http.ts @@ -0,0 +1,36 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-http.html + * + * @internal + */ +export interface EcsHttp { + request?: Request; + response?: Response; + version?: string; +} + +interface Request { + body?: { bytes?: number; content?: string }; + bytes?: number; + id?: string; + // We can't provide predefined values here because ECS requires preserving the + // original casing for anomaly detection use cases. + method?: string; + mime_type?: string; + referrer?: string; +} + +interface Response { + body?: { bytes?: number; content?: string }; + bytes?: number; + mime_type?: string; + status_code?: number; +} diff --git a/packages/kbn-logging/src/ecs/index.ts b/packages/kbn-logging/src/ecs/index.ts new file mode 100644 index 0000000000000..30da3baa43b72 --- /dev/null +++ b/packages/kbn-logging/src/ecs/index.ts @@ -0,0 +1,97 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsBase } from './base'; + +import { EcsAgent } from './agent'; +import { EcsAutonomousSystem } from './autonomous_system'; +import { EcsClient } from './client'; +import { EcsCloud } from './cloud'; +import { EcsContainer } from './container'; +import { EcsDestination } from './destination'; +import { EcsDns } from './dns'; +import { EcsError } from './error'; +import { EcsEvent } from './event'; +import { EcsFile } from './file'; +import { EcsGroup } from './group'; +import { EcsHost } from './host'; +import { EcsHttp } from './http'; +import { EcsLog } from './log'; +import { EcsNetwork } from './network'; +import { EcsObserver } from './observer'; +import { EcsOrganization } from './organization'; +import { EcsPackage } from './package'; +import { EcsProcess } from './process'; +import { EcsRegistry } from './registry'; +import { EcsRelated } from './related'; +import { EcsRule } from './rule'; +import { EcsServer } from './server'; +import { EcsService } from './service'; +import { EcsSource } from './source'; +import { EcsThreat } from './threat'; +import { EcsTls } from './tls'; +import { EcsTracing } from './tracing'; +import { EcsUrl } from './url'; +import { EcsUser } from './user'; +import { EcsUserAgent } from './user_agent'; +import { EcsVulnerability } from './vulnerability'; + +export { EcsEventCategory, EcsEventKind, EcsEventOutcome, EcsEventType } from './event'; + +interface EcsField { + /** + * These typings were written as of ECS 1.9.0. + * Don't change this value without checking the rest + * of the types to conform to that ECS version. + * + * https://www.elastic.co/guide/en/ecs/1.9/index.html + */ + version: '1.9.0'; +} + +/** + * Represents the full ECS schema. + * + * @public + */ +export type Ecs = EcsBase & + EcsTracing & { + ecs: EcsField; + + agent?: EcsAgent; + as?: EcsAutonomousSystem; + client?: EcsClient; + cloud?: EcsCloud; + container?: EcsContainer; + destination?: EcsDestination; + dns?: EcsDns; + error?: EcsError; + event?: EcsEvent; + file?: EcsFile; + group?: EcsGroup; + host?: EcsHost; + http?: EcsHttp; + log?: EcsLog; + network?: EcsNetwork; + observer?: EcsObserver; + organization?: EcsOrganization; + package?: EcsPackage; + process?: EcsProcess; + registry?: EcsRegistry; + related?: EcsRelated; + rule?: EcsRule; + server?: EcsServer; + service?: EcsService; + source?: EcsSource; + threat?: EcsThreat; + tls?: EcsTls; + url?: EcsUrl; + user?: EcsUser; + user_agent?: EcsUserAgent; + vulnerability?: EcsVulnerability; + }; diff --git a/packages/kbn-logging/src/ecs/interface.ts b/packages/kbn-logging/src/ecs/interface.ts new file mode 100644 index 0000000000000..49b33e8338184 --- /dev/null +++ b/packages/kbn-logging/src/ecs/interface.ts @@ -0,0 +1,18 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-interface.html + * + * @internal + */ +export interface EcsInterface { + alias?: string; + id?: string; + name?: string; +} diff --git a/packages/kbn-logging/src/ecs/log.ts b/packages/kbn-logging/src/ecs/log.ts new file mode 100644 index 0000000000000..8bc2e4982e96c --- /dev/null +++ b/packages/kbn-logging/src/ecs/log.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-log.html + * + * @internal + */ +export interface EcsLog { + file?: { path: string }; + level?: string; + logger?: string; + origin?: Origin; + original?: string; + syslog?: Syslog; +} + +interface Origin { + file?: { line?: number; name?: string }; + function?: string; +} + +interface Syslog { + facility?: { code?: number; name?: string }; + priority?: number; + severity?: { code?: number; name?: string }; +} diff --git a/packages/kbn-logging/src/ecs/network.ts b/packages/kbn-logging/src/ecs/network.ts new file mode 100644 index 0000000000000..912427b6cdb7e --- /dev/null +++ b/packages/kbn-logging/src/ecs/network.ts @@ -0,0 +1,33 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsVlan } from './vlan'; + +interface NestedFields { + inner?: { vlan?: EcsVlan }; + vlan?: EcsVlan; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-network.html + * + * @internal + */ +export interface EcsNetwork extends NestedFields { + application?: string; + bytes?: number; + community_id?: string; + direction?: string; + forwarded_ip?: string; + iana_number?: string; + name?: string; + packets?: number; + protocol?: string; + transport?: string; + type?: string; +} diff --git a/packages/kbn-logging/src/ecs/observer.ts b/packages/kbn-logging/src/ecs/observer.ts new file mode 100644 index 0000000000000..be2636d15dcdf --- /dev/null +++ b/packages/kbn-logging/src/ecs/observer.ts @@ -0,0 +1,56 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsGeo } from './geo'; +import { EcsInterface } from './interface'; +import { EcsOs } from './os'; +import { EcsVlan } from './vlan'; + +interface NestedFields { + egress?: NestedEgressFields; + geo?: EcsGeo; + ingress?: NestedIngressFields; + os?: EcsOs; +} + +interface NestedEgressFields { + interface?: EcsInterface; + vlan?: EcsVlan; +} + +interface NestedIngressFields { + interface?: EcsInterface; + vlan?: EcsVlan; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-observer.html + * + * @internal + */ +export interface EcsObserver extends NestedFields { + egress?: Egress; + hostname?: string; + ingress?: Ingress; + ip?: string[]; + mac?: string[]; + name?: string; + product?: string; + serial_number?: string; + type?: string; + vendor?: string; + version?: string; +} + +interface Egress extends NestedEgressFields { + zone?: string; +} + +interface Ingress extends NestedIngressFields { + zone?: string; +} diff --git a/packages/kbn-logging/src/ecs/organization.ts b/packages/kbn-logging/src/ecs/organization.ts new file mode 100644 index 0000000000000..370e6b2646a2f --- /dev/null +++ b/packages/kbn-logging/src/ecs/organization.ts @@ -0,0 +1,17 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-organization.html + * + * @internal + */ +export interface EcsOrganization { + id?: string; + name?: string; +} diff --git a/packages/kbn-logging/src/ecs/os.ts b/packages/kbn-logging/src/ecs/os.ts new file mode 100644 index 0000000000000..342eb14264fd3 --- /dev/null +++ b/packages/kbn-logging/src/ecs/os.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-os.html + * + * @internal + */ +export interface EcsOs { + family?: string; + full?: string; + kernel?: string; + name?: string; + platform?: string; + type?: string; + version?: string; +} diff --git a/packages/kbn-logging/src/ecs/package.ts b/packages/kbn-logging/src/ecs/package.ts new file mode 100644 index 0000000000000..10528066f3f29 --- /dev/null +++ b/packages/kbn-logging/src/ecs/package.ts @@ -0,0 +1,28 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-package.html + * + * @internal + */ +export interface EcsPackage { + architecture?: string; + build_version?: string; + checksum?: string; + description?: string; + install_scope?: string; + installed?: string; + license?: string; + name?: string; + path?: string; + reference?: string; + size?: number; + type?: string; + version?: string; +} diff --git a/packages/kbn-logging/src/ecs/pe.ts b/packages/kbn-logging/src/ecs/pe.ts new file mode 100644 index 0000000000000..bd53b7048a50d --- /dev/null +++ b/packages/kbn-logging/src/ecs/pe.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-pe.html + * + * @internal + */ +export interface EcsPe { + architecture?: string; + company?: string; + description?: string; + file_version?: string; + imphash?: string; + original_file_name?: string; + product?: string; +} diff --git a/packages/kbn-logging/src/ecs/process.ts b/packages/kbn-logging/src/ecs/process.ts new file mode 100644 index 0000000000000..9a034c30fd531 --- /dev/null +++ b/packages/kbn-logging/src/ecs/process.ts @@ -0,0 +1,41 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsCodeSignature } from './code_signature'; +import { EcsHash } from './hash'; +import { EcsPe } from './pe'; + +interface NestedFields { + code_signature?: EcsCodeSignature; + hash?: EcsHash; + parent?: EcsProcess; + pe?: EcsPe; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-process.html + * + * @internal + */ +export interface EcsProcess extends NestedFields { + args?: string[]; + args_count?: number; + command_line?: string; + entity_id?: string; + executable?: string; + exit_code?: number; + name?: string; + pgid?: number; + pid?: number; + ppid?: number; + start?: string; + thread?: { id?: number; name?: string }; + title?: string; + uptime?: number; + working_directory?: string; +} diff --git a/packages/kbn-logging/src/ecs/registry.ts b/packages/kbn-logging/src/ecs/registry.ts new file mode 100644 index 0000000000000..ba7ef699e2cdb --- /dev/null +++ b/packages/kbn-logging/src/ecs/registry.ts @@ -0,0 +1,26 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-registry.html + * + * @internal + */ +export interface EcsRegistry { + data?: Data; + hive?: string; + key?: string; + path?: string; + value?: string; +} + +interface Data { + bytes?: string; + strings?: string[]; + type?: string; +} diff --git a/packages/kbn-logging/src/ecs/related.ts b/packages/kbn-logging/src/ecs/related.ts new file mode 100644 index 0000000000000..33c3ff50540ce --- /dev/null +++ b/packages/kbn-logging/src/ecs/related.ts @@ -0,0 +1,19 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-related.html + * + * @internal + */ +export interface EcsRelated { + hash?: string[]; + hosts?: string[]; + ip?: string[]; + user?: string[]; +} diff --git a/packages/kbn-logging/src/ecs/rule.ts b/packages/kbn-logging/src/ecs/rule.ts new file mode 100644 index 0000000000000..c6bf1ce96552a --- /dev/null +++ b/packages/kbn-logging/src/ecs/rule.ts @@ -0,0 +1,25 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-rule.html + * + * @internal + */ +export interface EcsRule { + author?: string[]; + category?: string; + description?: string; + id?: string; + license?: string; + name?: string; + reference?: string; + ruleset?: string; + uuid?: string; + version?: string; +} diff --git a/packages/kbn-logging/src/ecs/server.ts b/packages/kbn-logging/src/ecs/server.ts new file mode 100644 index 0000000000000..9b2a9b1a11b42 --- /dev/null +++ b/packages/kbn-logging/src/ecs/server.ts @@ -0,0 +1,36 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsAutonomousSystem } from './autonomous_system'; +import { EcsGeo } from './geo'; +import { EcsNestedUser } from './user'; + +interface NestedFields { + as?: EcsAutonomousSystem; + geo?: EcsGeo; + user?: EcsNestedUser; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-server.html + * + * @internal + */ +export interface EcsServer extends NestedFields { + address?: string; + bytes?: number; + domain?: string; + ip?: string; + mac?: string; + nat?: { ip?: string; port?: number }; + packets?: number; + port?: number; + registered_domain?: string; + subdomain?: string; + top_level_domain?: string; +} diff --git a/packages/kbn-logging/src/ecs/service.ts b/packages/kbn-logging/src/ecs/service.ts new file mode 100644 index 0000000000000..4cd79e928c076 --- /dev/null +++ b/packages/kbn-logging/src/ecs/service.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-service.html + * + * @internal + */ +export interface EcsService { + ephemeral_id?: string; + id?: string; + name?: string; + node?: { name: string }; + state?: string; + type?: string; + version?: string; +} diff --git a/packages/kbn-logging/src/ecs/source.ts b/packages/kbn-logging/src/ecs/source.ts new file mode 100644 index 0000000000000..9ec7e2521d0b9 --- /dev/null +++ b/packages/kbn-logging/src/ecs/source.ts @@ -0,0 +1,36 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsAutonomousSystem } from './autonomous_system'; +import { EcsGeo } from './geo'; +import { EcsNestedUser } from './user'; + +interface NestedFields { + as?: EcsAutonomousSystem; + geo?: EcsGeo; + user?: EcsNestedUser; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-source.html + * + * @internal + */ +export interface EcsSource extends NestedFields { + address?: string; + bytes?: number; + domain?: string; + ip?: string; + mac?: string; + nat?: { ip?: string; port?: number }; + packets?: number; + port?: number; + registered_domain?: string; + subdomain?: string; + top_level_domain?: string; +} diff --git a/packages/kbn-logging/src/ecs/threat.ts b/packages/kbn-logging/src/ecs/threat.ts new file mode 100644 index 0000000000000..ac6033949fccd --- /dev/null +++ b/packages/kbn-logging/src/ecs/threat.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-threat.html + * + * @internal + */ +export interface EcsThreat { + framework?: string; + tactic?: Tactic; + technique?: Technique; +} + +interface Tactic { + id?: string[]; + name?: string[]; + reference?: string[]; +} + +interface Technique { + id?: string[]; + name?: string[]; + reference?: string[]; + subtechnique?: Technique; +} diff --git a/packages/kbn-logging/src/ecs/tls.ts b/packages/kbn-logging/src/ecs/tls.ts new file mode 100644 index 0000000000000..b04d03d650908 --- /dev/null +++ b/packages/kbn-logging/src/ecs/tls.ts @@ -0,0 +1,64 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsX509 } from './x509'; + +interface NestedClientFields { + x509?: EcsX509; +} + +interface NestedServerFields { + x509?: EcsX509; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-tls.html + * + * @internal + */ +export interface EcsTls { + cipher?: string; + client?: Client; + curve?: string; + established?: boolean; + next_protocol?: string; + resumed?: boolean; + server?: Server; + version?: string; + version_protocol?: string; +} + +interface Client extends NestedClientFields { + certificate?: string; + certificate_chain?: string[]; + hash?: Hash; + issuer?: string; + ja3?: string; + not_after?: string; + not_before?: string; + server_name?: string; + subject?: string; + supported_ciphers?: string[]; +} + +interface Server extends NestedServerFields { + certificate?: string; + certificate_chain?: string[]; + hash?: Hash; + issuer?: string; + ja3s?: string; + not_after?: string; + not_before?: string; + subject?: string; +} + +interface Hash { + md5?: string; + sha1?: string; + sha256?: string; +} diff --git a/packages/kbn-logging/src/ecs/tracing.ts b/packages/kbn-logging/src/ecs/tracing.ts new file mode 100644 index 0000000000000..1abbbd4b4c8a2 --- /dev/null +++ b/packages/kbn-logging/src/ecs/tracing.ts @@ -0,0 +1,23 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Unlike other ECS field sets, tracing fields are not nested under the field + * set name (i.e. `trace.id` is valid, `tracing.trace.id` is not). So, like + * the base fields, we will need to do an intersection with these types at + * the root level. + * + * https://www.elastic.co/guide/en/ecs/1.9/ecs-tracing.html + * + * @internal + */ +export interface EcsTracing { + span?: { id?: string }; + trace?: { id?: string }; + transaction?: { id?: string }; +} diff --git a/packages/kbn-logging/src/ecs/url.ts b/packages/kbn-logging/src/ecs/url.ts new file mode 100644 index 0000000000000..5985b28a4f6c3 --- /dev/null +++ b/packages/kbn-logging/src/ecs/url.ts @@ -0,0 +1,29 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-url.html + * + * @internal + */ +export interface EcsUrl { + domain?: string; + extension?: string; + fragment?: string; + full?: string; + original?: string; + password?: string; + path?: string; + port?: number; + query?: string; + registered_domain?: string; + scheme?: string; + subdomain?: string; + top_level_domain?: string; + username?: string; +} diff --git a/packages/kbn-logging/src/ecs/user.ts b/packages/kbn-logging/src/ecs/user.ts new file mode 100644 index 0000000000000..3ab0c946b49b7 --- /dev/null +++ b/packages/kbn-logging/src/ecs/user.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsGroup } from './group'; + +interface NestedFields { + group?: EcsGroup; +} + +/** + * `User` is unlike most other fields which can be reused in multiple places + * in that ECS places restrictions on which individual properties can be reused; + * + * Specifically, `changes`, `effective`, and `target` may be used if `user` is + * placed at the root level, but not if it is nested inside another field like + * `destination`. A more detailed explanation of these nuances can be found at: + * + * https://www.elastic.co/guide/en/ecs/1.9/ecs-user-usage.html + * + * As a result, we need to export a separate `NestedUser` type to import into + * other interfaces internally. This contains the reusable subset of properties + * from `User`. + * + * @internal + */ +export interface EcsNestedUser extends NestedFields { + domain?: string; + email?: string; + full_name?: string; + hash?: string; + id?: string; + name?: string; + roles?: string[]; +} + +/** + * @internal + */ +export interface EcsUser extends EcsNestedUser { + changes?: EcsNestedUser; + effective?: EcsNestedUser; + target?: EcsNestedUser; +} diff --git a/packages/kbn-logging/src/ecs/user_agent.ts b/packages/kbn-logging/src/ecs/user_agent.ts new file mode 100644 index 0000000000000..f77b3ba9e1f0f --- /dev/null +++ b/packages/kbn-logging/src/ecs/user_agent.ts @@ -0,0 +1,25 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsOs } from './os'; + +interface NestedFields { + os?: EcsOs; +} + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-user_agent.html + * + * @internal + */ +export interface EcsUserAgent extends NestedFields { + device?: { name: string }; + name?: string; + original?: string; + version?: string; +} diff --git a/packages/kbn-logging/src/ecs/vlan.ts b/packages/kbn-logging/src/ecs/vlan.ts new file mode 100644 index 0000000000000..646f8ee17fd03 --- /dev/null +++ b/packages/kbn-logging/src/ecs/vlan.ts @@ -0,0 +1,17 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-vlan.html + * + * @internal + */ +export interface EcsVlan { + id?: string; + name?: string; +} diff --git a/packages/kbn-logging/src/ecs/vulnerability.ts b/packages/kbn-logging/src/ecs/vulnerability.ts new file mode 100644 index 0000000000000..2c26d557d2ba9 --- /dev/null +++ b/packages/kbn-logging/src/ecs/vulnerability.ts @@ -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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-vulnerability.html + * + * @internal + */ +export interface EcsVulnerability { + category?: string[]; + classification?: string; + description?: string; + enumeration?: string; + id?: string; + reference?: string; + report_id?: string; + scanner?: { vendor: string }; + score?: Score; + severity?: string; +} + +interface Score { + base?: number; + environmental?: number; + temporal?: number; + version?: string; +} diff --git a/packages/kbn-logging/src/ecs/x509.ts b/packages/kbn-logging/src/ecs/x509.ts new file mode 100644 index 0000000000000..35bc1b458579a --- /dev/null +++ b/packages/kbn-logging/src/ecs/x509.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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * https://www.elastic.co/guide/en/ecs/1.9/ecs-x509.html + * + * @internal + */ +export interface EcsX509 { + alternative_names?: string[]; + issuer?: Issuer; + not_after?: string; + not_before?: string; + public_key_algorithm?: string; + public_key_curve?: string; + public_key_exponent?: number; + public_key_size?: number; + serial_number?: string; + signature_algorithm?: string; + subject?: Subject; + version_number?: string; +} + +interface Issuer { + common_name?: string[]; + country?: string[]; + distinguished_name?: string; + locality?: string[]; + organization?: string[]; + organizational_unit?: string[]; + state_or_province?: string[]; +} + +interface Subject { + common_name?: string[]; + country?: string[]; + distinguished_name?: string; + locality?: string[]; + organization?: string[]; + organizational_unit?: string[]; + state_or_province?: string[]; +} diff --git a/packages/kbn-logging/src/index.ts b/packages/kbn-logging/src/index.ts index 048a95395e5c6..075e18f99afe3 100644 --- a/packages/kbn-logging/src/index.ts +++ b/packages/kbn-logging/src/index.ts @@ -8,7 +8,9 @@ export { LogLevel, LogLevelId } from './log_level'; export { LogRecord } from './log_record'; -export { Logger, LogMeta } from './logger'; +export { Logger } from './logger'; +export { LogMeta } from './log_meta'; export { LoggerFactory } from './logger_factory'; export { Layout } from './layout'; export { Appender, DisposableAppender } from './appenders'; +export { Ecs, EcsEventCategory, EcsEventKind, EcsEventOutcome, EcsEventType } from './ecs'; diff --git a/packages/kbn-logging/src/log_meta.ts b/packages/kbn-logging/src/log_meta.ts new file mode 100644 index 0000000000000..7822792c7fbeb --- /dev/null +++ b/packages/kbn-logging/src/log_meta.ts @@ -0,0 +1,87 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EcsBase } from './ecs/base'; + +import { EcsAgent } from './ecs/agent'; +import { EcsAutonomousSystem } from './ecs/autonomous_system'; +import { EcsClient } from './ecs/client'; +import { EcsCloud } from './ecs/cloud'; +import { EcsContainer } from './ecs/container'; +import { EcsDestination } from './ecs/destination'; +import { EcsDns } from './ecs/dns'; +import { EcsError } from './ecs/error'; +import { EcsEvent } from './ecs/event'; +import { EcsFile } from './ecs/file'; +import { EcsGroup } from './ecs/group'; +import { EcsHost } from './ecs/host'; +import { EcsHttp } from './ecs/http'; +import { EcsLog } from './ecs/log'; +import { EcsNetwork } from './ecs/network'; +import { EcsObserver } from './ecs/observer'; +import { EcsOrganization } from './ecs/organization'; +import { EcsPackage } from './ecs/package'; +import { EcsProcess } from './ecs/process'; +import { EcsRegistry } from './ecs/registry'; +import { EcsRelated } from './ecs/related'; +import { EcsRule } from './ecs/rule'; +import { EcsServer } from './ecs/server'; +import { EcsService } from './ecs/service'; +import { EcsSource } from './ecs/source'; +import { EcsThreat } from './ecs/threat'; +import { EcsTls } from './ecs/tls'; +import { EcsTracing } from './ecs/tracing'; +import { EcsUrl } from './ecs/url'; +import { EcsUser } from './ecs/user'; +import { EcsUserAgent } from './ecs/user_agent'; +import { EcsVulnerability } from './ecs/vulnerability'; + +/** + * Represents the ECS schema with the following reserved keys excluded: + * - `ecs` + * - `@timestamp` + * - `message` + * - `log.level` + * - `log.logger` + * + * @public + */ +export type LogMeta = Omit & + EcsTracing & { + agent?: EcsAgent; + as?: EcsAutonomousSystem; + client?: EcsClient; + cloud?: EcsCloud; + container?: EcsContainer; + destination?: EcsDestination; + dns?: EcsDns; + error?: EcsError; + event?: EcsEvent; + file?: EcsFile; + group?: EcsGroup; + host?: EcsHost; + http?: EcsHttp; + log?: Omit; + network?: EcsNetwork; + observer?: EcsObserver; + organization?: EcsOrganization; + package?: EcsPackage; + process?: EcsProcess; + registry?: EcsRegistry; + related?: EcsRelated; + rule?: EcsRule; + server?: EcsServer; + service?: EcsService; + source?: EcsSource; + threat?: EcsThreat; + tls?: EcsTls; + url?: EcsUrl; + user?: EcsUser; + user_agent?: EcsUserAgent; + vulnerability?: EcsVulnerability; + }; diff --git a/packages/kbn-logging/src/logger.ts b/packages/kbn-logging/src/logger.ts index dad4fb07c6cfa..fda3cf45b9d79 100644 --- a/packages/kbn-logging/src/logger.ts +++ b/packages/kbn-logging/src/logger.ts @@ -6,17 +6,9 @@ * Side Public License, v 1. */ +import { LogMeta } from './log_meta'; import { LogRecord } from './log_record'; -/** - * Contextual metadata - * - * @public - */ -export interface LogMeta { - [key: string]: any; -} - /** * Logger exposes all the necessary methods to log any type of information and * this is the interface used by the logging consumers including plugins. @@ -30,28 +22,28 @@ export interface Logger { * @param message - The log message * @param meta - */ - trace(message: string, meta?: LogMeta): void; + trace(message: string, meta?: Meta): void; /** * Log messages useful for debugging and interactive investigation * @param message - The log message * @param meta - */ - debug(message: string, meta?: LogMeta): void; + debug(message: string, meta?: Meta): void; /** * Logs messages related to general application flow * @param message - The log message * @param meta - */ - info(message: string, meta?: LogMeta): void; + info(message: string, meta?: Meta): void; /** * Logs abnormal or unexpected errors or messages * @param errorOrMessage - An Error object or message string to log * @param meta - */ - warn(errorOrMessage: string | Error, meta?: LogMeta): void; + warn(errorOrMessage: string | Error, meta?: Meta): void; /** * Logs abnormal or unexpected errors or messages that caused a failure in the application flow @@ -59,7 +51,7 @@ export interface Logger { * @param errorOrMessage - An Error object or message string to log * @param meta - */ - error(errorOrMessage: string | Error, meta?: LogMeta): void; + error(errorOrMessage: string | Error, meta?: Meta): void; /** * Logs abnormal or unexpected errors or messages that caused an unrecoverable failure @@ -67,7 +59,7 @@ export interface Logger { * @param errorOrMessage - An Error object or message string to log * @param meta - */ - fatal(errorOrMessage: string | Error, meta?: LogMeta): void; + fatal(errorOrMessage: string | Error, meta?: Meta): void; /** @internal */ log(record: LogRecord): void; diff --git a/src/core/server/environment/write_pid_file.ts b/src/core/server/environment/write_pid_file.ts index b7d47111a4d53..46096ca347e8a 100644 --- a/src/core/server/environment/write_pid_file.ts +++ b/src/core/server/environment/write_pid_file.ts @@ -31,13 +31,23 @@ export const writePidFile = async ({ if (pidConfig.exclusive) { throw new Error(message); } else { - logger.warn(message, { path, pid }); + logger.warn(message, { + process: { + pid: process.pid, + path, + }, + }); } } await writeFile(path, pid); - logger.debug(`wrote pid file to ${path}`, { path, pid }); + logger.debug(`wrote pid file to ${path}`, { + process: { + pid: process.pid, + path, + }, + }); const clean = once(() => { unlink(path); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index cd7d7ccc5aeff..62a4217da6d13 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -303,7 +303,7 @@ export class HttpServer { const log = this.logger.get('http', 'server', 'response'); this.handleServerResponseEvent = (request) => { - const { message, ...meta } = getEcsResponseLog(request, this.log); + const { message, meta } = getEcsResponseLog(request, this.log); log.debug(message!, meta); }; diff --git a/src/core/server/http/logging/get_response_log.test.ts b/src/core/server/http/logging/get_response_log.test.ts index 64241ff44fc6b..5f749220138d7 100644 --- a/src/core/server/http/logging/get_response_log.test.ts +++ b/src/core/server/http/logging/get_response_log.test.ts @@ -81,7 +81,8 @@ describe('getEcsResponseLog', () => { }, }); const result = getEcsResponseLog(req, logger); - expect(result.http.response.responseTime).toBe(1000); + // @ts-expect-error ECS custom field + expect(result.meta.http.response.responseTime).toBe(1000); }); test('with response.info.responded', () => { @@ -92,14 +93,16 @@ describe('getEcsResponseLog', () => { }, }); const result = getEcsResponseLog(req, logger); - expect(result.http.response.responseTime).toBe(500); + // @ts-expect-error ECS custom field + expect(result.meta.http.response.responseTime).toBe(500); }); test('excludes responseTime from message if none is provided', () => { const req = createMockHapiRequest(); const result = getEcsResponseLog(req, logger); expect(result.message).toMatchInlineSnapshot(`"GET /path 200 - 1.2KB"`); - expect(result.http.response.responseTime).toBeUndefined(); + // @ts-expect-error ECS custom field + expect(result.meta.http.response.responseTime).toBeUndefined(); }); }); @@ -112,7 +115,7 @@ describe('getEcsResponseLog', () => { }, }); const result = getEcsResponseLog(req, logger); - expect(result.url.query).toMatchInlineSnapshot(`"a=hello&b=world"`); + expect(result.meta.url!.query).toMatchInlineSnapshot(`"a=hello&b=world"`); expect(result.message).toMatchInlineSnapshot(`"GET /path?a=hello&b=world 200 - 1.2KB"`); }); @@ -121,7 +124,7 @@ describe('getEcsResponseLog', () => { query: { a: '¡hola!' }, }); const result = getEcsResponseLog(req, logger); - expect(result.url.query).toMatchInlineSnapshot(`"a=%C2%A1hola!"`); + expect(result.meta.url!.query).toMatchInlineSnapshot(`"a=%C2%A1hola!"`); expect(result.message).toMatchInlineSnapshot(`"GET /path?a=%C2%A1hola! 200 - 1.2KB"`); }); }); @@ -145,7 +148,7 @@ describe('getEcsResponseLog', () => { response: Boom.badRequest(), }); const result = getEcsResponseLog(req, logger); - expect(result.http.response.status_code).toBe(400); + expect(result.meta.http!.response!.status_code).toBe(400); }); describe('filters sensitive headers', () => { @@ -155,14 +158,16 @@ describe('getEcsResponseLog', () => { response: { headers: { 'content-length': 123, 'set-cookie': 'c' } }, }); const result = getEcsResponseLog(req, logger); - expect(result.http.request.headers).toMatchInlineSnapshot(` + // @ts-expect-error ECS custom field + expect(result.meta.http.request.headers).toMatchInlineSnapshot(` Object { "authorization": "[REDACTED]", "cookie": "[REDACTED]", "user-agent": "hi", } `); - expect(result.http.response.headers).toMatchInlineSnapshot(` + // @ts-expect-error ECS custom field + expect(result.meta.http.response.headers).toMatchInlineSnapshot(` Object { "content-length": 123, "set-cookie": "[REDACTED]", @@ -196,9 +201,12 @@ describe('getEcsResponseLog', () => { } `); - responseLog.http.request.headers.a = 'testA'; - responseLog.http.request.headers.b[1] = 'testB'; - responseLog.http.request.headers.c = 'testC'; + // @ts-expect-error ECS custom field + responseLog.meta.http.request.headers.a = 'testA'; + // @ts-expect-error ECS custom field + responseLog.meta.http.request.headers.b[1] = 'testB'; + // @ts-expect-error ECS custom field + responseLog.meta.http.request.headers.c = 'testC'; expect(reqHeaders).toMatchInlineSnapshot(` Object { "a": "foo", @@ -244,48 +252,41 @@ describe('getEcsResponseLog', () => { }); describe('ecs', () => { - test('specifies correct ECS version', () => { - const req = createMockHapiRequest(); - const result = getEcsResponseLog(req, logger); - expect(result.ecs.version).toBe('1.7.0'); - }); - test('provides an ECS-compatible response', () => { const req = createMockHapiRequest(); const result = getEcsResponseLog(req, logger); expect(result).toMatchInlineSnapshot(` Object { - "client": Object { - "ip": undefined, - }, - "ecs": Object { - "version": "1.7.0", - }, - "http": Object { - "request": Object { - "headers": Object { - "user-agent": "", - }, - "method": "GET", - "mime_type": "application/json", - "referrer": "localhost:5601/app/home", + "message": "GET /path 200 - 1.2KB", + "meta": Object { + "client": Object { + "ip": undefined, }, - "response": Object { - "body": Object { - "bytes": 1234, + "http": Object { + "request": Object { + "headers": Object { + "user-agent": "", + }, + "method": "GET", + "mime_type": "application/json", + "referrer": "localhost:5601/app/home", + }, + "response": Object { + "body": Object { + "bytes": 1234, + }, + "headers": Object {}, + "responseTime": undefined, + "status_code": 200, }, - "headers": Object {}, - "responseTime": undefined, - "status_code": 200, }, - }, - "message": "GET /path 200 - 1.2KB", - "url": Object { - "path": "/path", - "query": "", - }, - "user_agent": Object { - "original": "", + "url": Object { + "path": "/path", + "query": "", + }, + "user_agent": Object { + "original": "", + }, }, } `); diff --git a/src/core/server/http/logging/get_response_log.ts b/src/core/server/http/logging/get_response_log.ts index 57c02e05bebff..37ee618e43395 100644 --- a/src/core/server/http/logging/get_response_log.ts +++ b/src/core/server/http/logging/get_response_log.ts @@ -11,10 +11,9 @@ import { isBoom } from '@hapi/boom'; import type { Request } from '@hapi/hapi'; import numeral from '@elastic/numeral'; import { LogMeta } from '@kbn/logging'; -import { EcsEvent, Logger } from '../../logging'; +import { Logger } from '../../logging'; import { getResponsePayloadBytes } from './get_payload_size'; -const ECS_VERSION = '1.7.0'; const FORBIDDEN_HEADERS = ['authorization', 'cookie', 'set-cookie']; const REDACTED_HEADER_TEXT = '[REDACTED]'; @@ -44,7 +43,7 @@ function cloneAndFilterHeaders(headers?: HapiHeaders) { * * @internal */ -export function getEcsResponseLog(request: Request, log: Logger): LogMeta { +export function getEcsResponseLog(request: Request, log: Logger) { const { path, response } = request; const method = request.method.toUpperCase(); @@ -66,9 +65,7 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta { const bytes = getResponsePayloadBytes(response, log); const bytesMsg = bytes ? ` - ${numeral(bytes).format('0.0b')}` : ''; - const meta: EcsEvent = { - ecs: { version: ECS_VERSION }, - message: `${method} ${pathWithQuery} ${status_code}${responseTimeMsg}${bytesMsg}`, + const meta: LogMeta = { client: { ip: request.info.remoteAddress, }, @@ -77,7 +74,7 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta { method, mime_type: request.mime, referrer: request.info.referrer, - // @ts-expect-error Headers are not yet part of ECS: https://github.com/elastic/ecs/issues/232. + // @ts-expect-error ECS custom field: https://github.com/elastic/ecs/issues/232. headers: requestHeaders, }, response: { @@ -85,7 +82,7 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta { bytes, }, status_code, - // @ts-expect-error Headers are not yet part of ECS: https://github.com/elastic/ecs/issues/232. + // @ts-expect-error ECS custom field: https://github.com/elastic/ecs/issues/232. headers: responseHeaders, // responseTime is a custom non-ECS field responseTime: !isNaN(responseTime) ? responseTime : undefined, @@ -100,5 +97,8 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta { }, }; - return meta; + return { + message: `${method} ${pathWithQuery} ${status_code}${responseTimeMsg}${bytesMsg}`, + meta, + }; } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 2c6fa74cb54a0..31508e075297b 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -236,6 +236,11 @@ export type { IRenderOptions } from './rendering'; export type { Logger, LoggerFactory, + Ecs, + EcsEventCategory, + EcsEventKind, + EcsEventOutcome, + EcsEventType, LogMeta, LogRecord, LogLevel, diff --git a/src/core/server/logging/__snapshots__/logging_system.test.ts.snap b/src/core/server/logging/__snapshots__/logging_system.test.ts.snap index 81321a3b1fe44..d74317203d78e 100644 --- a/src/core/server/logging/__snapshots__/logging_system.test.ts.snap +++ b/src/core/server/logging/__snapshots__/logging_system.test.ts.snap @@ -15,6 +15,9 @@ exports[`appends records via multiple appenders.: file logs 2`] = ` exports[`asLoggerFactory() only allows to create new loggers. 1`] = ` Object { "@timestamp": "2012-01-30T22:33:22.011-05:00", + "ecs": Object { + "version": "1.9.0", + }, "log": Object { "level": "TRACE", "logger": "test.context", @@ -29,6 +32,9 @@ Object { exports[`asLoggerFactory() only allows to create new loggers. 2`] = ` Object { "@timestamp": "2012-01-30T17:33:22.011-05:00", + "ecs": Object { + "version": "1.9.0", + }, "log": Object { "level": "INFO", "logger": "test.context", @@ -44,6 +50,9 @@ Object { exports[`asLoggerFactory() only allows to create new loggers. 3`] = ` Object { "@timestamp": "2012-01-30T12:33:22.011-05:00", + "ecs": Object { + "version": "1.9.0", + }, "log": Object { "level": "FATAL", "logger": "test.context", @@ -58,6 +67,9 @@ Object { exports[`flushes memory buffer logger and switches to real logger once config is provided: buffered messages 1`] = ` Object { "@timestamp": "2012-02-01T09:33:22.011-05:00", + "ecs": Object { + "version": "1.9.0", + }, "log": Object { "level": "INFO", "logger": "test.context", @@ -73,6 +85,9 @@ Object { exports[`flushes memory buffer logger and switches to real logger once config is provided: new messages 1`] = ` Object { "@timestamp": "2012-01-31T23:33:22.011-05:00", + "ecs": Object { + "version": "1.9.0", + }, "log": Object { "level": "INFO", "logger": "test.context", diff --git a/src/core/server/logging/appenders/rewrite/policies/meta/meta_policy.test.ts b/src/core/server/logging/appenders/rewrite/policies/meta/meta_policy.test.ts index 52b88331a75be..faa026363ed40 100644 --- a/src/core/server/logging/appenders/rewrite/policies/meta/meta_policy.test.ts +++ b/src/core/server/logging/appenders/rewrite/policies/meta/meta_policy.test.ts @@ -26,12 +26,14 @@ describe('MetaRewritePolicy', () => { describe('mode: update', () => { it('updates existing properties in LogMeta', () => { + // @ts-expect-error ECS custom meta const log = createLogRecord({ a: 'before' }); const policy = createPolicy('update', [{ path: 'a', value: 'after' }]); expect(policy.rewrite(log).meta!.a).toBe('after'); }); it('updates nested properties in LogMeta', () => { + // @ts-expect-error ECS custom meta const log = createLogRecord({ a: 'before a', b: { c: 'before b.c' }, d: [0, 1] }); const policy = createPolicy('update', [ { path: 'a', value: 'after a' }, @@ -60,6 +62,7 @@ describe('MetaRewritePolicy', () => { { path: 'd', value: 'hi' }, ]); const log = createLogRecord({ + // @ts-expect-error ECS custom meta a: 'a', b: 'b', c: 'c', @@ -80,6 +83,7 @@ describe('MetaRewritePolicy', () => { { path: 'a.b', value: 'foo' }, { path: 'a.c', value: 'bar' }, ]); + // @ts-expect-error ECS custom meta const log = createLogRecord({ a: { b: 'existing meta' } }); const { meta } = policy.rewrite(log); expect(meta!.a.b).toBe('foo'); @@ -106,12 +110,14 @@ describe('MetaRewritePolicy', () => { describe('mode: remove', () => { it('removes existing properties in LogMeta', () => { + // @ts-expect-error ECS custom meta const log = createLogRecord({ a: 'goodbye' }); const policy = createPolicy('remove', [{ path: 'a' }]); expect(policy.rewrite(log).meta!.a).toBeUndefined(); }); it('removes nested properties in LogMeta', () => { + // @ts-expect-error ECS custom meta const log = createLogRecord({ a: 'a', b: { c: 'b.c' }, d: [0, 1] }); const policy = createPolicy('remove', [{ path: 'b.c' }, { path: 'd[1]' }]); expect(policy.rewrite(log).meta).toMatchInlineSnapshot(` @@ -127,6 +133,7 @@ describe('MetaRewritePolicy', () => { }); it('has no effect if property does not exist', () => { + // @ts-expect-error ECS custom meta const log = createLogRecord({ a: 'a' }); const policy = createPolicy('remove', [{ path: 'b' }]); expect(policy.rewrite(log).meta).toMatchInlineSnapshot(` diff --git a/src/core/server/logging/appenders/rewrite/rewrite_appender.test.ts b/src/core/server/logging/appenders/rewrite/rewrite_appender.test.ts index 72a54b5012ce5..f4ce64ee65075 100644 --- a/src/core/server/logging/appenders/rewrite/rewrite_appender.test.ts +++ b/src/core/server/logging/appenders/rewrite/rewrite_appender.test.ts @@ -85,8 +85,8 @@ describe('RewriteAppender', () => { const appender = new RewriteAppender(config); appenderMocks.forEach((mock) => appender.addAppender(...mock)); - const log1 = createLogRecord({ a: 'b' }); - const log2 = createLogRecord({ c: 'd' }); + const log1 = createLogRecord({ user_agent: { name: 'a' } }); + const log2 = createLogRecord({ user_agent: { name: 'b' } }); appender.append(log1); @@ -109,8 +109,8 @@ describe('RewriteAppender', () => { const appender = new RewriteAppender(config); appender.addAppender(...createAppenderMock('mock1')); - const log1 = createLogRecord({ a: 'b' }); - const log2 = createLogRecord({ c: 'd' }); + const log1 = createLogRecord({ user_agent: { name: 'a' } }); + const log2 = createLogRecord({ user_agent: { name: 'b' } }); appender.append(log1); diff --git a/src/core/server/logging/ecs.ts b/src/core/server/logging/ecs.ts deleted file mode 100644 index f6db79819d819..0000000000000 --- a/src/core/server/logging/ecs.ts +++ /dev/null @@ -1,129 +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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Typings for some ECS fields which core uses internally. - * These are not a complete set of ECS typings and should not - * be used externally; the only types included here are ones - * currently used in core. - * - * @internal - */ -export interface EcsEvent { - /** - * These typings were written as of ECS 1.7.0. - * Don't change this value without checking the rest - * of the types to conform to that ECS version. - * - * https://www.elastic.co/guide/en/ecs/1.7/index.html - */ - ecs: { version: '1.7.0' }; - - // base fields - ['@timestamp']?: string; - labels?: Record; - message?: string; - tags?: string[]; - - // other fields - client?: EcsClientField; - event?: EcsEventField; - http?: EcsHttpField; - process?: EcsProcessField; - url?: EcsUrlField; - user_agent?: EcsUserAgentField; -} - -/** @internal */ -export enum EcsEventKind { - ALERT = 'alert', - EVENT = 'event', - METRIC = 'metric', - STATE = 'state', - PIPELINE_ERROR = 'pipeline_error', - SIGNAL = 'signal', -} - -/** @internal */ -export enum EcsEventCategory { - AUTHENTICATION = 'authentication', - CONFIGURATION = 'configuration', - DATABASE = 'database', - DRIVER = 'driver', - FILE = 'file', - HOST = 'host', - IAM = 'iam', - INTRUSION_DETECTION = 'intrusion_detection', - MALWARE = 'malware', - NETWORK = 'network', - PACKAGE = 'package', - PROCESS = 'process', - WEB = 'web', -} - -/** @internal */ -export enum EcsEventType { - ACCESS = 'access', - ADMIN = 'admin', - ALLOWED = 'allowed', - CHANGE = 'change', - CONNECTION = 'connection', - CREATION = 'creation', - DELETION = 'deletion', - DENIED = 'denied', - END = 'end', - ERROR = 'error', - GROUP = 'group', - INFO = 'info', - INSTALLATION = 'installation', - PROTOCOL = 'protocol', - START = 'start', - USER = 'user', -} - -interface EcsEventField { - kind?: EcsEventKind; - category?: EcsEventCategory[]; - type?: EcsEventType; -} - -interface EcsProcessField { - uptime?: number; -} - -interface EcsClientField { - ip?: string; -} - -interface EcsHttpFieldRequest { - body?: { bytes?: number; content?: string }; - method?: string; - mime_type?: string; - referrer?: string; -} - -interface EcsHttpFieldResponse { - body?: { bytes?: number; content?: string }; - bytes?: number; - status_code?: number; -} - -interface EcsHttpField { - version?: string; - request?: EcsHttpFieldRequest; - response?: EcsHttpFieldResponse; -} - -interface EcsUrlField { - path?: string; - query?: string; -} - -interface EcsUserAgentField { - original?: string; -} diff --git a/src/core/server/logging/index.ts b/src/core/server/logging/index.ts index cef96be54870e..9d17b289bfa4c 100644 --- a/src/core/server/logging/index.ts +++ b/src/core/server/logging/index.ts @@ -9,6 +9,11 @@ export { LogLevel } from '@kbn/logging'; export type { DisposableAppender, Appender, + Ecs, + EcsEventCategory, + EcsEventKind, + EcsEventOutcome, + EcsEventType, LogRecord, Layout, LoggerFactory, @@ -16,8 +21,6 @@ export type { Logger, LogLevelId, } from '@kbn/logging'; -export { EcsEventType, EcsEventCategory, EcsEventKind } from './ecs'; -export type { EcsEvent } from './ecs'; export { config } from './logging_config'; export type { LoggingConfigType, diff --git a/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap b/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap index 0e7ce8d0b2f3c..a131d5c8a9248 100644 --- a/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap +++ b/src/core/server/logging/layouts/__snapshots__/json_layout.test.ts.snap @@ -1,13 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`\`format()\` correctly formats record. 1`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-1\\",\\"error\\":{\\"message\\":\\"Some error message\\",\\"type\\":\\"Some error name\\",\\"stack_trace\\":\\"Some error stack\\"},\\"log\\":{\\"level\\":\\"FATAL\\",\\"logger\\":\\"context-1\\"},\\"process\\":{\\"pid\\":5355}}"`; +exports[`\`format()\` correctly formats record. 1`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-1\\",\\"error\\":{\\"message\\":\\"Some error message\\",\\"type\\":\\"Some error name\\",\\"stack_trace\\":\\"Some error stack\\"},\\"log\\":{\\"level\\":\\"FATAL\\",\\"logger\\":\\"context-1\\"},\\"process\\":{\\"pid\\":5355}}"`; -exports[`\`format()\` correctly formats record. 2`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-2\\",\\"log\\":{\\"level\\":\\"ERROR\\",\\"logger\\":\\"context-2\\"},\\"process\\":{\\"pid\\":5355}}"`; +exports[`\`format()\` correctly formats record. 2`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-2\\",\\"log\\":{\\"level\\":\\"ERROR\\",\\"logger\\":\\"context-2\\"},\\"process\\":{\\"pid\\":5355}}"`; -exports[`\`format()\` correctly formats record. 3`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-3\\",\\"log\\":{\\"level\\":\\"WARN\\",\\"logger\\":\\"context-3\\"},\\"process\\":{\\"pid\\":5355}}"`; +exports[`\`format()\` correctly formats record. 3`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-3\\",\\"log\\":{\\"level\\":\\"WARN\\",\\"logger\\":\\"context-3\\"},\\"process\\":{\\"pid\\":5355}}"`; -exports[`\`format()\` correctly formats record. 4`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-4\\",\\"log\\":{\\"level\\":\\"DEBUG\\",\\"logger\\":\\"context-4\\"},\\"process\\":{\\"pid\\":5355}}"`; +exports[`\`format()\` correctly formats record. 4`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-4\\",\\"log\\":{\\"level\\":\\"DEBUG\\",\\"logger\\":\\"context-4\\"},\\"process\\":{\\"pid\\":5355}}"`; -exports[`\`format()\` correctly formats record. 5`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-5\\",\\"log\\":{\\"level\\":\\"INFO\\",\\"logger\\":\\"context-5\\"},\\"process\\":{\\"pid\\":5355}}"`; +exports[`\`format()\` correctly formats record. 5`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-5\\",\\"log\\":{\\"level\\":\\"INFO\\",\\"logger\\":\\"context-5\\"},\\"process\\":{\\"pid\\":5355}}"`; -exports[`\`format()\` correctly formats record. 6`] = `"{\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-6\\",\\"log\\":{\\"level\\":\\"TRACE\\",\\"logger\\":\\"context-6\\"},\\"process\\":{\\"pid\\":5355}}"`; +exports[`\`format()\` correctly formats record. 6`] = `"{\\"ecs\\":{\\"version\\":\\"1.9.0\\"},\\"@timestamp\\":\\"2012-02-01T09:30:22.011-05:00\\",\\"message\\":\\"message-6\\",\\"log\\":{\\"level\\":\\"TRACE\\",\\"logger\\":\\"context-6\\"},\\"process\\":{\\"pid\\":5355}}"`; diff --git a/src/core/server/logging/layouts/json_layout.test.ts b/src/core/server/logging/layouts/json_layout.test.ts index e55f69daab110..e76e3fb4402bb 100644 --- a/src/core/server/logging/layouts/json_layout.test.ts +++ b/src/core/server/logging/layouts/json_layout.test.ts @@ -94,6 +94,7 @@ test('`format()` correctly formats record with meta-data', () => { }) ) ).toStrictEqual({ + ecs: { version: '1.9.0' }, '@timestamp': '2012-02-01T09:30:22.011-05:00', log: { level: 'DEBUG', @@ -135,6 +136,7 @@ test('`format()` correctly formats error record with meta-data', () => { }) ) ).toStrictEqual({ + ecs: { version: '1.9.0' }, '@timestamp': '2012-02-01T09:30:22.011-05:00', log: { level: 'DEBUG', @@ -156,7 +158,39 @@ test('`format()` correctly formats error record with meta-data', () => { }); }); -test('format() meta can override @timestamp', () => { +test('format() meta can merge override logs', () => { + const layout = new JsonLayout(); + expect( + JSON.parse( + layout.format({ + timestamp, + message: 'foo', + level: LogLevel.Error, + context: 'bar', + pid: 3, + meta: { + log: { + kbn_custom_field: 'hello', + }, + }, + }) + ) + ).toStrictEqual({ + ecs: { version: '1.9.0' }, + '@timestamp': '2012-02-01T09:30:22.011-05:00', + message: 'foo', + log: { + level: 'ERROR', + logger: 'bar', + kbn_custom_field: 'hello', + }, + process: { + pid: 3, + }, + }); +}); + +test('format() meta can not override message', () => { const layout = new JsonLayout(); expect( JSON.parse( @@ -167,12 +201,13 @@ test('format() meta can override @timestamp', () => { context: 'bar', pid: 3, meta: { - '@timestamp': '2099-05-01T09:30:22.011-05:00', + message: 'baz', }, }) ) ).toStrictEqual({ - '@timestamp': '2099-05-01T09:30:22.011-05:00', + ecs: { version: '1.9.0' }, + '@timestamp': '2012-02-01T09:30:22.011-05:00', message: 'foo', log: { level: 'DEBUG', @@ -184,30 +219,60 @@ test('format() meta can override @timestamp', () => { }); }); -test('format() meta can merge override logs', () => { +test('format() meta can not override ecs version', () => { const layout = new JsonLayout(); expect( JSON.parse( layout.format({ + message: 'foo', timestamp, + level: LogLevel.Debug, + context: 'bar', + pid: 3, + meta: { + message: 'baz', + }, + }) + ) + ).toStrictEqual({ + ecs: { version: '1.9.0' }, + '@timestamp': '2012-02-01T09:30:22.011-05:00', + message: 'foo', + log: { + level: 'DEBUG', + logger: 'bar', + }, + process: { + pid: 3, + }, + }); +}); + +test('format() meta can not override logger or level', () => { + const layout = new JsonLayout(); + expect( + JSON.parse( + layout.format({ message: 'foo', - level: LogLevel.Error, + timestamp, + level: LogLevel.Debug, context: 'bar', pid: 3, meta: { log: { - kbn_custom_field: 'hello', + level: 'IGNORE', + logger: 'me', }, }, }) ) ).toStrictEqual({ + ecs: { version: '1.9.0' }, '@timestamp': '2012-02-01T09:30:22.011-05:00', message: 'foo', log: { - level: 'ERROR', + level: 'DEBUG', logger: 'bar', - kbn_custom_field: 'hello', }, process: { pid: 3, @@ -215,29 +280,28 @@ test('format() meta can merge override logs', () => { }); }); -test('format() meta can override log level objects', () => { +test('format() meta can not override timestamp', () => { const layout = new JsonLayout(); expect( JSON.parse( layout.format({ - timestamp, - context: '123', message: 'foo', - level: LogLevel.Error, + timestamp, + level: LogLevel.Debug, + context: 'bar', pid: 3, meta: { - log: { - level: 'FATAL', - }, + '@timestamp': '2099-02-01T09:30:22.011-05:00', }, }) ) ).toStrictEqual({ + ecs: { version: '1.9.0' }, '@timestamp': '2012-02-01T09:30:22.011-05:00', message: 'foo', log: { - level: 'FATAL', - logger: '123', + level: 'DEBUG', + logger: 'bar', }, process: { pid: 3, diff --git a/src/core/server/logging/layouts/json_layout.ts b/src/core/server/logging/layouts/json_layout.ts index bb8423f8240af..add88cc01b6d2 100644 --- a/src/core/server/logging/layouts/json_layout.ts +++ b/src/core/server/logging/layouts/json_layout.ts @@ -9,7 +9,7 @@ import moment from 'moment-timezone'; import { merge } from '@kbn/std'; import { schema } from '@kbn/config-schema'; -import { LogRecord, Layout } from '@kbn/logging'; +import { Ecs, LogRecord, Layout } from '@kbn/logging'; const { literal, object } = schema; @@ -42,7 +42,8 @@ export class JsonLayout implements Layout { } public format(record: LogRecord): string { - const log = { + const log: Ecs = { + ecs: { version: '1.9.0' }, '@timestamp': moment(record.timestamp).format('YYYY-MM-DDTHH:mm:ss.SSSZ'), message: record.message, error: JsonLayout.errorToSerializableObject(record.error), @@ -54,7 +55,8 @@ export class JsonLayout implements Layout { pid: record.pid, }, }; - const output = record.meta ? merge(log, record.meta) : log; + const output = record.meta ? merge({ ...record.meta }, log) : log; + return JSON.stringify(output); } } diff --git a/src/core/server/logging/logger.test.ts b/src/core/server/logging/logger.test.ts index b7f224e73cb8b..c57ce2563ca3d 100644 --- a/src/core/server/logging/logger.test.ts +++ b/src/core/server/logging/logger.test.ts @@ -45,6 +45,7 @@ test('`trace()` correctly forms `LogRecord` and passes it to all appenders.', () }); } + // @ts-expect-error ECS custom meta logger.trace('message-2', { trace: true }); for (const appenderMock of appenderMocks) { expect(appenderMock.append).toHaveBeenCalledTimes(2); @@ -75,6 +76,7 @@ test('`debug()` correctly forms `LogRecord` and passes it to all appenders.', () }); } + // @ts-expect-error ECS custom meta logger.debug('message-2', { debug: true }); for (const appenderMock of appenderMocks) { expect(appenderMock.append).toHaveBeenCalledTimes(2); @@ -105,6 +107,7 @@ test('`info()` correctly forms `LogRecord` and passes it to all appenders.', () }); } + // @ts-expect-error ECS custom meta logger.info('message-2', { info: true }); for (const appenderMock of appenderMocks) { expect(appenderMock.append).toHaveBeenCalledTimes(2); @@ -150,6 +153,7 @@ test('`warn()` correctly forms `LogRecord` and passes it to all appenders.', () }); } + // @ts-expect-error ECS custom meta logger.warn('message-3', { warn: true }); for (const appenderMock of appenderMocks) { expect(appenderMock.append).toHaveBeenCalledTimes(3); @@ -195,6 +199,7 @@ test('`error()` correctly forms `LogRecord` and passes it to all appenders.', () }); } + // @ts-expect-error ECS custom meta logger.error('message-3', { error: true }); for (const appenderMock of appenderMocks) { expect(appenderMock.append).toHaveBeenCalledTimes(3); @@ -240,6 +245,7 @@ test('`fatal()` correctly forms `LogRecord` and passes it to all appenders.', () }); } + // @ts-expect-error ECS custom meta logger.fatal('message-3', { fatal: true }); for (const appenderMock of appenderMocks) { expect(appenderMock.append).toHaveBeenCalledTimes(3); diff --git a/src/core/server/logging/logger.ts b/src/core/server/logging/logger.ts index 4ba334cec2fb9..e025c28a88f0e 100644 --- a/src/core/server/logging/logger.ts +++ b/src/core/server/logging/logger.ts @@ -21,28 +21,28 @@ export class BaseLogger implements Logger { private readonly factory: LoggerFactory ) {} - public trace(message: string, meta?: LogMeta): void { - this.log(this.createLogRecord(LogLevel.Trace, message, meta)); + public trace(message: string, meta?: Meta): void { + this.log(this.createLogRecord(LogLevel.Trace, message, meta)); } - public debug(message: string, meta?: LogMeta): void { - this.log(this.createLogRecord(LogLevel.Debug, message, meta)); + public debug(message: string, meta?: Meta): void { + this.log(this.createLogRecord(LogLevel.Debug, message, meta)); } - public info(message: string, meta?: LogMeta): void { - this.log(this.createLogRecord(LogLevel.Info, message, meta)); + public info(message: string, meta?: Meta): void { + this.log(this.createLogRecord(LogLevel.Info, message, meta)); } - public warn(errorOrMessage: string | Error, meta?: LogMeta): void { - this.log(this.createLogRecord(LogLevel.Warn, errorOrMessage, meta)); + public warn(errorOrMessage: string | Error, meta?: Meta): void { + this.log(this.createLogRecord(LogLevel.Warn, errorOrMessage, meta)); } - public error(errorOrMessage: string | Error, meta?: LogMeta): void { - this.log(this.createLogRecord(LogLevel.Error, errorOrMessage, meta)); + public error(errorOrMessage: string | Error, meta?: Meta): void { + this.log(this.createLogRecord(LogLevel.Error, errorOrMessage, meta)); } - public fatal(errorOrMessage: string | Error, meta?: LogMeta): void { - this.log(this.createLogRecord(LogLevel.Fatal, errorOrMessage, meta)); + public fatal(errorOrMessage: string | Error, meta?: Meta): void { + this.log(this.createLogRecord(LogLevel.Fatal, errorOrMessage, meta)); } public log(record: LogRecord) { @@ -59,10 +59,10 @@ export class BaseLogger implements Logger { return this.factory.get(...[this.context, ...childContextPaths]); } - private createLogRecord( + private createLogRecord( level: LogLevel, errorOrMessage: string | Error, - meta?: LogMeta + meta?: Meta ): LogRecord { if (isError(errorOrMessage)) { return { diff --git a/src/core/server/logging/logging_system.test.ts b/src/core/server/logging/logging_system.test.ts index b67be384732cb..9c4313bc0c49d 100644 --- a/src/core/server/logging/logging_system.test.ts +++ b/src/core/server/logging/logging_system.test.ts @@ -49,6 +49,7 @@ test('uses default memory buffer logger until config is provided', () => { // We shouldn't create new buffer appender for another context name. const anotherLogger = system.get('test', 'context2'); + // @ts-expect-error ECS custom meta anotherLogger.fatal('fatal message', { some: 'value' }); expect(bufferAppendSpy).toHaveBeenCalledTimes(2); @@ -62,6 +63,7 @@ test('flushes memory buffer logger and switches to real logger once config is pr const logger = system.get('test', 'context'); logger.trace('buffered trace message'); + // @ts-expect-error ECS custom meta logger.info('buffered info message', { some: 'value' }); logger.fatal('buffered fatal message'); @@ -159,6 +161,7 @@ test('attaches appenders to appenders that declare refs', async () => { ); const testLogger = system.get('tests'); + // @ts-expect-error ECS custom meta testLogger.warn('This message goes to a test context.', { a: 'hi', b: 'remove me' }); expect(mockConsoleLog).toHaveBeenCalledTimes(1); @@ -233,6 +236,7 @@ test('asLoggerFactory() only allows to create new loggers.', async () => { ); logger.trace('buffered trace message'); + // @ts-expect-error ECS custom meta logger.info('buffered info message', { some: 'value' }); logger.fatal('buffered fatal message'); diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.test.ts b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts index 014d3ae258823..e535b9babf92b 100644 --- a/src/core/server/metrics/logging/get_ops_metrics_log.test.ts +++ b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts @@ -66,7 +66,7 @@ describe('getEcsOpsMetricsLog', () => { it('correctly formats process uptime', () => { const logMeta = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); - expect(logMeta.process!.uptime).toEqual(1); + expect(logMeta.meta.process!.uptime).toEqual(1); }); it('excludes values from the message if unavailable', () => { @@ -80,44 +80,40 @@ describe('getEcsOpsMetricsLog', () => { expect(logMeta.message).toMatchInlineSnapshot(`""`); }); - it('specifies correct ECS version', () => { - const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); - expect(logMeta.ecs.version).toBe('1.7.0'); - }); - it('provides an ECS-compatible response', () => { const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); expect(logMeta).toMatchInlineSnapshot(` Object { - "ecs": Object { - "version": "1.7.0", - }, - "event": Object { - "category": Array [ - "process", - "host", - ], - "kind": "metric", - "type": "info", - }, - "host": Object { - "os": Object { - "load": Object { - "15m": 1, - "1m": 1, - "5m": 1, + "message": "memory: 1.0B load: [1.00,1.00,1.00] delay: 1.000", + "meta": Object { + "event": Object { + "category": Array [ + "process", + "host", + ], + "kind": "metric", + "type": Array [ + "info", + ], + }, + "host": Object { + "os": Object { + "load": Object { + "15m": 1, + "1m": 1, + "5m": 1, + }, }, }, - }, - "message": "memory: 1.0B load: [1.00,1.00,1.00] delay: 1.000", - "process": Object { - "eventLoopDelay": 1, - "memory": Object { - "heap": Object { - "usedInBytes": 1, + "process": Object { + "eventLoopDelay": 1, + "memory": Object { + "heap": Object { + "usedInBytes": 1, + }, }, + "uptime": 0, }, - "uptime": 0, }, } `); @@ -125,8 +121,8 @@ describe('getEcsOpsMetricsLog', () => { it('logs ECS fields in the log meta', () => { const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); - expect(logMeta.event!.kind).toBe('metric'); - expect(logMeta.event!.category).toEqual(expect.arrayContaining(['process', 'host'])); - expect(logMeta.event!.type).toBe('info'); + expect(logMeta.meta.event!.kind).toBe('metric'); + expect(logMeta.meta.event!.category).toEqual(expect.arrayContaining(['process', 'host'])); + expect(logMeta.meta.event!.type).toEqual(expect.arrayContaining(['info'])); }); }); diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.ts b/src/core/server/metrics/logging/get_ops_metrics_log.ts index 02c3ad312c7dd..7e13f35889ec7 100644 --- a/src/core/server/metrics/logging/get_ops_metrics_log.ts +++ b/src/core/server/metrics/logging/get_ops_metrics_log.ts @@ -7,16 +7,15 @@ */ import numeral from '@elastic/numeral'; -import { EcsEvent, EcsEventKind, EcsEventCategory, EcsEventType } from '../../logging'; +import { LogMeta } from '@kbn/logging'; import { OpsMetrics } from '..'; -const ECS_VERSION = '1.7.0'; /** * Converts ops metrics into ECS-compliant `LogMeta` for logging * * @internal */ -export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsEvent { +export function getEcsOpsMetricsLog(metrics: OpsMetrics) { const { process, os } = metrics; const processMemoryUsedInBytes = process?.memory?.heap?.used_in_bytes; const processMemoryUsedInBytesMsg = processMemoryUsedInBytes @@ -51,13 +50,11 @@ export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsEvent { })}] ` : ''; - return { - ecs: { version: ECS_VERSION }, - message: `${processMemoryUsedInBytesMsg}${uptimeValMsg}${loadValsMsg}${eventLoopDelayValMsg}`, + const meta: LogMeta = { event: { - kind: EcsEventKind.METRIC, - category: [EcsEventCategory.PROCESS, EcsEventCategory.HOST], - type: EcsEventType.INFO, + kind: 'metric', + category: ['process', 'host'], + type: ['info'], }, process: { uptime: uptimeVal, @@ -71,8 +68,14 @@ export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsEvent { }, host: { os: { + // @ts-expect-error custom fields not yet part of ECS load: loadEntries, }, }, }; + + return { + message: `${processMemoryUsedInBytesMsg}${uptimeValMsg}${loadValsMsg}${eventLoopDelayValMsg}`, + meta, + }; } diff --git a/src/core/server/metrics/metrics_service.test.ts b/src/core/server/metrics/metrics_service.test.ts index 4fbca5addda11..d7de41fd7ccf7 100644 --- a/src/core/server/metrics/metrics_service.test.ts +++ b/src/core/server/metrics/metrics_service.test.ts @@ -182,16 +182,15 @@ describe('MetricsService', () => { Array [ "", Object { - "ecs": Object { - "version": "1.7.0", - }, "event": Object { "category": Array [ "process", "host", ], "kind": "metric", - "type": "info", + "type": Array [ + "info", + ], }, "host": Object { "os": Object { diff --git a/src/core/server/metrics/metrics_service.ts b/src/core/server/metrics/metrics_service.ts index 382848e0a80c3..78e4dd98f93d6 100644 --- a/src/core/server/metrics/metrics_service.ts +++ b/src/core/server/metrics/metrics_service.ts @@ -73,7 +73,7 @@ export class MetricsService private async refreshMetrics() { const metrics = await this.metricsCollector!.collect(); - const { message, ...meta } = getEcsOpsMetricsLog(metrics); + const { message, meta } = getEcsOpsMetricsLog(metrics); this.opsMetricsLogger.debug(message!, meta); this.metricsCollector!.reset(); this.metrics$.next(metrics); diff --git a/src/core/server/saved_objects/migrations/core/migration_logger.ts b/src/core/server/saved_objects/migrations/core/migration_logger.ts index e8cb6352195de..6c935b915ce68 100644 --- a/src/core/server/saved_objects/migrations/core/migration_logger.ts +++ b/src/core/server/saved_objects/migrations/core/migration_logger.ts @@ -24,7 +24,7 @@ export interface SavedObjectsMigrationLogger { */ warning: (msg: string) => void; warn: (msg: string) => void; - error: (msg: string, meta: LogMeta) => void; + error: (msg: string, meta: Meta) => void; } export class MigrationLogger implements SavedObjectsMigrationLogger { diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts index fa2e65f16bb2d..a6617fc2fb7f4 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.test.ts @@ -211,86 +211,90 @@ describe('migrationsStateActionMachine', () => { Array [ "[.my-so-index] INIT -> LEGACY_DELETE", Object { - "batchSize": 1000, - "controlState": "LEGACY_DELETE", - "currentAlias": ".my-so-index", - "indexPrefix": ".my-so-index", - "kibanaVersion": "7.11.0", - "legacyIndex": ".my-so-index", - "logs": Array [ - Object { - "level": "info", - "message": "Log from LEGACY_DELETE control state", - }, - ], - "outdatedDocuments": Array [ - "1234", - ], - "outdatedDocumentsQuery": Object { - "bool": Object { - "should": Array [], - }, - }, - "preMigrationScript": Object { - "_tag": "None", - }, - "reason": "the fatal reason", - "retryAttempts": 5, - "retryCount": 0, - "retryDelay": 0, - "targetIndexMappings": Object { - "properties": Object {}, - }, - "tempIndex": ".my-so-index_7.11.0_reindex_temp", - "tempIndexMappings": Object { - "dynamic": false, - "properties": Object { - "migrationVersion": Object { - "dynamic": "true", - "type": "object", + "kibana": Object { + "migrationState": Object { + "batchSize": 1000, + "controlState": "LEGACY_DELETE", + "currentAlias": ".my-so-index", + "indexPrefix": ".my-so-index", + "kibanaVersion": "7.11.0", + "legacyIndex": ".my-so-index", + "logs": Array [ + Object { + "level": "info", + "message": "Log from LEGACY_DELETE control state", + }, + ], + "outdatedDocuments": Array [ + "1234", + ], + "outdatedDocumentsQuery": Object { + "bool": Object { + "should": Array [], + }, }, - "type": Object { - "type": "keyword", + "preMigrationScript": Object { + "_tag": "None", }, - }, - }, - "unusedTypesQuery": Object { - "_tag": "Some", - "value": Object { - "bool": Object { - "must_not": Array [ - Object { - "term": Object { - "type": "fleet-agent-events", - }, + "reason": "the fatal reason", + "retryAttempts": 5, + "retryCount": 0, + "retryDelay": 0, + "targetIndexMappings": Object { + "properties": Object {}, + }, + "tempIndex": ".my-so-index_7.11.0_reindex_temp", + "tempIndexMappings": Object { + "dynamic": false, + "properties": Object { + "migrationVersion": Object { + "dynamic": "true", + "type": "object", }, - Object { - "term": Object { - "type": "tsvb-validation-telemetry", - }, + "type": Object { + "type": "keyword", }, - Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, + }, + }, + "unusedTypesQuery": Object { + "_tag": "Some", + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", }, - Object { - "match": Object { - "search-session.persisted": false, - }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", }, - ], - }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], }, - ], + }, }, + "versionAlias": ".my-so-index_7.11.0", + "versionIndex": ".my-so-index_7.11.0_001", }, }, - "versionAlias": ".my-so-index_7.11.0", - "versionIndex": ".my-so-index_7.11.0_001", }, ], Array [ @@ -303,90 +307,94 @@ describe('migrationsStateActionMachine', () => { Array [ "[.my-so-index] LEGACY_DELETE -> FATAL", Object { - "batchSize": 1000, - "controlState": "FATAL", - "currentAlias": ".my-so-index", - "indexPrefix": ".my-so-index", - "kibanaVersion": "7.11.0", - "legacyIndex": ".my-so-index", - "logs": Array [ - Object { - "level": "info", - "message": "Log from LEGACY_DELETE control state", - }, - Object { - "level": "info", - "message": "Log from FATAL control state", - }, - ], - "outdatedDocuments": Array [ - "1234", - ], - "outdatedDocumentsQuery": Object { - "bool": Object { - "should": Array [], - }, - }, - "preMigrationScript": Object { - "_tag": "None", - }, - "reason": "the fatal reason", - "retryAttempts": 5, - "retryCount": 0, - "retryDelay": 0, - "targetIndexMappings": Object { - "properties": Object {}, - }, - "tempIndex": ".my-so-index_7.11.0_reindex_temp", - "tempIndexMappings": Object { - "dynamic": false, - "properties": Object { - "migrationVersion": Object { - "dynamic": "true", - "type": "object", + "kibana": Object { + "migrationState": Object { + "batchSize": 1000, + "controlState": "FATAL", + "currentAlias": ".my-so-index", + "indexPrefix": ".my-so-index", + "kibanaVersion": "7.11.0", + "legacyIndex": ".my-so-index", + "logs": Array [ + Object { + "level": "info", + "message": "Log from LEGACY_DELETE control state", + }, + Object { + "level": "info", + "message": "Log from FATAL control state", + }, + ], + "outdatedDocuments": Array [ + "1234", + ], + "outdatedDocumentsQuery": Object { + "bool": Object { + "should": Array [], + }, }, - "type": Object { - "type": "keyword", + "preMigrationScript": Object { + "_tag": "None", }, - }, - }, - "unusedTypesQuery": Object { - "_tag": "Some", - "value": Object { - "bool": Object { - "must_not": Array [ - Object { - "term": Object { - "type": "fleet-agent-events", - }, + "reason": "the fatal reason", + "retryAttempts": 5, + "retryCount": 0, + "retryDelay": 0, + "targetIndexMappings": Object { + "properties": Object {}, + }, + "tempIndex": ".my-so-index_7.11.0_reindex_temp", + "tempIndexMappings": Object { + "dynamic": false, + "properties": Object { + "migrationVersion": Object { + "dynamic": "true", + "type": "object", }, - Object { - "term": Object { - "type": "tsvb-validation-telemetry", - }, + "type": Object { + "type": "keyword", }, - Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, + }, + }, + "unusedTypesQuery": Object { + "_tag": "Some", + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", }, - Object { - "match": Object { - "search-session.persisted": false, - }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], }, - ], - }, + }, + ], }, - ], + }, }, + "versionAlias": ".my-so-index_7.11.0", + "versionIndex": ".my-so-index_7.11.0_001", }, }, - "versionAlias": ".my-so-index_7.11.0", - "versionIndex": ".my-so-index_7.11.0_001", }, ], ] @@ -490,84 +498,88 @@ describe('migrationsStateActionMachine', () => { Array [ "[.my-so-index] INIT -> LEGACY_REINDEX", Object { - "batchSize": 1000, - "controlState": "LEGACY_REINDEX", - "currentAlias": ".my-so-index", - "indexPrefix": ".my-so-index", - "kibanaVersion": "7.11.0", - "legacyIndex": ".my-so-index", - "logs": Array [ - Object { - "level": "info", - "message": "Log from LEGACY_REINDEX control state", - }, - ], - "outdatedDocuments": Array [], - "outdatedDocumentsQuery": Object { - "bool": Object { - "should": Array [], - }, - }, - "preMigrationScript": Object { - "_tag": "None", - }, - "reason": "the fatal reason", - "retryAttempts": 5, - "retryCount": 0, - "retryDelay": 0, - "targetIndexMappings": Object { - "properties": Object {}, - }, - "tempIndex": ".my-so-index_7.11.0_reindex_temp", - "tempIndexMappings": Object { - "dynamic": false, - "properties": Object { - "migrationVersion": Object { - "dynamic": "true", - "type": "object", + "kibana": Object { + "migrationState": Object { + "batchSize": 1000, + "controlState": "LEGACY_REINDEX", + "currentAlias": ".my-so-index", + "indexPrefix": ".my-so-index", + "kibanaVersion": "7.11.0", + "legacyIndex": ".my-so-index", + "logs": Array [ + Object { + "level": "info", + "message": "Log from LEGACY_REINDEX control state", + }, + ], + "outdatedDocuments": Array [], + "outdatedDocumentsQuery": Object { + "bool": Object { + "should": Array [], + }, }, - "type": Object { - "type": "keyword", + "preMigrationScript": Object { + "_tag": "None", }, - }, - }, - "unusedTypesQuery": Object { - "_tag": "Some", - "value": Object { - "bool": Object { - "must_not": Array [ - Object { - "term": Object { - "type": "fleet-agent-events", - }, + "reason": "the fatal reason", + "retryAttempts": 5, + "retryCount": 0, + "retryDelay": 0, + "targetIndexMappings": Object { + "properties": Object {}, + }, + "tempIndex": ".my-so-index_7.11.0_reindex_temp", + "tempIndexMappings": Object { + "dynamic": false, + "properties": Object { + "migrationVersion": Object { + "dynamic": "true", + "type": "object", }, - Object { - "term": Object { - "type": "tsvb-validation-telemetry", - }, + "type": Object { + "type": "keyword", }, - Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, + }, + }, + "unusedTypesQuery": Object { + "_tag": "Some", + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", }, - Object { - "match": Object { - "search-session.persisted": false, - }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", }, - ], - }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], + }, + }, + ], }, - ], + }, }, + "versionAlias": ".my-so-index_7.11.0", + "versionIndex": ".my-so-index_7.11.0_001", }, }, - "versionAlias": ".my-so-index_7.11.0", - "versionIndex": ".my-so-index_7.11.0_001", }, ], Array [ @@ -577,88 +589,92 @@ describe('migrationsStateActionMachine', () => { Array [ "[.my-so-index] LEGACY_REINDEX -> LEGACY_DELETE", Object { - "batchSize": 1000, - "controlState": "LEGACY_DELETE", - "currentAlias": ".my-so-index", - "indexPrefix": ".my-so-index", - "kibanaVersion": "7.11.0", - "legacyIndex": ".my-so-index", - "logs": Array [ - Object { - "level": "info", - "message": "Log from LEGACY_REINDEX control state", - }, - Object { - "level": "info", - "message": "Log from LEGACY_DELETE control state", - }, - ], - "outdatedDocuments": Array [], - "outdatedDocumentsQuery": Object { - "bool": Object { - "should": Array [], - }, - }, - "preMigrationScript": Object { - "_tag": "None", - }, - "reason": "the fatal reason", - "retryAttempts": 5, - "retryCount": 0, - "retryDelay": 0, - "targetIndexMappings": Object { - "properties": Object {}, - }, - "tempIndex": ".my-so-index_7.11.0_reindex_temp", - "tempIndexMappings": Object { - "dynamic": false, - "properties": Object { - "migrationVersion": Object { - "dynamic": "true", - "type": "object", + "kibana": Object { + "migrationState": Object { + "batchSize": 1000, + "controlState": "LEGACY_DELETE", + "currentAlias": ".my-so-index", + "indexPrefix": ".my-so-index", + "kibanaVersion": "7.11.0", + "legacyIndex": ".my-so-index", + "logs": Array [ + Object { + "level": "info", + "message": "Log from LEGACY_REINDEX control state", + }, + Object { + "level": "info", + "message": "Log from LEGACY_DELETE control state", + }, + ], + "outdatedDocuments": Array [], + "outdatedDocumentsQuery": Object { + "bool": Object { + "should": Array [], + }, }, - "type": Object { - "type": "keyword", + "preMigrationScript": Object { + "_tag": "None", }, - }, - }, - "unusedTypesQuery": Object { - "_tag": "Some", - "value": Object { - "bool": Object { - "must_not": Array [ - Object { - "term": Object { - "type": "fleet-agent-events", - }, + "reason": "the fatal reason", + "retryAttempts": 5, + "retryCount": 0, + "retryDelay": 0, + "targetIndexMappings": Object { + "properties": Object {}, + }, + "tempIndex": ".my-so-index_7.11.0_reindex_temp", + "tempIndexMappings": Object { + "dynamic": false, + "properties": Object { + "migrationVersion": Object { + "dynamic": "true", + "type": "object", }, - Object { - "term": Object { - "type": "tsvb-validation-telemetry", - }, + "type": Object { + "type": "keyword", }, - Object { - "bool": Object { - "must": Array [ - Object { - "match": Object { - "type": "search-session", - }, + }, + }, + "unusedTypesQuery": Object { + "_tag": "Some", + "value": Object { + "bool": Object { + "must_not": Array [ + Object { + "term": Object { + "type": "fleet-agent-events", + }, + }, + Object { + "term": Object { + "type": "tsvb-validation-telemetry", }, - Object { - "match": Object { - "search-session.persisted": false, - }, + }, + Object { + "bool": Object { + "must": Array [ + Object { + "match": Object { + "type": "search-session", + }, + }, + Object { + "match": Object { + "search-session.persisted": false, + }, + }, + ], }, - ], - }, + }, + ], }, - ], + }, }, + "versionAlias": ".my-so-index_7.11.0", + "versionIndex": ".my-so-index_7.11.0_001", }, }, - "versionAlias": ".my-so-index_7.11.0", - "versionIndex": ".my-so-index_7.11.0_001", }, ], ] diff --git a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts index e35e21421ac1f..20177dda63b3b 100644 --- a/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts +++ b/src/core/server/saved_objects/migrationsv2/migrations_state_action_machine.ts @@ -13,6 +13,12 @@ import { CorruptSavedObjectError } from '../migrations/core/migrate_raw_docs'; import { Model, Next, stateActionMachine } from './state_action_machine'; import { State } from './types'; +interface StateLogMeta extends LogMeta { + kibana: { + migrationState: State; + }; +} + type ExecutionLog = Array< | { type: 'transition'; @@ -35,9 +41,15 @@ const logStateTransition = ( tookMs: number ) => { if (newState.logs.length > oldState.logs.length) { - newState.logs - .slice(oldState.logs.length) - .forEach((log) => logger[log.level](logMessagePrefix + log.message)); + newState.logs.slice(oldState.logs.length).forEach((log) => { + const getLogger = (level: keyof Logger) => { + if (level === 'error') { + return logger[level] as Logger['error']; + } + return logger[level] as Logger['info']; + }; + getLogger(log.level)(logMessagePrefix + log.message); + }); } logger.info( @@ -58,7 +70,14 @@ const dumpExecutionLog = (logger: Logger, logMessagePrefix: string, executionLog logger.error(logMessagePrefix + 'migration failed, dumping execution log:'); executionLog.forEach((log) => { if (log.type === 'transition') { - logger.info(logMessagePrefix + `${log.prevControlState} -> ${log.controlState}`, log.state); + logger.info( + logMessagePrefix + `${log.prevControlState} -> ${log.controlState}`, + { + kibana: { + migrationState: log.state, + }, + } + ); } if (log.type === 'response') { logger.info(logMessagePrefix + `${log.controlState} RESPONSE`, log.res as LogMeta); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index e5804b3c9fc58..ec447f636f9c3 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -49,6 +49,11 @@ import { DeleteTemplateParams } from 'elasticsearch'; import { DetailedPeerCertificate } from 'tls'; import { Duration } from 'moment'; import { Duration as Duration_2 } from 'moment-timezone'; +import { Ecs } from '@kbn/logging'; +import { EcsEventCategory } from '@kbn/logging'; +import { EcsEventKind } from '@kbn/logging'; +import { EcsEventOutcome } from '@kbn/logging'; +import { EcsEventType } from '@kbn/logging'; import { EnvironmentMode } from '@kbn/config'; import { estypes } from '@elastic/elasticsearch'; import { ExistsParams } from 'elasticsearch'; @@ -886,6 +891,16 @@ export interface DiscoveredPlugin { readonly requiredPlugins: readonly PluginName[]; } +export { Ecs } + +export { EcsEventCategory } + +export { EcsEventKind } + +export { EcsEventOutcome } + +export { EcsEventType } + // @public export type ElasticsearchClient = Omit & { transport: { @@ -2779,7 +2794,7 @@ export interface SavedObjectsMigrationLogger { // (undocumented) debug: (msg: string) => void; // (undocumented) - error: (msg: string, meta: LogMeta) => void; + error: (msg: string, meta: Meta) => void; // (undocumented) info: (msg: string) => void; // (undocumented) diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts index 09cf5b92b2b8a..7724e7a5e44b4 100644 --- a/src/core/server/status/status_service.ts +++ b/src/core/server/status/status_service.ts @@ -12,7 +12,7 @@ import { isDeepStrictEqual } from 'util'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; -import { Logger } from '../logging'; +import { Logger, LogMeta } from '../logging'; import { InternalElasticsearchServiceSetup } from '../elasticsearch'; import { InternalHttpServiceSetup } from '../http'; import { InternalSavedObjectsServiceSetup } from '../saved_objects'; @@ -26,6 +26,10 @@ import { ServiceStatus, CoreStatus, InternalStatusServiceSetup } from './types'; import { getSummaryStatus } from './get_summary_status'; import { PluginsStatusService } from './plugins_status'; +interface StatusLogMeta extends LogMeta { + kibana: { status: ServiceStatus }; +} + interface SetupDeps { elasticsearch: Pick; environment: InternalEnvironmentServiceSetup; @@ -70,7 +74,11 @@ export class StatusService implements CoreService { ...Object.entries(coreStatus), ...Object.entries(pluginsStatus), ]); - this.logger.debug(`Recalculated overall status`, { status: summary }); + this.logger.debug(`Recalculated overall status`, { + kibana: { + status: summary, + }, + }); return summary; }), distinctUntilChanged(isDeepStrictEqual), diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index b169c715b9b95..669849dcd8d9b 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -131,8 +131,12 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () { Array [ "Upgrade config from 4.0.0 to 4.0.1", Object { - "newVersion": "4.0.1", - "prevVersion": "4.0.0", + "kibana": Object { + "config": Object { + "newVersion": "4.0.1", + "prevVersion": "4.0.0", + }, + }, }, ], ] diff --git a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts index a32556d1aef6f..d015f506df6e3 100644 --- a/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts @@ -10,10 +10,16 @@ import { defaults } from 'lodash'; import { SavedObjectsClientContract } from '../../saved_objects/types'; import { SavedObjectsErrorHelpers } from '../../saved_objects/'; -import { Logger } from '../../logging'; +import { Logger, LogMeta } from '../../logging'; import { getUpgradeableConfig } from './get_upgradeable_config'; +interface ConfigLogMeta extends LogMeta { + kibana: { + config: { prevVersion: string; newVersion: string }; + }; +} + interface Options { savedObjectsClient: SavedObjectsClientContract; version: string; @@ -60,9 +66,13 @@ export async function createOrUpgradeSavedConfig( } if (upgradeableConfig) { - log.debug(`Upgrade config from ${upgradeableConfig.id} to ${version}`, { - prevVersion: upgradeableConfig.id, - newVersion: version, + log.debug(`Upgrade config from ${upgradeableConfig.id} to ${version}`, { + kibana: { + config: { + prevVersion: upgradeableConfig.id, + newVersion: version, + }, + }, }); } } diff --git a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.test.ts b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.test.ts index c800bce6390c9..8a76368c8cd9d 100644 --- a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.test.ts +++ b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.test.ts @@ -187,10 +187,13 @@ describe('UsageCountersService', () => { await tick(); // number of incrementCounter calls + number of retries expect(mockIncrementCounter).toBeCalledTimes(2 + 1); - expect(logger.debug).toHaveBeenNthCalledWith(1, 'Store counters into savedObjects', [ - mockError, - 'pass', - ]); + expect(logger.debug).toHaveBeenNthCalledWith(1, 'Store counters into savedObjects', { + kibana: { + usageCounters: { + results: [mockError, 'pass'], + }, + }, + }); }); it('buffers counters within `bufferDurationMs` time', async () => { diff --git a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.ts b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.ts index 88ca9f6358926..a698ea3db5bad 100644 --- a/src/plugins/usage_collection/server/usage_counters/usage_counters_service.ts +++ b/src/plugins/usage_collection/server/usage_counters/usage_counters_service.ts @@ -13,7 +13,7 @@ import { SavedObjectsServiceSetup, SavedObjectsServiceStart, } from 'src/core/server'; -import type { Logger } from 'src/core/server'; +import type { Logger, LogMeta } from 'src/core/server'; import moment from 'moment'; import { CounterMetric, UsageCounter } from './usage_counter'; @@ -23,6 +23,10 @@ import { serializeCounterKey, } from './saved_objects'; +interface UsageCountersLogMeta extends LogMeta { + kibana: { usageCounters: { results: unknown[] } }; +} + export interface UsageCountersServiceDeps { logger: Logger; retryCount: number; @@ -116,7 +120,11 @@ export class UsageCountersService { rxOp.concatMap((counters) => this.storeDate$(counters, internalRepository)) ) .subscribe((results) => { - this.logger.debug('Store counters into savedObjects', results); + this.logger.debug('Store counters into savedObjects', { + kibana: { + usageCounters: { results }, + }, + }); }); this.flushCache$.next(); diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index d8dcde2fab103..9f87de5f686cc 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -18,7 +18,7 @@ import { KibanaRequest, SavedObjectsUtils, } from '../../../../src/core/server'; -import { AuditLogger, EventOutcome } from '../../security/server'; +import { AuditLogger } from '../../security/server'; import { ActionType } from '../common'; import { ActionTypeRegistry } from './action_type_registry'; import { validateConfig, validateSecrets, ActionExecutorContract } from './lib'; @@ -146,7 +146,7 @@ export class ActionsClient { connectorAuditEvent({ action: ConnectorAuditAction.CREATE, savedObject: { type: 'action', id }, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', }) ); @@ -218,7 +218,7 @@ export class ActionsClient { connectorAuditEvent({ action: ConnectorAuditAction.UPDATE, savedObject: { type: 'action', id }, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', }) ); @@ -452,7 +452,7 @@ export class ActionsClient { this.auditLogger?.log( connectorAuditEvent({ action: ConnectorAuditAction.DELETE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'action', id }, }) ); diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts index ac9c4211f07cc..6c54c1b9f2ff1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts @@ -9,7 +9,7 @@ import { curry } from 'lodash'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; -import { Logger } from '../../../../../src/core/server'; +import { Logger, LogMeta } from '../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { withoutControlCharacters } from './lib/string_utils'; @@ -66,7 +66,7 @@ async function executor( const sanitizedMessage = withoutControlCharacters(params.message); try { - logger[params.level](`Server log: ${sanitizedMessage}`); + (logger[params.level] as Logger['info'])(`Server log: ${sanitizedMessage}`); } catch (err) { const message = i18n.translate('xpack.actions.builtin.serverLog.errorLoggingErrorMessage', { defaultMessage: 'error logging message', diff --git a/x-pack/plugins/actions/server/lib/audit_events.test.ts b/x-pack/plugins/actions/server/lib/audit_events.test.ts index 6047a97b63c54..b30ccc1fb372b 100644 --- a/x-pack/plugins/actions/server/lib/audit_events.test.ts +++ b/x-pack/plugins/actions/server/lib/audit_events.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { EventOutcome } from '../../../security/server/audit'; import { ConnectorAuditAction, connectorAuditEvent } from './audit_events'; describe('#connectorAuditEvent', () => { @@ -13,7 +12,7 @@ describe('#connectorAuditEvent', () => { expect( connectorAuditEvent({ action: ConnectorAuditAction.CREATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'action', id: 'ACTION_ID' }, }) ).toMatchInlineSnapshot(` @@ -21,9 +20,13 @@ describe('#connectorAuditEvent', () => { "error": undefined, "event": Object { "action": "connector_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "unknown", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { @@ -47,9 +50,13 @@ describe('#connectorAuditEvent', () => { "error": undefined, "event": Object { "action": "connector_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "success", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { @@ -77,9 +84,13 @@ describe('#connectorAuditEvent', () => { }, "event": Object { "action": "connector_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "failure", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { diff --git a/x-pack/plugins/actions/server/lib/audit_events.ts b/x-pack/plugins/actions/server/lib/audit_events.ts index f80fa00e11641..5231c9bab7c37 100644 --- a/x-pack/plugins/actions/server/lib/audit_events.ts +++ b/x-pack/plugins/actions/server/lib/audit_events.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { AuditEvent, EventOutcome, EventCategory, EventType } from '../../../security/server'; +import type { EcsEventOutcome, EcsEventType } from 'src/core/server'; +import { AuditEvent } from '../../../security/server'; export enum ConnectorAuditAction { CREATE = 'connector_create', @@ -27,18 +28,18 @@ const eventVerbs: Record = { connector_execute: ['execute', 'executing', 'executed'], }; -const eventTypes: Record = { - connector_create: EventType.CREATION, - connector_get: EventType.ACCESS, - connector_update: EventType.CHANGE, - connector_delete: EventType.DELETION, - connector_find: EventType.ACCESS, +const eventTypes: Record = { + connector_create: 'creation', + connector_get: 'access', + connector_update: 'change', + connector_delete: 'deletion', + connector_find: 'access', connector_execute: undefined, }; export interface ConnectorAuditEventParams { action: ConnectorAuditAction; - outcome?: EventOutcome; + outcome?: EcsEventOutcome; savedObject?: NonNullable['saved_object']; error?: Error; } @@ -53,7 +54,7 @@ export function connectorAuditEvent({ const [present, progressive, past] = eventVerbs[action]; const message = error ? `Failed attempt to ${present} ${doc}` - : outcome === EventOutcome.UNKNOWN + : outcome === 'unknown' ? `User is ${progressive} ${doc}` : `User has ${past} ${doc}`; const type = eventTypes[action]; @@ -62,9 +63,9 @@ export function connectorAuditEvent({ message, event: { action, - category: EventCategory.DATABASE, - type, - outcome: outcome ?? (error ? EventOutcome.FAILURE : EventOutcome.SUCCESS), + category: ['database'], + type: type ? [type] : undefined, + outcome: outcome ?? (error ? 'failure' : 'success'), }, kibana: { saved_object: savedObject, diff --git a/x-pack/plugins/actions/server/saved_objects/migrations.ts b/x-pack/plugins/actions/server/saved_objects/migrations.ts index 9b8b887fbec28..9bd54330f5d05 100644 --- a/x-pack/plugins/actions/server/saved_objects/migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/migrations.ts @@ -6,6 +6,7 @@ */ import { + LogMeta, SavedObjectMigrationMap, SavedObjectUnsanitizedDoc, SavedObjectMigrationFn, @@ -14,6 +15,10 @@ import { import { RawAction } from '../types'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; +interface ActionsLogMeta extends LogMeta { + migrations: { actionDocument: SavedObjectUnsanitizedDoc }; +} + type ActionMigration = ( doc: SavedObjectUnsanitizedDoc ) => SavedObjectUnsanitizedDoc; @@ -50,9 +55,13 @@ function executeMigrationWithErrorHandling( try { return migrationFunc(doc, context); } catch (ex) { - context.log.error( + context.log.error( `encryptedSavedObject ${version} migration failed for action ${doc.id} with error: ${ex.message}`, - { actionDocument: doc } + { + migrations: { + actionDocument: doc, + }, + } ); } return doc; diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts index e316ecd3c6fec..210bdf954ada4 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts @@ -51,7 +51,7 @@ import { IEventLogClient } from '../../../../plugins/event_log/server'; import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date'; import { alertInstanceSummaryFromEventLog } from '../lib/alert_instance_summary_from_event_log'; import { IEvent } from '../../../event_log/server'; -import { AuditLogger, EventOutcome } from '../../../security/server'; +import { AuditLogger } from '../../../security/server'; import { parseDuration } from '../../common/parse_duration'; import { retryIfConflicts } from '../lib/retry_if_conflicts'; import { partiallyUpdateAlert } from '../saved_objects'; @@ -293,7 +293,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.CREATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id }, }) ); @@ -598,7 +598,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.DELETE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id }, }) ); @@ -671,7 +671,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.UPDATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id }, }) ); @@ -850,7 +850,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.UPDATE_API_KEY, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id }, }) ); @@ -935,7 +935,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.ENABLE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id }, }) ); @@ -1036,7 +1036,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.DISABLE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id }, }) ); @@ -1112,7 +1112,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.MUTE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id }, }) ); @@ -1173,7 +1173,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.UNMUTE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id }, }) ); @@ -1234,7 +1234,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.MUTE_INSTANCE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id: alertId }, }) ); @@ -1300,7 +1300,7 @@ export class AlertsClient { this.auditLogger?.log( alertAuditEvent({ action: AlertAuditAction.UNMUTE_INSTANCE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id: alertId }, }) ); diff --git a/x-pack/plugins/alerting/server/alerts_client/audit_events.test.ts b/x-pack/plugins/alerting/server/alerts_client/audit_events.test.ts index fd79e9fac4fd1..4ccb69832cd26 100644 --- a/x-pack/plugins/alerting/server/alerts_client/audit_events.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/audit_events.test.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { EventOutcome } from '../../../security/server/audit'; import { AlertAuditAction, alertAuditEvent } from './audit_events'; describe('#alertAuditEvent', () => { @@ -13,7 +12,7 @@ describe('#alertAuditEvent', () => { expect( alertAuditEvent({ action: AlertAuditAction.CREATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'alert', id: 'ALERT_ID' }, }) ).toMatchInlineSnapshot(` @@ -21,9 +20,13 @@ describe('#alertAuditEvent', () => { "error": undefined, "event": Object { "action": "alert_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "unknown", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { @@ -47,9 +50,13 @@ describe('#alertAuditEvent', () => { "error": undefined, "event": Object { "action": "alert_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "success", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { @@ -77,9 +84,13 @@ describe('#alertAuditEvent', () => { }, "event": Object { "action": "alert_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "failure", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { diff --git a/x-pack/plugins/alerting/server/alerts_client/audit_events.ts b/x-pack/plugins/alerting/server/alerts_client/audit_events.ts index 354f58bafd888..93cca255d6ebc 100644 --- a/x-pack/plugins/alerting/server/alerts_client/audit_events.ts +++ b/x-pack/plugins/alerting/server/alerts_client/audit_events.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { AuditEvent, EventOutcome, EventCategory, EventType } from '../../../security/server'; +import { EcsEventOutcome, EcsEventType } from 'src/core/server'; +import { AuditEvent } from '../../../security/server'; export enum AlertAuditAction { CREATE = 'alert_create', @@ -39,24 +40,24 @@ const eventVerbs: Record = { alert_instance_unmute: ['unmute instance of', 'unmuting instance of', 'unmuted instance of'], }; -const eventTypes: Record = { - alert_create: EventType.CREATION, - alert_get: EventType.ACCESS, - alert_update: EventType.CHANGE, - alert_update_api_key: EventType.CHANGE, - alert_enable: EventType.CHANGE, - alert_disable: EventType.CHANGE, - alert_delete: EventType.DELETION, - alert_find: EventType.ACCESS, - alert_mute: EventType.CHANGE, - alert_unmute: EventType.CHANGE, - alert_instance_mute: EventType.CHANGE, - alert_instance_unmute: EventType.CHANGE, +const eventTypes: Record = { + alert_create: 'creation', + alert_get: 'access', + alert_update: 'change', + alert_update_api_key: 'change', + alert_enable: 'change', + alert_disable: 'change', + alert_delete: 'deletion', + alert_find: 'access', + alert_mute: 'change', + alert_unmute: 'change', + alert_instance_mute: 'change', + alert_instance_unmute: 'change', }; export interface AlertAuditEventParams { action: AlertAuditAction; - outcome?: EventOutcome; + outcome?: EcsEventOutcome; savedObject?: NonNullable['saved_object']; error?: Error; } @@ -71,7 +72,7 @@ export function alertAuditEvent({ const [present, progressive, past] = eventVerbs[action]; const message = error ? `Failed attempt to ${present} ${doc}` - : outcome === EventOutcome.UNKNOWN + : outcome === 'unknown' ? `User is ${progressive} ${doc}` : `User has ${past} ${doc}`; const type = eventTypes[action]; @@ -80,9 +81,9 @@ export function alertAuditEvent({ message, event: { action, - category: EventCategory.DATABASE, - type, - outcome: outcome ?? (error ? EventOutcome.FAILURE : EventOutcome.SUCCESS), + category: ['database'], + type: type ? [type] : undefined, + outcome: outcome ?? (error ? 'failure' : 'success'), }, kibana: { saved_object: savedObject, diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index a080809bbc968..4888116e43602 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -252,10 +252,12 @@ describe('7.10.0 migrates with failure', () => { expect(migrationContext.log.error).toHaveBeenCalledWith( `encryptedSavedObject 7.10.0 migration failed for alert ${alert.id} with error: Can't migrate!`, { - alertDocument: { - ...alert, - attributes: { - ...alert.attributes, + migrations: { + alertDocument: { + ...alert, + attributes: { + ...alert.attributes, + }, }, }, } diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index c9327ed8f186a..8969e3ad0fdef 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -6,6 +6,7 @@ */ import { + LogMeta, SavedObjectMigrationMap, SavedObjectUnsanitizedDoc, SavedObjectMigrationFn, @@ -20,6 +21,10 @@ const SIEM_APP_ID = 'securitySolution'; const SIEM_SERVER_APP_ID = 'siem'; export const LEGACY_LAST_MODIFIED_VERSION = 'pre-7.10.0'; +interface AlertLogMeta extends LogMeta { + migrations: { alertDocument: SavedObjectUnsanitizedDoc }; +} + type AlertMigration = ( doc: SavedObjectUnsanitizedDoc ) => SavedObjectUnsanitizedDoc; @@ -84,9 +89,13 @@ function executeMigrationWithErrorHandling( try { return migrationFunc(doc, context); } catch (ex) { - context.log.error( + context.log.error( `encryptedSavedObject ${version} migration failed for alert ${doc.id} with error: ${ex.message}`, - { alertDocument: doc } + { + migrations: { + alertDocument: doc, + }, + } ); } return doc; diff --git a/x-pack/plugins/security/README.md b/x-pack/plugins/security/README.md index b93be0269536b..cc817b50fa442 100644 --- a/x-pack/plugins/security/README.md +++ b/x-pack/plugins/security/README.md @@ -13,9 +13,9 @@ auditLogger.log({ message: 'User is updating dashboard [id=123]', event: { action: 'saved_object_update', - category: EventCategory.DATABASE, - type: EventType.CHANGE, - outcome: EventOutcome.UNKNOWN, + category: ['database'], + type: ['change'], + outcome: 'unknown', }, kibana: { saved_object: { type: 'dashboard', id: '123' }, diff --git a/x-pack/plugins/security/server/audit/audit_events.test.ts b/x-pack/plugins/security/server/audit/audit_events.test.ts index f986c57987022..779463aaaf794 100644 --- a/x-pack/plugins/security/server/audit/audit_events.test.ts +++ b/x-pack/plugins/security/server/audit/audit_events.test.ts @@ -12,7 +12,6 @@ import { httpServerMock } from 'src/core/server/mocks'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; import { AuthenticationResult } from '../authentication'; import { - EventOutcome, httpRequestEvent, SavedObjectAction, savedObjectEvent, @@ -26,7 +25,7 @@ describe('#savedObjectEvent', () => { expect( savedObjectEvent({ action: SavedObjectAction.CREATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, }) ).toMatchInlineSnapshot(` @@ -34,9 +33,13 @@ describe('#savedObjectEvent', () => { "error": undefined, "event": Object { "action": "saved_object_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "unknown", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "add_to_spaces": undefined, @@ -62,9 +65,13 @@ describe('#savedObjectEvent', () => { "error": undefined, "event": Object { "action": "saved_object_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "success", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "add_to_spaces": undefined, @@ -94,9 +101,13 @@ describe('#savedObjectEvent', () => { }, "event": Object { "action": "saved_object_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "failure", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "add_to_spaces": undefined, @@ -197,9 +208,13 @@ describe('#savedObjectEvent', () => { "error": undefined, "event": Object { "action": "saved_object_remove_references", - "category": "database", + "category": Array [ + "database", + ], "outcome": "success", - "type": "change", + "type": Array [ + "change", + ], }, "kibana": Object { "add_to_spaces": undefined, @@ -228,7 +243,9 @@ describe('#userLoginEvent', () => { "error": undefined, "event": Object { "action": "user_login", - "category": "authentication", + "category": Array [ + "authentication", + ], "outcome": "success", }, "kibana": Object { @@ -264,7 +281,9 @@ describe('#userLoginEvent', () => { }, "event": Object { "action": "user_login", - "category": "authentication", + "category": Array [ + "authentication", + ], "outcome": "failure", }, "kibana": Object { @@ -291,7 +310,9 @@ describe('#httpRequestEvent', () => { Object { "event": Object { "action": "http_request", - "category": "web", + "category": Array [ + "web", + ], "outcome": "unknown", }, "http": Object { @@ -328,7 +349,9 @@ describe('#httpRequestEvent', () => { Object { "event": Object { "action": "http_request", - "category": "web", + "category": Array [ + "web", + ], "outcome": "unknown", }, "http": Object { @@ -354,7 +377,7 @@ describe('#spaceAuditEvent', () => { expect( spaceAuditEvent({ action: SpaceAuditAction.CREATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'space', id: 'SPACE_ID' }, }) ).toMatchInlineSnapshot(` @@ -362,9 +385,13 @@ describe('#spaceAuditEvent', () => { "error": undefined, "event": Object { "action": "space_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "unknown", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { @@ -388,9 +415,13 @@ describe('#spaceAuditEvent', () => { "error": undefined, "event": Object { "action": "space_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "success", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { @@ -418,9 +449,13 @@ describe('#spaceAuditEvent', () => { }, "event": Object { "action": "space_create", - "category": "database", + "category": Array [ + "database", + ], "outcome": "failure", - "type": "creation", + "type": Array [ + "creation", + ], }, "kibana": Object { "saved_object": Object { diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index 00f77ff2bc5fd..70d8149682370 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -5,36 +5,20 @@ * 2.0. */ -import type { KibanaRequest } from 'src/core/server'; +import type { EcsEventOutcome, EcsEventType, KibanaRequest, LogMeta } from 'src/core/server'; import type { AuthenticationResult } from '../authentication/authentication_result'; /** - * Audit event schema using ECS format: https://www.elastic.co/guide/en/ecs/1.6/index.html + * Audit event schema using ECS format: https://www.elastic.co/guide/en/ecs/1.9/index.html * * If you add additional fields to the schema ensure you update the Kibana Filebeat module: * https://github.com/elastic/beats/tree/master/filebeat/module/kibana * * @public */ -export interface AuditEvent { - /** - * Human readable message describing action, outcome and user. - * - * @example - * Failed attempt to login using basic provider [name=basic1] - */ +export interface AuditEvent extends LogMeta { message: string; - event: { - action: string; - category?: EventCategory; - type?: EventType; - outcome?: EventOutcome; - }; - user?: { - name: string; - roles?: readonly string[]; - }; kibana?: { /** * The ID of the space associated with this event. @@ -77,41 +61,6 @@ export interface AuditEvent { */ delete_from_spaces?: readonly string[]; }; - error?: { - code?: string; - message?: string; - }; - http?: { - request?: { - method?: string; - }; - }; - url?: { - domain?: string; - path?: string; - port?: number; - query?: string; - scheme?: string; - }; -} - -export enum EventCategory { - DATABASE = 'database', - WEB = 'web', - AUTHENTICATION = 'authentication', -} - -export enum EventType { - CREATION = 'creation', - ACCESS = 'access', - CHANGE = 'change', - DELETION = 'deletion', -} - -export enum EventOutcome { - SUCCESS = 'success', - FAILURE = 'failure', - UNKNOWN = 'unknown', } export interface HttpRequestParams { @@ -125,8 +74,8 @@ export function httpRequestEvent({ request }: HttpRequestParams): AuditEvent { message: `User is requesting [${url.pathname}] endpoint`, event: { action: 'http_request', - category: EventCategory.WEB, - outcome: EventOutcome.UNKNOWN, + category: ['web'], + outcome: 'unknown', }, http: { request: { @@ -160,12 +109,12 @@ export function userLoginEvent({ : `Failed attempt to login using ${authenticationType} provider [name=${authenticationProvider}]`, event: { action: 'user_login', - category: EventCategory.AUTHENTICATION, - outcome: authenticationResult.user ? EventOutcome.SUCCESS : EventOutcome.FAILURE, + category: ['authentication'], + outcome: authenticationResult.user ? 'success' : 'failure', }, user: authenticationResult.user && { name: authenticationResult.user.username, - roles: authenticationResult.user.roles, + roles: authenticationResult.user.roles as string[], }, kibana: { space_id: undefined, // Ensure this does not get populated by audit service @@ -223,23 +172,23 @@ const savedObjectAuditVerbs: Record = { ], }; -const savedObjectAuditTypes: Record = { - saved_object_create: EventType.CREATION, - saved_object_get: EventType.ACCESS, - saved_object_resolve: EventType.ACCESS, - saved_object_update: EventType.CHANGE, - saved_object_delete: EventType.DELETION, - saved_object_find: EventType.ACCESS, - saved_object_add_to_spaces: EventType.CHANGE, - saved_object_delete_from_spaces: EventType.CHANGE, - saved_object_open_point_in_time: EventType.CREATION, - saved_object_close_point_in_time: EventType.DELETION, - saved_object_remove_references: EventType.CHANGE, +const savedObjectAuditTypes: Record = { + saved_object_create: 'creation', + saved_object_get: 'access', + saved_object_resolve: 'access', + saved_object_update: 'change', + saved_object_delete: 'deletion', + saved_object_find: 'access', + saved_object_add_to_spaces: 'change', + saved_object_delete_from_spaces: 'change', + saved_object_open_point_in_time: 'creation', + saved_object_close_point_in_time: 'deletion', + saved_object_remove_references: 'change', }; export interface SavedObjectEventParams { action: SavedObjectAction; - outcome?: EventOutcome; + outcome?: EcsEventOutcome; savedObject?: NonNullable['saved_object']; addToSpaces?: readonly string[]; deleteFromSpaces?: readonly string[]; @@ -258,13 +207,13 @@ export function savedObjectEvent({ const [present, progressive, past] = savedObjectAuditVerbs[action]; const message = error ? `Failed attempt to ${present} ${doc}` - : outcome === EventOutcome.UNKNOWN + : outcome === 'unknown' ? `User is ${progressive} ${doc}` : `User has ${past} ${doc}`; const type = savedObjectAuditTypes[action]; if ( - type === EventType.ACCESS && + type === 'access' && savedObject && (savedObject.type === 'config' || savedObject.type === 'telemetry') ) { @@ -275,9 +224,9 @@ export function savedObjectEvent({ message, event: { action, - category: EventCategory.DATABASE, - type, - outcome: outcome ?? (error ? EventOutcome.FAILURE : EventOutcome.SUCCESS), + category: ['database'], + type: [type], + outcome: outcome ?? (error ? 'failure' : 'success'), }, kibana: { saved_object: savedObject, @@ -307,17 +256,17 @@ const spaceAuditVerbs: Record = { space_find: ['access', 'accessing', 'accessed'], }; -const spaceAuditTypes: Record = { - space_create: EventType.CREATION, - space_get: EventType.ACCESS, - space_update: EventType.CHANGE, - space_delete: EventType.DELETION, - space_find: EventType.ACCESS, +const spaceAuditTypes: Record = { + space_create: 'creation', + space_get: 'access', + space_update: 'change', + space_delete: 'deletion', + space_find: 'access', }; export interface SpacesAuditEventParams { action: SpaceAuditAction; - outcome?: EventOutcome; + outcome?: EcsEventOutcome; savedObject?: NonNullable['saved_object']; error?: Error; } @@ -332,7 +281,7 @@ export function spaceAuditEvent({ const [present, progressive, past] = spaceAuditVerbs[action]; const message = error ? `Failed attempt to ${present} ${doc}` - : outcome === EventOutcome.UNKNOWN + : outcome === 'unknown' ? `User is ${progressive} ${doc}` : `User has ${past} ${doc}`; const type = spaceAuditTypes[action]; @@ -341,9 +290,9 @@ export function spaceAuditEvent({ message, event: { action, - category: EventCategory.DATABASE, - type, - outcome: outcome ?? (error ? EventOutcome.FAILURE : EventOutcome.SUCCESS), + category: ['database'], + type: [type], + outcome: outcome ?? (error ? 'failure' : 'success'), }, kibana: { saved_object: savedObject, 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 ffacaff7237c5..7c7bc4f031793 100644 --- a/x-pack/plugins/security/server/audit/audit_service.test.ts +++ b/x-pack/plugins/security/server/audit/audit_service.test.ts @@ -19,7 +19,6 @@ import { licenseMock } from '../../common/licensing/index.mock'; import type { ConfigType } from '../config'; import { ConfigSchema } from '../config'; import type { AuditEvent } from './audit_events'; -import { EventCategory, EventOutcome, EventType } from './audit_events'; import { AuditService, createLoggingConfig, @@ -185,10 +184,8 @@ describe('#asScoped', () => { await auditSetup.asScoped(request).log({ message: 'MESSAGE', event: { action: 'ACTION' } }); expect(logger.info).toHaveBeenCalledWith('MESSAGE', { - ecs: { version: '1.6.0' }, event: { action: 'ACTION' }, kibana: { space_id: 'default', session_id: 'SESSION_ID' }, - message: 'MESSAGE', trace: { id: 'REQUEST_ID' }, user: { name: 'jdoe', roles: ['admin'] }, }); @@ -349,21 +346,25 @@ describe('#createLoggingConfig', () => { }); describe('#filterEvent', () => { - const event: AuditEvent = { - message: 'this is my audit message', - event: { - action: 'http_request', - category: EventCategory.WEB, - type: EventType.ACCESS, - outcome: EventOutcome.SUCCESS, - }, - user: { - name: 'jdoe', - }, - kibana: { - space_id: 'default', - }, - }; + let event: AuditEvent; + + beforeEach(() => { + event = { + message: 'this is my audit message', + event: { + action: 'http_request', + category: ['web'], + type: ['access'], + outcome: 'success', + }, + user: { + name: 'jdoe', + }, + kibana: { + space_id: 'default', + }, + }; + }); test('keeps event when ignore filters are undefined or empty', () => { expect(filterEvent(event, undefined)).toBeTruthy(); @@ -421,6 +422,66 @@ describe('#filterEvent', () => { ).toBeTruthy(); }); + test('keeps event when one item per category does not match', () => { + event = { + message: 'this is my audit message', + event: { + action: 'http_request', + category: ['authentication', 'web'], + type: ['access'], + outcome: 'success', + }, + user: { + name: 'jdoe', + }, + kibana: { + space_id: 'default', + }, + }; + + expect( + filterEvent(event, [ + { + actions: ['http_request'], + categories: ['web', 'NO_MATCH'], + types: ['access'], + outcomes: ['success'], + spaces: ['default'], + }, + ]) + ).toBeTruthy(); + }); + + test('keeps event when one item per type does not match', () => { + event = { + message: 'this is my audit message', + event: { + action: 'http_request', + category: ['web'], + type: ['access', 'user'], + outcome: 'success', + }, + user: { + name: 'jdoe', + }, + kibana: { + space_id: 'default', + }, + }; + + expect( + filterEvent(event, [ + { + actions: ['http_request'], + categories: ['web'], + types: ['access', 'NO_MATCH'], + outcomes: ['success'], + spaces: ['default'], + }, + ]) + ).toBeTruthy(); + }); + test('filters out event when all criteria in a single rule match', () => { expect( filterEvent(event, [ @@ -441,6 +502,66 @@ describe('#filterEvent', () => { ]) ).toBeFalsy(); }); + + test('filters out event when all categories match', () => { + event = { + message: 'this is my audit message', + event: { + action: 'http_request', + category: ['authentication', 'web'], + type: ['access'], + outcome: 'success', + }, + user: { + name: 'jdoe', + }, + kibana: { + space_id: 'default', + }, + }; + + expect( + filterEvent(event, [ + { + actions: ['http_request'], + categories: ['authentication', 'web'], + types: ['access'], + outcomes: ['success'], + spaces: ['default'], + }, + ]) + ).toBeFalsy(); + }); + + test('filters out event when all types match', () => { + event = { + message: 'this is my audit message', + event: { + action: 'http_request', + category: ['web'], + type: ['access', 'user'], + outcome: 'success', + }, + user: { + name: 'jdoe', + }, + kibana: { + space_id: 'default', + }, + }; + + expect( + filterEvent(event, [ + { + actions: ['http_request'], + categories: ['web'], + types: ['access', 'user'], + outcomes: ['success'], + spaces: ['default'], + }, + ]) + ).toBeFalsy(); + }); }); describe('#getLogger', () => { diff --git a/x-pack/plugins/security/server/audit/audit_service.ts b/x-pack/plugins/security/server/audit/audit_service.ts index 7511e079b9adb..a6205ff196537 100644 --- a/x-pack/plugins/security/server/audit/audit_service.ts +++ b/x-pack/plugins/security/server/audit/audit_service.ts @@ -37,15 +37,6 @@ export interface AuditLogger { log: (event: AuditEvent | undefined) => void; } -interface AuditLogMeta extends AuditEvent { - ecs: { - version: string; - }; - trace: { - id: string; - }; -} - export interface AuditServiceSetup { asScoped: (request: KibanaRequest) => AuditLogger; getLogger: (id?: string) => LegacyAuditLogger; @@ -146,7 +137,7 @@ export class AuditService { * message: 'User is updating dashboard [id=123]', * event: { * action: 'saved_object_update', - * outcome: EventOutcome.UNKNOWN + * outcome: 'unknown' * }, * kibana: { * saved_object: { type: 'dashboard', id: '123' } @@ -161,13 +152,12 @@ export class AuditService { const spaceId = getSpaceId(request); const user = getCurrentUser(request); const sessionId = await getSID(request); - const meta: AuditLogMeta = { - ecs: { version: ECS_VERSION }, + const meta: AuditEvent = { ...event, user: (user && { name: user.username, - roles: user.roles, + roles: user.roles as string[], }) || event.user, kibana: { @@ -178,7 +168,8 @@ export class AuditService { trace: { id: request.id }, }; if (filterEvent(meta, config.ignore_filters)) { - this.ecsLogger.info(event.message!, meta); + const { message, ...eventMeta } = meta; + this.ecsLogger.info(message, eventMeta); } }; return { log }; @@ -243,6 +234,13 @@ export const createLoggingConfig = (config: ConfigType['audit']) => ], })); +/** + * Evaluates the list of provided ignore rules, and filters out events only + * if *all* rules match the event. + * + * For event fields that can contain an array of multiple values, every value + * must be matched by an ignore rule for the event to be excluded. + */ export function filterEvent( event: AuditEvent, ignoreFilters: ConfigType['audit']['ignore_filters'] @@ -250,10 +248,10 @@ export function filterEvent( if (ignoreFilters) { return !ignoreFilters.some( (rule) => - (!rule.actions || rule.actions.includes(event.event.action)) && - (!rule.categories || rule.categories.includes(event.event.category!)) && - (!rule.types || rule.types.includes(event.event.type!)) && - (!rule.outcomes || rule.outcomes.includes(event.event.outcome!)) && + (!rule.actions || rule.actions.includes(event.event?.action!)) && + (!rule.categories || event.event?.category?.every((c) => rule.categories?.includes(c))) && + (!rule.types || event.event?.type?.every((t) => rule.types?.includes(t))) && + (!rule.outcomes || rule.outcomes.includes(event.event?.outcome!)) && (!rule.spaces || rule.spaces.includes(event.kibana?.space_id!)) ); } diff --git a/x-pack/plugins/security/server/audit/index.ts b/x-pack/plugins/security/server/audit/index.ts index ebf1e9bed5df6..c42022bc76aa9 100644 --- a/x-pack/plugins/security/server/audit/index.ts +++ b/x-pack/plugins/security/server/audit/index.ts @@ -8,9 +8,6 @@ export { AuditService, AuditServiceSetup, AuditLogger, LegacyAuditLogger } from './audit_service'; export { AuditEvent, - EventCategory, - EventType, - EventOutcome, userLoginEvent, httpRequestEvent, savedObjectEvent, diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index be53caffc066d..1bd430d0c5c98 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -337,7 +337,7 @@ describe('Authenticator', () => { expect(auditLogger.log).toHaveBeenCalledTimes(1); expect(auditLogger.log).toHaveBeenCalledWith( expect.objectContaining({ - event: { action: 'user_login', category: 'authentication', outcome: 'success' }, + event: { action: 'user_login', category: ['authentication'], outcome: 'success' }, }) ); }); @@ -353,7 +353,7 @@ describe('Authenticator', () => { expect(auditLogger.log).toHaveBeenCalledTimes(1); expect(auditLogger.log).toHaveBeenCalledWith( expect.objectContaining({ - event: { action: 'user_login', category: 'authentication', outcome: 'failure' }, + event: { action: 'user_login', category: ['authentication'], outcome: 'failure' }, }) ); }); diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index 6412562af8a41..b66ed6e9eb7ca 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -27,14 +27,7 @@ export type { GrantAPIKeyResult, } from './authentication'; export type { CheckPrivilegesPayload } from './authorization'; -export { - LegacyAuditLogger, - AuditLogger, - AuditEvent, - EventCategory, - EventType, - EventOutcome, -} from './audit'; +export { LegacyAuditLogger, AuditLogger, AuditEvent } from './audit'; export type { SecurityPluginSetup, SecurityPluginStart }; export type { AuthenticatedUser } from '../common/model'; diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts index 554244dc98be9..2658f4edec5ac 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts @@ -5,11 +5,10 @@ * 2.0. */ -import type { SavedObjectsClientContract } from 'src/core/server'; +import type { EcsEventOutcome, SavedObjectsClientContract } from 'src/core/server'; import { httpServerMock, savedObjectsClientMock } from 'src/core/server/mocks'; import type { AuditEvent } from '../audit'; -import { EventOutcome } from '../audit'; import { auditServiceMock, securityAuditLoggerMock } from '../audit/index.mock'; import { Actions } from '../authorization'; import type { SavedObjectActions } from '../authorization/actions/saved_object'; @@ -199,8 +198,8 @@ const expectObjectNamespaceFiltering = async ( }; const expectAuditEvent = ( - action: AuditEvent['event']['action'], - outcome: AuditEvent['event']['outcome'], + action: string, + outcome: EcsEventOutcome, savedObject?: Required['kibana']['saved_object'] ) => { expect(clientOpts.auditLogger.log).toHaveBeenCalledWith( @@ -445,14 +444,14 @@ describe('#addToNamespaces', () => { await client.addToNamespaces(type, id, namespaces); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_add_to_spaces', EventOutcome.UNKNOWN, { type, id }); + expectAuditEvent('saved_object_add_to_spaces', 'unknown', { type, id }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.addToNamespaces(type, id, namespaces)).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_add_to_spaces', EventOutcome.FAILURE, { type, id }); + expectAuditEvent('saved_object_add_to_spaces', 'failure', { type, id }); }); }); @@ -515,16 +514,16 @@ describe('#bulkCreate', () => { const options = { namespace }; await expectSuccess(client.bulkCreate, { objects, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_create', EventOutcome.UNKNOWN, { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_create', EventOutcome.UNKNOWN, { type: obj2.type, id: obj2.id }); + expectAuditEvent('saved_object_create', 'unknown', { type: obj1.type, id: obj1.id }); + expectAuditEvent('saved_object_create', 'unknown', { type: obj2.type, id: obj2.id }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.bulkCreate([obj1, obj2], { namespace })).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_create', EventOutcome.FAILURE, { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_create', EventOutcome.FAILURE, { type: obj2.type, id: obj2.id }); + expectAuditEvent('saved_object_create', 'failure', { type: obj1.type, id: obj1.id }); + expectAuditEvent('saved_object_create', 'failure', { type: obj2.type, id: obj2.id }); }); }); @@ -573,16 +572,16 @@ describe('#bulkGet', () => { const options = { namespace }; await expectSuccess(client.bulkGet, { objects, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_get', EventOutcome.SUCCESS, obj1); - expectAuditEvent('saved_object_get', EventOutcome.SUCCESS, obj2); + expectAuditEvent('saved_object_get', 'success', obj1); + expectAuditEvent('saved_object_get', 'success', obj2); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.bulkGet([obj1, obj2], { namespace })).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_get', EventOutcome.FAILURE, obj1); - expectAuditEvent('saved_object_get', EventOutcome.FAILURE, obj2); + expectAuditEvent('saved_object_get', 'failure', obj1); + expectAuditEvent('saved_object_get', 'failure', obj2); }); }); @@ -642,16 +641,16 @@ describe('#bulkUpdate', () => { const options = { namespace }; await expectSuccess(client.bulkUpdate, { objects, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_update', EventOutcome.UNKNOWN, { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_update', EventOutcome.UNKNOWN, { type: obj2.type, id: obj2.id }); + expectAuditEvent('saved_object_update', 'unknown', { type: obj1.type, id: obj1.id }); + expectAuditEvent('saved_object_update', 'unknown', { type: obj2.type, id: obj2.id }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.bulkUpdate([obj1, obj2], { namespace })).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_update', EventOutcome.FAILURE, { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_update', EventOutcome.FAILURE, { type: obj2.type, id: obj2.id }); + expectAuditEvent('saved_object_update', 'failure', { type: obj1.type, id: obj1.id }); + expectAuditEvent('saved_object_update', 'failure', { type: obj2.type, id: obj2.id }); }); }); @@ -744,14 +743,14 @@ describe('#create', () => { const options = { id: 'mock-saved-object-id', namespace }; await expectSuccess(client.create, { type, attributes, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_create', EventOutcome.UNKNOWN, { type, id: expect.any(String) }); + expectAuditEvent('saved_object_create', 'unknown', { type, id: expect.any(String) }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.create(type, attributes, { namespace })).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_create', EventOutcome.FAILURE, { type, id: expect.any(String) }); + expectAuditEvent('saved_object_create', 'failure', { type, id: expect.any(String) }); }); }); @@ -789,14 +788,14 @@ describe('#delete', () => { const options = { namespace }; await expectSuccess(client.delete, { type, id, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_delete', EventOutcome.UNKNOWN, { type, id }); + expectAuditEvent('saved_object_delete', 'unknown', { type, id }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.delete(type, id)).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_delete', EventOutcome.FAILURE, { type, id }); + expectAuditEvent('saved_object_delete', 'failure', { type, id }); }); }); @@ -936,8 +935,8 @@ describe('#find', () => { const options = Object.freeze({ type: type1, namespaces: ['some-ns'] }); await expectSuccess(client.find, { options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_find', EventOutcome.SUCCESS, obj1); - expectAuditEvent('saved_object_find', EventOutcome.SUCCESS, obj2); + expectAuditEvent('saved_object_find', 'success', obj1); + expectAuditEvent('saved_object_find', 'success', obj2); }); test(`adds audit event when not successful`, async () => { @@ -946,7 +945,7 @@ describe('#find', () => { ); await client.find({ type: type1 }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_find', EventOutcome.FAILURE); + expectAuditEvent('saved_object_find', 'failure'); }); }); @@ -989,14 +988,14 @@ describe('#get', () => { const options = { namespace }; await expectSuccess(client.get, { type, id, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_get', EventOutcome.SUCCESS, { type, id }); + expectAuditEvent('saved_object_get', 'success', { type, id }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.get(type, id, { namespace })).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_get', EventOutcome.FAILURE, { type, id }); + expectAuditEvent('saved_object_get', 'failure', { type, id }); }); }); @@ -1023,14 +1022,14 @@ describe('#openPointInTimeForType', () => { const options = { namespace }; await expectSuccess(client.openPointInTimeForType, { type, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_open_point_in_time', EventOutcome.UNKNOWN); + expectAuditEvent('saved_object_open_point_in_time', 'unknown'); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.openPointInTimeForType(type, { namespace })).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_open_point_in_time', EventOutcome.FAILURE); + expectAuditEvent('saved_object_open_point_in_time', 'failure'); }); }); @@ -1054,7 +1053,7 @@ describe('#closePointInTime', () => { const options = { namespace }; await client.closePointInTime(id, options); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_close_point_in_time', EventOutcome.UNKNOWN); + expectAuditEvent('saved_object_close_point_in_time', 'unknown'); }); }); @@ -1153,14 +1152,14 @@ describe('#resolve', () => { const options = { namespace }; await expectSuccess(client.resolve, { type, id, options }, 'resolve'); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_resolve', EventOutcome.SUCCESS, { type, id: resolvedId }); + expectAuditEvent('saved_object_resolve', 'success', { type, id: resolvedId }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.resolve(type, id, { namespace })).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_resolve', EventOutcome.FAILURE, { type, id }); + expectAuditEvent('saved_object_resolve', 'failure', { type, id }); }); }); @@ -1239,14 +1238,14 @@ describe('#deleteFromNamespaces', () => { clientOpts.baseClient.deleteFromNamespaces.mockReturnValue(apiCallReturnValue as any); await client.deleteFromNamespaces(type, id, namespaces); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_delete_from_spaces', EventOutcome.UNKNOWN, { type, id }); + expectAuditEvent('saved_object_delete_from_spaces', 'unknown', { type, id }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.deleteFromNamespaces(type, id, namespaces)).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_delete_from_spaces', EventOutcome.FAILURE, { type, id }); + expectAuditEvent('saved_object_delete_from_spaces', 'failure', { type, id }); }); }); @@ -1290,14 +1289,14 @@ describe('#update', () => { const options = { namespace }; await expectSuccess(client.update, { type, id, attributes, options }); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_update', EventOutcome.UNKNOWN, { type, id }); + expectAuditEvent('saved_object_update', 'unknown', { type, id }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.update(type, id, attributes, { namespace })).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_update', EventOutcome.FAILURE, { type, id }); + expectAuditEvent('saved_object_update', 'failure', { type, id }); }); }); @@ -1341,14 +1340,14 @@ describe('#removeReferencesTo', () => { await client.removeReferencesTo(type, id); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_remove_references', EventOutcome.UNKNOWN, { type, id }); + expectAuditEvent('saved_object_remove_references', 'unknown', { type, id }); }); test(`adds audit event when not successful`, async () => { clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); await expect(() => client.removeReferencesTo(type, id)).rejects.toThrow(); expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_remove_references', EventOutcome.FAILURE, { type, id }); + expectAuditEvent('saved_object_remove_references', 'failure', { type, id }); }); }); diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts index d876175a05fe8..066a720f70721 100644 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts @@ -28,7 +28,7 @@ import type { import { SavedObjectsUtils } from '../../../../../src/core/server'; import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../common/constants'; import type { AuditLogger, SecurityAuditLogger } from '../audit'; -import { EventOutcome, SavedObjectAction, savedObjectEvent } from '../audit'; +import { SavedObjectAction, savedObjectEvent } from '../audit'; import type { Actions, CheckSavedObjectsPrivileges } from '../authorization'; import type { CheckPrivilegesResponse } from '../authorization/types'; import type { SpacesService } from '../plugin'; @@ -116,7 +116,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.CREATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type, id: optionsWithId.id }, }) ); @@ -178,7 +178,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.CREATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type, id }, }) ) @@ -205,7 +205,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.DELETE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type, id }, }) ); @@ -400,7 +400,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.UPDATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type, id }, }) ); @@ -446,7 +446,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.ADD_TO_SPACES, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type, id }, addToSpaces: namespaces, }) @@ -483,7 +483,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.DELETE_FROM_SPACES, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type, id }, deleteFromSpaces: namespaces, }) @@ -524,7 +524,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.UPDATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type, id }, }) ) @@ -560,7 +560,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra savedObjectEvent({ action: SavedObjectAction.REMOVE_REFERENCES, savedObject: { type, id }, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', }) ); @@ -592,7 +592,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.OPEN_POINT_IN_TIME, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', }) ); @@ -611,7 +611,7 @@ export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContra this.auditLogger.log( savedObjectEvent({ action: SavedObjectAction.CLOSE_POINT_IN_TIME, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', }) ); diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts index 3f17d18bbe5f7..0b8a7abab2382 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts @@ -6,13 +6,14 @@ */ import { deepFreeze } from '@kbn/std'; +import type { EcsEventOutcome } from 'src/core/server'; import { SavedObjectsErrorHelpers } from 'src/core/server'; import { httpServerMock } from 'src/core/server/mocks'; import type { GetAllSpacesPurpose, Space } from '../../../spaces/server'; import { spacesClientMock } from '../../../spaces/server/mocks'; import type { AuditEvent, AuditLogger } from '../audit'; -import { EventOutcome, SpaceAuditAction } from '../audit'; +import { SpaceAuditAction } from '../audit'; import { auditServiceMock } from '../audit/index.mock'; import type { AuthorizationServiceSetup } from '../authorization'; import { authorizationMock } from '../authorization/index.mock'; @@ -135,8 +136,8 @@ const expectSuccessAuditLogging = ( const expectAuditEvent = ( auditLogger: AuditLogger, - action: AuditEvent['event']['action'], - outcome: AuditEvent['event']['outcome'], + action: string, + outcome: EcsEventOutcome, savedObject?: Required['kibana']['saved_object'] ) => { expect(auditLogger.log).toHaveBeenCalledWith( @@ -194,15 +195,15 @@ describe('SecureSpacesClientWrapper', () => { expect(response).toEqual(spaces); expectNoAuthorizationCheck(authorization); expectNoAuditLogging(legacyAuditLogger); - expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.SUCCESS, { + expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', { type: 'space', id: spaces[0].id, }); - expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.SUCCESS, { + expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', { type: 'space', id: spaces[1].id, }); - expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.SUCCESS, { + expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', { type: 'space', id: spaces[2].id, }); @@ -285,7 +286,7 @@ describe('SecureSpacesClientWrapper', () => { ); expectForbiddenAuditLogging(legacyAuditLogger, username, 'getAll'); - expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.FAILURE); + expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'failure'); }); test(`returns spaces that the user is authorized for`, async () => { @@ -330,7 +331,7 @@ describe('SecureSpacesClientWrapper', () => { ); expectSuccessAuditLogging(legacyAuditLogger, username, 'getAll', [spaces[0].id]); - expectAuditEvent(auditLogger, SpaceAuditAction.FIND, EventOutcome.SUCCESS, { + expectAuditEvent(auditLogger, SpaceAuditAction.FIND, 'success', { type: 'space', id: spaces[0].id, }); @@ -351,7 +352,7 @@ describe('SecureSpacesClientWrapper', () => { expect(response).toEqual(spaces[0]); expectNoAuthorizationCheck(authorization); expectNoAuditLogging(legacyAuditLogger); - expectAuditEvent(auditLogger, SpaceAuditAction.GET, EventOutcome.SUCCESS, { + expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'success', { type: 'space', id: spaces[0].id, }); @@ -392,7 +393,7 @@ describe('SecureSpacesClientWrapper', () => { }); expectForbiddenAuditLogging(legacyAuditLogger, username, 'get', spaceId); - expectAuditEvent(auditLogger, SpaceAuditAction.GET, EventOutcome.FAILURE, { + expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'failure', { type: 'space', id: spaces[0].id, }); @@ -432,7 +433,7 @@ describe('SecureSpacesClientWrapper', () => { }); expectSuccessAuditLogging(legacyAuditLogger, username, 'get', [spaceId]); - expectAuditEvent(auditLogger, SpaceAuditAction.GET, EventOutcome.SUCCESS, { + expectAuditEvent(auditLogger, SpaceAuditAction.GET, 'success', { type: 'space', id: spaceId, }); @@ -457,7 +458,7 @@ describe('SecureSpacesClientWrapper', () => { expect(response).toEqual(space); expectNoAuthorizationCheck(authorization); expectNoAuditLogging(legacyAuditLogger); - expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, EventOutcome.UNKNOWN, { + expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'unknown', { type: 'space', id: space.id, }); @@ -495,7 +496,7 @@ describe('SecureSpacesClientWrapper', () => { }); expectForbiddenAuditLogging(legacyAuditLogger, username, 'create'); - expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, EventOutcome.FAILURE, { + expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'failure', { type: 'space', id: space.id, }); @@ -534,7 +535,7 @@ describe('SecureSpacesClientWrapper', () => { }); expectSuccessAuditLogging(legacyAuditLogger, username, 'create'); - expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, EventOutcome.UNKNOWN, { + expectAuditEvent(auditLogger, SpaceAuditAction.CREATE, 'unknown', { type: 'space', id: space.id, }); @@ -559,7 +560,7 @@ describe('SecureSpacesClientWrapper', () => { expect(response).toEqual(space.id); expectNoAuthorizationCheck(authorization); expectNoAuditLogging(legacyAuditLogger); - expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, EventOutcome.UNKNOWN, { + expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'unknown', { type: 'space', id: space.id, }); @@ -597,7 +598,7 @@ describe('SecureSpacesClientWrapper', () => { }); expectForbiddenAuditLogging(legacyAuditLogger, username, 'update'); - expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, EventOutcome.FAILURE, { + expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'failure', { type: 'space', id: space.id, }); @@ -636,7 +637,7 @@ describe('SecureSpacesClientWrapper', () => { }); expectSuccessAuditLogging(legacyAuditLogger, username, 'update'); - expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, EventOutcome.UNKNOWN, { + expectAuditEvent(auditLogger, SpaceAuditAction.UPDATE, 'unknown', { type: 'space', id: space.id, }); @@ -660,7 +661,7 @@ describe('SecureSpacesClientWrapper', () => { expect(baseClient.delete).toHaveBeenCalledWith(space.id); expectNoAuthorizationCheck(authorization); expectNoAuditLogging(legacyAuditLogger); - expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, EventOutcome.UNKNOWN, { + expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'unknown', { type: 'space', id: space.id, }); @@ -698,7 +699,7 @@ describe('SecureSpacesClientWrapper', () => { }); expectForbiddenAuditLogging(legacyAuditLogger, username, 'delete'); - expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, EventOutcome.FAILURE, { + expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'failure', { type: 'space', id: space.id, }); @@ -735,7 +736,7 @@ describe('SecureSpacesClientWrapper', () => { }); expectSuccessAuditLogging(legacyAuditLogger, username, 'delete'); - expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, EventOutcome.UNKNOWN, { + expectAuditEvent(auditLogger, SpaceAuditAction.DELETE, 'unknown', { type: 'space', id: space.id, }); diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts index 7257dc625d4b4..ab882570ac630 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts @@ -17,7 +17,7 @@ import type { Space, } from '../../../spaces/server'; import type { AuditLogger } from '../audit'; -import { EventOutcome, SpaceAuditAction, spaceAuditEvent } from '../audit'; +import { SpaceAuditAction, spaceAuditEvent } from '../audit'; import type { AuthorizationServiceSetup } from '../authorization'; import type { SecurityPluginSetup } from '../plugin'; import type { LegacySpacesAuditLogger } from './legacy_audit_logger'; @@ -207,7 +207,7 @@ export class SecureSpacesClientWrapper implements ISpacesClient { this.auditLogger.log( spaceAuditEvent({ action: SpaceAuditAction.CREATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'space', id: space.id }, }) ); @@ -238,7 +238,7 @@ export class SecureSpacesClientWrapper implements ISpacesClient { this.auditLogger.log( spaceAuditEvent({ action: SpaceAuditAction.UPDATE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'space', id }, }) ); @@ -269,7 +269,7 @@ export class SecureSpacesClientWrapper implements ISpacesClient { this.auditLogger.log( spaceAuditEvent({ action: SpaceAuditAction.DELETE, - outcome: EventOutcome.UNKNOWN, + outcome: 'unknown', savedObject: { type: 'space', id }, }) );