From de05b5d64d5c54ff69d73f93029dc7cbe7f9e9bc Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Wed, 4 Sep 2019 08:52:18 -0400 Subject: [PATCH] event log index and basic structures somewhat working --- rfcs/text/0006_event_log.md | 398 ++++ .../server/lib/action_executor.test.ts | 2 + .../actions/server/lib/action_executor.ts | 35 +- .../server/lib/task_runner_factory.test.ts | 2 + .../legacy/plugins/actions/server/plugin.ts | 14 + x-pack/legacy/plugins/actions/server/shim.ts | 3 + x-pack/plugins/event_log/generated/README.md | 4 + .../plugins/event_log/generated/mappings.json | 1876 +++++++++++++++++ x-pack/plugins/event_log/generated/schemas.ts | 1391 ++++++++++++ x-pack/plugins/event_log/kibana.json | 8 + .../event_log/scripts/create_schemas.js | 338 +++ .../event_log/scripts/lib/line_writer.js | 36 + .../plugins/event_log/server/config_schema.ts | 11 + x-pack/plugins/event_log/server/es/context.ts | 105 + .../plugins/event_log/server/es/documents.ts | 49 + x-pack/plugins/event_log/server/es/index.ts | 8 + x-pack/plugins/event_log/server/es/init.ts | 139 ++ x-pack/plugins/event_log/server/es/names.ts | 28 + .../event_log/server/event_log_service.ts | 74 + .../event_log/server/event_logger.mock.ts | 13 + .../plugins/event_log/server/event_logger.ts | 129 ++ x-pack/plugins/event_log/server/index.ts | 13 + .../event_log/server/lib/ready_signal.ts | 28 + x-pack/plugins/event_log/server/lib/retry.ts | 39 + x-pack/plugins/event_log/server/plugin.ts | 103 + .../plugins/event_log/server/routes/index.ts | 23 + .../server/routes/provider_actions.ts | 24 + .../plugins/event_log/server/routes/search.ts | 23 + x-pack/plugins/event_log/server/types.ts | 49 + 29 files changed, 4964 insertions(+), 1 deletion(-) create mode 100644 rfcs/text/0006_event_log.md create mode 100644 x-pack/plugins/event_log/generated/README.md create mode 100644 x-pack/plugins/event_log/generated/mappings.json create mode 100644 x-pack/plugins/event_log/generated/schemas.ts create mode 100644 x-pack/plugins/event_log/kibana.json create mode 100755 x-pack/plugins/event_log/scripts/create_schemas.js create mode 100644 x-pack/plugins/event_log/scripts/lib/line_writer.js create mode 100644 x-pack/plugins/event_log/server/config_schema.ts create mode 100644 x-pack/plugins/event_log/server/es/context.ts create mode 100644 x-pack/plugins/event_log/server/es/documents.ts create mode 100644 x-pack/plugins/event_log/server/es/index.ts create mode 100644 x-pack/plugins/event_log/server/es/init.ts create mode 100644 x-pack/plugins/event_log/server/es/names.ts create mode 100644 x-pack/plugins/event_log/server/event_log_service.ts create mode 100644 x-pack/plugins/event_log/server/event_logger.mock.ts create mode 100644 x-pack/plugins/event_log/server/event_logger.ts create mode 100644 x-pack/plugins/event_log/server/index.ts create mode 100644 x-pack/plugins/event_log/server/lib/ready_signal.ts create mode 100644 x-pack/plugins/event_log/server/lib/retry.ts create mode 100644 x-pack/plugins/event_log/server/plugin.ts create mode 100644 x-pack/plugins/event_log/server/routes/index.ts create mode 100644 x-pack/plugins/event_log/server/routes/provider_actions.ts create mode 100644 x-pack/plugins/event_log/server/routes/search.ts create mode 100644 x-pack/plugins/event_log/server/types.ts diff --git a/rfcs/text/0006_event_log.md b/rfcs/text/0006_event_log.md new file mode 100644 index 000000000000000..68365b924012f94 --- /dev/null +++ b/rfcs/text/0006_event_log.md @@ -0,0 +1,398 @@ +- Start Date: 2019-09-12 +- RFC PR: currently https://github.com/elastic/kibana/pull/45081 +- Kibana Issue: (leave this empty) + +See also issue https://github.com/elastic/kibana/issues/45083, which is an +umbrella issue tracking ongoing initial work. + + +# Summary + +For the Make It Action alerting / action plugins, we will need a way to +persist data regarding alerts and actions, for UI and investigative purposes. +We're referring to this persisted data as "events", and will be persisted to +a new elasticsearch index referred to as the "event log". + +Example events are actions firing, alerts running their scheduled functions, +alerts scheduling actions to run, etc. + +This functionality will be provided in a new NP plugin `event_log`, and will +provide server-side plugin APIs to write to the event log, and run queries +against it. We'll also want some query capability exposed as an HTTP endpoint. + +The current clients for the event log are the actions and alerting plugins, +however the event log currently has nothing specific to them, and is general +purpose, so can be used by any plugin to "log events". + +We currently assume that there may be many events logged, and that (some) customers +may not be interested in "old" events, and so to keep the event log from +consuming too much disk space, we'll set it up with ILM and some kind of +reasonable default policy that can be customized by the user. This implies +also the use of rollver, setting a write index alias upon rollover, and +that searches for events will be done via an ES index pattern / alias to search +across event log indices with a wildcard. + +# Basic example + +When an action is executed, an event should be written to the event log. + +Here's a [`kbn-action` command](https://github.com/pmuellr/kbn-action) to +execute a "server log" action (writes a message to the Kibana log): + +```console +$ kbn-action execute d90f5563-ab7c-4292-b56d-c68166c09a59 '{message: hallo}' +{ + "status": "ok" +} +``` + +Here's the event written to the event log index: + +```json +{ + "_index": ".kibana-event-log-000001", + "_type": "_doc", + "_id": "d2CXT20BPOpswQ8vgXp5", + "_score": 1, + "_source": { + "timestamp": "2019-09-20T16:53:12.177Z", + "pluginId": "actions", + "spaceId": "default", + "username": "elastic", + "type": "action", + "subType": "executed", + "data": { + "actionId": "d90f5563-ab7c-4292-b56d-c68166c09a59", + "actionTypeId": ".server-log", + "config": {}, + "params": { + "message": "hallo" + }, + "description": "server-log", + "result": { + "status": "ok" + } + } + } +} +``` + + +# Motivation + +The existing designs for Make It Action describe UIs which show some amount +of alert "history". Up till now, we had no where to write this history, +and so a new elasticsearch index is required for that. Because we already +have two known clients of this plugin, which are related but different, +the design of the event documents is very generic, which means it could easily +be used by other plugins that would like to record "events", for the same +general purposes. + + +# Detailed design + +## API + +```typescript +export interface IEventLog { + registerEventType(eventType: string, subTypes: string[]): void; + getEventTypes(): Map>; + + registerTags(tags: string[]): void; + getTags(): Set; + + getLogger(properties: Partial): IEventLogger; + + searchEvents(search: any): Promise; +} + +export interface IEventLogger { + logEvent(properties: Partial): void; +} + +export interface IEvent { + timestamp: Date; + spaceId: string; + username: string; + pluginId: string; + type: string; + subType: string; + tags: string[]; + data: any; +} +``` + +The plugin exposes an `IEventLog` object to plugins that pre-req it. Those +plugins need to call `registerEventType()` and `registerTags()` to indicate +the values of the `type`, `subType`, and `tags` values they will be using +when logging events. + +The pre-registration helps in two ways: + +- dealing with misspelled values +- constraining the values that will end up being indexed; for example, we + probably don't want people creating tags that contain a lot of uuids, or + dates, etc, to prevent high cardinality issues with the index. + +Once the values are registered, the plugin will get an `IEventLogger` instance +by passing in a set of default properties to be used for all it's logging, +to the `getLogger()` method. For instance, the `actions` plugin creates a +logger with it's plugin id and the only `type` value that it uses. + +The `IEventLogger` object can be cached at the plugin level and accessed by +any code in the plugin. It has a single method to write an event log entry, +`logEvent()`, which is passed specific properties for the event. + +The final data written is a combination of the data passed to `getLogger()` when +creating the logger, and the data passed on the `logEvent()` call, and then +that result is validated to ensure it's complete and valid. Errors will be +logged to the server log. + +The `logEvent()` method returns no values, and is itself not asynchronous. +It's a "call and forget" kind of thing. The method itself will arrange +to have the ultimate document written to the index asynchrnously. It's designed +this way because it's not clear what a client would do with a result from this +method, nor what it would do if the method threw an error. All the error +processing involved with getting the data into the index is handled internally, +and logged to the server log as appropriate. + + +## Stored data + +The elasticsearch index for the event log will have ILM and rollover support, +as customers may decide to only keep recent event documents, wanting indices +with older event documents deleted, turned cold, frozen, etc. We'll supply +some default values, but customers will be able to tweak these. + +The index template for these indices looks like this: + +```js +export function getIndexTemplate(esNames: EsNames, ilmExists: boolean) { + const mapDate = { type: 'date' }; + const mapObject = { type: 'object', enabled: false }; + const mapKeyword = { type: 'keyword', ignore_above: 256 }; + + return { + index_patterns: [esNames.indexPattern], + aliases: { + [esNames.alias]: { + is_write_index: true + }, + }, + settings: { + number_of_shards: 1, + number_of_replicas: 1, + 'index.lifecycle.name': esNames.ilmPolicy, + 'index.lifecycle.rollover_alias': esNames.alias, + }, + mappings: { + dynamic: 'strict', + properties: { + timestamp: mapDate, + type: mapKeyword, + subType: mapKeyword, + pluginId: mapKeyword, + spaceId: mapKeyword, + username: mapKeyword, + tags: mapKeyword, + data: mapObject, + }, + }, + }; +} +``` + +See [ilm rollover action docs][] for more info on the `is_write_index`, and `index.lifecycle.*` properties. + +[ilm rollover action docs]: https://www.elastic.co/guide/en/elasticsearch/reference/current/_actions.html#ilm-rollover-action + +Of particular note in the `mappings`: + +- `dynamic: 'strict'` implies users can't add new fields +- all the `properties` are indexed, except `data` which contains event-specific + data, and so event-specific data is not directly searchable + +Long-term we may want a story where event log clients can add their own +mappings in some new field `object` field that we do enable for indexing. Not +clear if that's required yet. + +From the "Basic Example" above, you can see that the action execution records +everything about the action itself and it's execution - action id, description, +config, **not the secrets** _of course_ :-), the action execution parameters, +and the result of the execution. Complete denormalization. Whether or not +this is appropriate or not is TBD, but seems like it might be fine. + + +## ILM setup + +We'll want to provide default ILM policy, this seems like a reasonable first +attempt: + +``` +PUT _ilm/policy/event_log_policy +{ + "policy": { + "phases": { + "hot": { + "actions": { + "rollover": { + "max_size": "5GB", + "max_age": "30d" + } + } + } + } + } +} +``` + +This means that ILM would "rollover" the current index, say +`.kibana-event-log-000001` by creating a new index `.kibana-event-log-000002`, +which would "inherit" everything from the index template, and then ILM will +set the write index of the the alias to the new index. This would happen +when the original index grew past 5 GB, or was created more than 30 days ago. + +For more relevant information on ILM, see: +[getting started with ILM doc][] and [write index alias behavior][]: + +[getting started with ILM doc]: https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-index-lifecycle-management.html +[write index alias behavior]: https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-rollover-index.html#indices-rollover-is-write-index + + +# Drawbacks + +TBD + + +# Alternatives + +## Using Saved Objects instead of a new index + +There are some potential advantages to using Saved Objects instead of a new +index as the persistence store: + +- setup would be easier, compared to the setup required using a new index + with ILM, etc +- support for per-event indexed properties +- support for migrations +- potential use of references to link events together (eg, an alert will + write a log entry for scheduling an action to run, and that action execution + also writes a log entry, so would be nice to "link" them together) +- any other good things Saved Objects gives us + +On the other hand, there are some known unknowns: + +- how would ILM rollover and Kibana migrations work, when both want to "own" + the index name suffix of rolled-over / migrated indices +- not clear if we will need more elaborate querying than Saved Objects + provides +- unknown unknowns, since the attempt at impplementing a saved object persistence + solution was never completed (and it was getting pretty complicated) + +## Concern with overlap with eventual audit log capability + +In an early review, concern was raised that there is a lot of overlap with +where we eventually be with audit logging. + +The issues with that are: + +- audit logs are currently just an `audit-log` tag on messages written to the + standard Kibana log; there is no infrastructure to allow them to be queried + +- security differences; the users able to read audit logs are probably much + more constrained than users that need to be able to read these event logs + +So, we don't really have an _alternative_ here, but ... a lot of overlap. +Long-term, it may make sense to make the eventual audit logging code +parameterizable, so that the core code could be used for other purposes, like +this event log. We're just not there yet. Or implement the audit log with +the event log :-) + +## Write to a events to a file and ingest via FileBeat + +This came up as _"why don't we use our own stuff to do this since that's what +it's designed for"_. Yup! However, the only thing that really changes over +writing our index is: + +- we'd write to some other log (could be console.log, or a file) instead of + an index, directly +- we'd have to arrange to run FileBeat alongside Kibana, ingesting these + logged events, which would send them into elasticsearch + +We still need to arrange to initialize all the elasticsearch resources (ilm, +index template, etc). + +The big kicker though is: where would we right these that FileBeat could +find them? Writing to a file might be problematic for users that run +Kibana with no writable file system, including folks using docker-based +deployments. + +Lots of unknown unknowns, it seems. + +Interestingly enough, this kind of story might work out extremely well for +Elastic Cloud, where we have a lot more control over the deployments! + +# Adoption strategy + +If we implement this proposal, how will existing Kibana developers adopt it? Is +this a breaking change? Can we write a codemod? Should we coordinate with +other projects or libraries? + +The API surface has been kept to a minimum to make it easy for other plugins +make use of it. But that's just a nice-to-have right now anyway, since +the only known clients will be actions and alerting. + + +# How we teach this + +**What names and terminology work best for these concepts and why? How is this +idea best presented? As a continuation of existing Kibana patterns?** + +"event" and "log" are probably the most controversial names in use here. +Previously, we've referred to this as an "audit log" or "history log". +Audit log is not a great name due to the existance of the audit logging +facility already available in Kibana. History or History log seems like +a closer fit. Event log works for me, as it provides just a touch more +information than history - it's a history of events - but of course "events" +as suitable vague enough that it doesn'tt add thatt much more. + +**Would the acceptance of this proposal mean the Kibana documentation must be +re-organized or altered? Does it change how Kibana is taught to new developers +at any level?** + +no + +**How should this feature be taught to existing Kibana developers?** + +Follow examples of other plugins using it. + +The API surface is very small, it's designed to be easy for other developers +to reuse. + + +# Unresolved questions + +**Optional, but suggested for first drafts. What parts of the design are still +TBD?** + +- Will we need more indexable fields in the event documents? + +- Will we need a way to link events together; for example, an alert schedules + an action to run, so at least two related events will be written, with no + direct (eg, indexable) linkage between them currently possible. + +- What do do when writing a log event document to an index encounters an + error relating to elasticsearch being down. Long-term we want to buffer + these in a file, but we should have some short-term solution until we get + there. Will probably mean providing a bounded queue of event documents + to be written when elasticsearch is available again. + + Not a great story, but it's also not clear, in practice, how many events + will need to be logged when elasticsearch is down. For the most part, + anything getting logged for actions and alerting will happen AFTER an + elasticsearch-related query anyway, meaning ... there just may not be + that many cases where this would happen in real life. We may in fact + need to do something special about "elasticsearch is down" kind of + errors - like specifically tell plugin users to not create such events - + as they could cause a thundering herd issue when in fact elasticsearch + goes down. Will be some fun experiments to run! \ No newline at end of file diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts index 661a08df3dc30dd..e2d2d7125d49ca1 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.test.ts @@ -13,6 +13,7 @@ import { SavedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; +import { createEventLoggerMock } from '../../../../../plugins/event_log/server/event_logger.mock'; const actionExecutor = new ActionExecutor(); const savedObjectsClient = SavedObjectsClientMock.create(); @@ -58,6 +59,7 @@ actionExecutor.initialize({ getServices, actionTypeRegistry, encryptedSavedObjectsPlugin, + eventLogger: createEventLoggerMock(), }); beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts index aef389262f884dc..ad6fc21c521ae09 100644 --- a/x-pack/legacy/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/legacy/plugins/actions/server/lib/action_executor.ts @@ -15,6 +15,8 @@ import { GetServicesFunction, RawAction, } from '../types'; +import { EVENT_LOG_ACTIONS } from '../plugin'; +import { IEvent, IEventLogger } from '../../../../../plugins/event_log/server/types'; export interface ActionExecutorContext { logger: Logger; @@ -22,6 +24,7 @@ export interface ActionExecutorContext { getServices: GetServicesFunction; encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; actionTypeRegistry: ActionTypeRegistryContract; + eventLogger: IEventLogger; } export interface ExecuteOptions { @@ -59,6 +62,7 @@ export class ActionExecutor { getServices, encryptedSavedObjectsPlugin, actionTypeRegistry, + eventLogger, } = this.actionExecutorContext!; const spacesPlugin = spaces(); @@ -97,6 +101,24 @@ export class ActionExecutor { let result: ActionTypeExecutorResult | null = null; const actionLabel = `${actionId} - ${actionTypeId} - ${description}`; + const loggedEvent: IEvent = { + event: { + action: EVENT_LOG_ACTIONS.execute, + original: JSON.stringify(validatedParams), + }, + kibana: { + space_id: namespace, + }, + service: { + id: actionId, + name: description, + type: actionTypeId, + }, + }; + + const timeStart = Date.now(); + let error: any; + try { result = await actionType.executor({ actionId, @@ -106,8 +128,19 @@ export class ActionExecutor { secrets: validatedSecrets, }); } catch (err) { + error = err; logger.warn(`action executed unsuccessfully: ${actionLabel} - ${err.message}`); - throw err; + loggedEvent.error = { message: err.message }; + } + + const duration = (Date.now() - timeStart) * 1000 * 1000; // nanoseconds + loggedEvent.event!.start = new Date(timeStart).toISOString(); + loggedEvent.event!.duration = duration; + + eventLogger.logEvent(loggedEvent); + + if (error != null) { + throw error; } logger.debug(`action executed successfully: ${actionLabel}`); diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts index cc18c7b16942984..24ff7904bfedcd2 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts @@ -16,6 +16,7 @@ import { SavedObjectsClientMock, loggingServiceMock, } from '../../../../../../src/core/server/mocks'; +import { createEventLoggerMock } from '../../../../../plugins/event_log/server/event_logger.mock'; const spaceIdToNamespace = jest.fn(); const actionTypeRegistry = actionTypeRegistryMock.create(); @@ -62,6 +63,7 @@ const actionExecutorInitializerParams = { actionTypeRegistry, spaces: () => undefined, encryptedSavedObjectsPlugin: mockedEncryptedSavedObjectsPlugin, + eventLogger: createEventLoggerMock(), }; const taskRunnerFactoryInitializerParams = { spaceIdToNamespace, diff --git a/x-pack/legacy/plugins/actions/server/plugin.ts b/x-pack/legacy/plugins/actions/server/plugin.ts index 618c1d120c37aed..be71278b00f423a 100644 --- a/x-pack/legacy/plugins/actions/server/plugin.ts +++ b/x-pack/legacy/plugins/actions/server/plugin.ts @@ -32,6 +32,13 @@ import { listActionTypesRoute, getExecuteActionRoute, } from './routes'; +import { IEventLogger } from '../../../../plugins/event_log/server/types'; + +const EVENT_LOG_PROVIDER = 'actions'; +export const EVENT_LOG_ACTIONS = { + execute: 'execute', + executeViaHttp: 'execute-via-http', +}; export interface PluginSetupContract { registerType: ActionTypeRegistry['register']; @@ -51,6 +58,7 @@ export class Plugin { private taskRunnerFactory?: TaskRunnerFactory; private actionTypeRegistry?: ActionTypeRegistry; private actionExecutor?: ActionExecutor; + private eventLogger?: IEventLogger; constructor(initializerContext: ActionsPluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'alerting'); @@ -102,6 +110,11 @@ export class Plugin { attributesToEncrypt: new Set(['apiKey']), }); + plugins.event_log.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS)); + this.eventLogger = plugins.event_log.getLogger({ + event: { provider: EVENT_LOG_PROVIDER }, + }); + const actionExecutor = new ActionExecutor(); const taskRunnerFactory = new TaskRunnerFactory(actionExecutor); const actionTypeRegistry = new ActionTypeRegistry({ @@ -165,6 +178,7 @@ export class Plugin { getServices, encryptedSavedObjectsPlugin: plugins.encrypted_saved_objects, actionTypeRegistry: actionTypeRegistry!, + eventLogger: this.eventLogger!, }); taskRunnerFactory!.initialize({ encryptedSavedObjectsPlugin: plugins.encrypted_saved_objects, diff --git a/x-pack/legacy/plugins/actions/server/shim.ts b/x-pack/legacy/plugins/actions/server/shim.ts index c457a40a78b6792..058832fb33c3c2a 100644 --- a/x-pack/legacy/plugins/actions/server/shim.ts +++ b/x-pack/legacy/plugins/actions/server/shim.ts @@ -19,6 +19,7 @@ import { LoggerFactory, SavedObjectsLegacyService, } from '../../../../../src/core/server'; +import { IEventLogService } from '../../../../plugins/event_log/server/types'; // Extend PluginProperties to indicate which plugins are guaranteed to exist // due to being marked as dependencies @@ -74,6 +75,7 @@ export interface ActionsPluginsSetup { task_manager: TaskManagerSetupContract; xpack_main: XPackMainPluginSetupContract; encrypted_saved_objects: EncryptedSavedObjectsSetupContract; + event_log: IEventLogService; } export interface ActionsPluginsStart { security?: SecurityPluginStartContract; @@ -127,6 +129,7 @@ export function shim( task_manager: server.plugins.task_manager, xpack_main: server.plugins.xpack_main, encrypted_saved_objects: server.plugins.encrypted_saved_objects, + event_log: newPlatform.setup.plugins.event_log as IEventLogService, }; const pluginsStart: ActionsPluginsStart = { diff --git a/x-pack/plugins/event_log/generated/README.md b/x-pack/plugins/event_log/generated/README.md new file mode 100644 index 000000000000000..0361cb12882ab73 --- /dev/null +++ b/x-pack/plugins/event_log/generated/README.md @@ -0,0 +1,4 @@ +The files in this directory were generated by manually running the script +../scripts/create-schemas.js from the root directory of the repository. + +These files should not be edited by hand. diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json new file mode 100644 index 000000000000000..9eca2292ea94085 --- /dev/null +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -0,0 +1,1876 @@ +{ + "dynamic": "strict", + "properties": { + "@timestamp": { + "type": "date" + }, + "agent": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "client": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "cloud": { + "properties": { + "account": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "availability_zone": { + "ignore_above": 1024, + "type": "keyword" + }, + "instance": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "machine": { + "properties": { + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "region": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "container": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "image": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "tag": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "runtime": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "destination": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "dns": { + "properties": { + "answers": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "data": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "ttl": { + "type": "long" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "header_flags": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "op_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "question": { + "properties": { + "class": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "subdomain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "resolved_ip": { + "type": "ip" + }, + "response_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ecs": { + "properties": { + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "error": { + "properties": { + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "message": { + "norms": false, + "type": "text" + }, + "stack_trace": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "event": { + "properties": { + "action": { + "ignore_above": 1024, + "type": "keyword" + }, + "category": { + "ignore_above": 1024, + "type": "keyword" + }, + "code": { + "ignore_above": 1024, + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "ignore_above": 1024, + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "kind": { + "ignore_above": 1024, + "type": "keyword" + }, + "module": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "outcome": { + "ignore_above": 1024, + "type": "keyword" + }, + "provider": { + "ignore_above": 1024, + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "file": { + "properties": { + "accessed": { + "type": "date" + }, + "created": { + "type": "date" + }, + "ctime": { + "type": "date" + }, + "device": { + "ignore_above": 1024, + "type": "keyword" + }, + "directory": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "gid": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "inode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mode": { + "ignore_above": 1024, + "type": "keyword" + }, + "mtime": { + "type": "date" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "owner": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "target_path": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uid": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "host": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "http": { + "properties": { + "request": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "method": { + "ignore_above": 1024, + "type": "keyword" + }, + "referrer": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "response": { + "properties": { + "body": { + "properties": { + "bytes": { + "type": "long" + }, + "content": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "bytes": { + "type": "long" + }, + "status_code": { + "type": "long" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "labels": { + "type": "object" + }, + "log": { + "properties": { + "level": { + "ignore_above": 1024, + "type": "keyword" + }, + "logger": { + "ignore_above": 1024, + "type": "keyword" + }, + "origin": { + "properties": { + "file": { + "properties": { + "line": { + "type": "integer" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "function": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "original": { + "doc_values": false, + "ignore_above": 1024, + "index": false, + "type": "keyword" + }, + "syslog": { + "properties": { + "facility": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "priority": { + "type": "long" + }, + "severity": { + "properties": { + "code": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + } + } + }, + "message": { + "norms": false, + "type": "text" + }, + "network": { + "properties": { + "application": { + "ignore_above": 1024, + "type": "keyword" + }, + "bytes": { + "type": "long" + }, + "community_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "direction": { + "ignore_above": 1024, + "type": "keyword" + }, + "forwarded_ip": { + "type": "ip" + }, + "iana_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "packets": { + "type": "long" + }, + "protocol": { + "ignore_above": 1024, + "type": "keyword" + }, + "transport": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "observer": { + "properties": { + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hostname": { + "ignore_above": 1024, + "type": "keyword" + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "product": { + "ignore_above": 1024, + "type": "keyword" + }, + "serial_number": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "vendor": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "organization": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "package": { + "properties": { + "architecture": { + "ignore_above": 1024, + "type": "keyword" + }, + "checksum": { + "ignore_above": 1024, + "type": "keyword" + }, + "description": { + "ignore_above": 1024, + "type": "keyword" + }, + "install_scope": { + "ignore_above": 1024, + "type": "keyword" + }, + "installed": { + "type": "date" + }, + "license": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "size": { + "type": "long" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "process": { + "properties": { + "args": { + "ignore_above": 1024, + "type": "keyword" + }, + "executable": { + "ignore_above": 1024, + "type": "keyword" + }, + "hash": { + "properties": { + "md5": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha1": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha256": { + "ignore_above": 1024, + "type": "keyword" + }, + "sha512": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "pgid": { + "type": "long" + }, + "pid": { + "type": "long" + }, + "ppid": { + "type": "long" + }, + "start": { + "type": "date" + }, + "thread": { + "properties": { + "id": { + "type": "long" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "title": { + "ignore_above": 1024, + "type": "keyword" + }, + "uptime": { + "type": "long" + }, + "working_directory": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "related": { + "properties": { + "ip": { + "type": "ip" + } + } + }, + "server": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "service": { + "properties": { + "ephemeral_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "node": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "state": { + "ignore_above": 1024, + "type": "keyword" + }, + "type": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "source": { + "properties": { + "address": { + "ignore_above": 1024, + "type": "keyword" + }, + "as": { + "properties": { + "number": { + "type": "long" + }, + "organization": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "bytes": { + "type": "long" + }, + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "geo": { + "properties": { + "city_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "continent_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "country_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_iso_code": { + "ignore_above": 1024, + "type": "keyword" + }, + "region_name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "ip": { + "type": "ip" + }, + "mac": { + "ignore_above": 1024, + "type": "keyword" + }, + "nat": { + "properties": { + "ip": { + "type": "ip" + }, + "port": { + "type": "long" + } + } + }, + "packets": { + "type": "long" + }, + "port": { + "type": "long" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "tags": { + "ignore_above": 1024, + "type": "keyword" + }, + "threat": { + "properties": { + "framework": { + "ignore_above": 1024, + "type": "keyword" + }, + "tactic": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "technique": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "reference": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } + }, + "trace": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "transaction": { + "properties": { + "id": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "url": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "extension": { + "ignore_above": 1024, + "type": "keyword" + }, + "fragment": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "password": { + "ignore_above": 1024, + "type": "keyword" + }, + "path": { + "ignore_above": 1024, + "type": "keyword" + }, + "port": { + "type": "long" + }, + "query": { + "ignore_above": 1024, + "type": "keyword" + }, + "registered_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "scheme": { + "ignore_above": 1024, + "type": "keyword" + }, + "top_level_domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "username": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "email": { + "ignore_above": 1024, + "type": "keyword" + }, + "full_name": { + "ignore_above": 1024, + "type": "keyword" + }, + "group": { + "properties": { + "domain": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "hash": { + "ignore_above": 1024, + "type": "keyword" + }, + "id": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "user_agent": { + "properties": { + "device": { + "properties": { + "name": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "original": { + "ignore_above": 1024, + "type": "keyword" + }, + "os": { + "properties": { + "family": { + "ignore_above": 1024, + "type": "keyword" + }, + "full": { + "ignore_above": 1024, + "type": "keyword" + }, + "kernel": { + "ignore_above": 1024, + "type": "keyword" + }, + "name": { + "ignore_above": 1024, + "type": "keyword" + }, + "platform": { + "ignore_above": 1024, + "type": "keyword" + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "version": { + "ignore_above": 1024, + "type": "keyword" + } + } + }, + "kibana": { + "properties": { + "username": { + "ignore_above": 1024, + "type": "keyword" + }, + "space_id": { + "ignore_above": 1024, + "type": "keyword" + }, + "uuid": { + "ignore_above": 1024, + "type": "keyword" + } + } + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts new file mode 100644 index 000000000000000..120c0d263477970 --- /dev/null +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -0,0 +1,1391 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// ---------------------------------- WARNING ---------------------------------- +// this file was generated, and should not be edited by hand +// ---------------------------------- WARNING ---------------------------------- + +// provides TypeScript and config-schema interfaces for ECS for use with +// the event log + +import { schema } from '@kbn/config-schema'; + +export const ECS_VERSION_GENERATED = '1.2.0'; + +// a typescript interface describing the schema +export interface IEventGenerated { + '@timestamp'?: string | string[]; + agent?: { + ephemeral_id?: string | string[]; + id?: string | string[]; + name?: string | string[]; + type?: string | string[]; + version?: string | string[]; + }; + as?: { + number?: number | number[]; + organization?: { + name?: string | string[]; + }; + }; + client?: { + address?: string | string[]; + as?: { + number?: number | number[]; + organization?: { + name?: string | string[]; + }; + }; + bytes?: number | number[]; + domain?: string | string[]; + geo?: { + city_name?: string | string[]; + continent_name?: string | string[]; + country_iso_code?: string | string[]; + country_name?: string | string[]; + location?: GeoPoint | GeoPoint[]; + name?: string | string[]; + region_iso_code?: string | string[]; + region_name?: string | string[]; + }; + ip?: string | string[]; + mac?: string | string[]; + nat?: { + ip?: string | string[]; + port?: number | number[]; + }; + packets?: number | number[]; + port?: number | number[]; + registered_domain?: string | string[]; + top_level_domain?: string | string[]; + user?: { + domain?: string | string[]; + email?: string | string[]; + full_name?: string | string[]; + group?: { + domain?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + hash?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + }; + cloud?: { + account?: { + id?: string | string[]; + }; + availability_zone?: string | string[]; + instance?: { + id?: string | string[]; + name?: string | string[]; + }; + machine?: { + type?: string | string[]; + }; + provider?: string | string[]; + region?: string | string[]; + }; + container?: { + id?: string | string[]; + image?: { + name?: string | string[]; + tag?: string | string[]; + }; + labels?: Record; + name?: string | string[]; + runtime?: string | string[]; + }; + destination?: { + address?: string | string[]; + as?: { + number?: number | number[]; + organization?: { + name?: string | string[]; + }; + }; + bytes?: number | number[]; + domain?: string | string[]; + geo?: { + city_name?: string | string[]; + continent_name?: string | string[]; + country_iso_code?: string | string[]; + country_name?: string | string[]; + location?: GeoPoint | GeoPoint[]; + name?: string | string[]; + region_iso_code?: string | string[]; + region_name?: string | string[]; + }; + ip?: string | string[]; + mac?: string | string[]; + nat?: { + ip?: string | string[]; + port?: number | number[]; + }; + packets?: number | number[]; + port?: number | number[]; + registered_domain?: string | string[]; + top_level_domain?: string | string[]; + user?: { + domain?: string | string[]; + email?: string | string[]; + full_name?: string | string[]; + group?: { + domain?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + hash?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + }; + dns?: { + answers?: { + class?: string | string[]; + data?: string | string[]; + name?: string | string[]; + ttl?: number | number[]; + type?: string | string[]; + }; + header_flags?: string | string[]; + id?: string | string[]; + op_code?: string | string[]; + question?: { + class?: string | string[]; + name?: string | string[]; + registered_domain?: string | string[]; + subdomain?: string | string[]; + top_level_domain?: string | string[]; + type?: string | string[]; + }; + resolved_ip?: string | string[]; + response_code?: string | string[]; + type?: string | string[]; + }; + ecs?: { + version?: string | string[]; + }; + error?: { + code?: string | string[]; + id?: string | string[]; + message?: string | string[]; + stack_trace?: string | string[]; + type?: string | string[]; + }; + event?: { + action?: string | string[]; + category?: string | string[]; + code?: string | string[]; + created?: string | string[]; + dataset?: string | string[]; + duration?: number | number[]; + end?: string | string[]; + hash?: string | string[]; + id?: string | string[]; + kind?: string | string[]; + module?: string | string[]; + original?: string | string[]; + outcome?: string | string[]; + provider?: string | string[]; + risk_score?: number | number[]; + risk_score_norm?: number | number[]; + sequence?: number | number[]; + severity?: number | number[]; + start?: string | string[]; + timezone?: string | string[]; + type?: string | string[]; + }; + file?: { + accessed?: string | string[]; + created?: string | string[]; + ctime?: string | string[]; + device?: string | string[]; + directory?: string | string[]; + extension?: string | string[]; + gid?: string | string[]; + group?: string | string[]; + hash?: { + md5?: string | string[]; + sha1?: string | string[]; + sha256?: string | string[]; + sha512?: string | string[]; + }; + inode?: string | string[]; + mode?: string | string[]; + mtime?: string | string[]; + name?: string | string[]; + owner?: string | string[]; + path?: string | string[]; + size?: number | number[]; + target_path?: string | string[]; + type?: string | string[]; + uid?: string | string[]; + }; + geo?: { + city_name?: string | string[]; + continent_name?: string | string[]; + country_iso_code?: string | string[]; + country_name?: string | string[]; + location?: GeoPoint | GeoPoint[]; + name?: string | string[]; + region_iso_code?: string | string[]; + region_name?: string | string[]; + }; + group?: { + domain?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + hash?: { + md5?: string | string[]; + sha1?: string | string[]; + sha256?: string | string[]; + sha512?: string | string[]; + }; + host?: { + architecture?: string | string[]; + geo?: { + city_name?: string | string[]; + continent_name?: string | string[]; + country_iso_code?: string | string[]; + country_name?: string | string[]; + location?: GeoPoint | GeoPoint[]; + name?: string | string[]; + region_iso_code?: string | string[]; + region_name?: string | string[]; + }; + hostname?: string | string[]; + id?: string | string[]; + ip?: string | string[]; + mac?: string | string[]; + name?: string | string[]; + os?: { + family?: string | string[]; + full?: string | string[]; + kernel?: string | string[]; + name?: string | string[]; + platform?: string | string[]; + version?: string | string[]; + }; + type?: string | string[]; + uptime?: number | number[]; + user?: { + domain?: string | string[]; + email?: string | string[]; + full_name?: string | string[]; + group?: { + domain?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + hash?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + }; + http?: { + request?: { + body?: { + bytes?: number | number[]; + content?: string | string[]; + }; + bytes?: number | number[]; + method?: string | string[]; + referrer?: string | string[]; + }; + response?: { + body?: { + bytes?: number | number[]; + content?: string | string[]; + }; + bytes?: number | number[]; + status_code?: number | number[]; + }; + version?: string | string[]; + }; + labels?: Record; + log?: { + level?: string | string[]; + logger?: string | string[]; + origin?: { + file?: { + line?: number | number[]; + name?: string | string[]; + }; + function?: string | string[]; + }; + original?: string | string[]; + syslog?: { + facility?: { + code?: number | number[]; + name?: string | string[]; + }; + priority?: number | number[]; + severity?: { + code?: number | number[]; + name?: string | string[]; + }; + }; + }; + message?: string | string[]; + network?: { + application?: string | string[]; + bytes?: number | number[]; + community_id?: string | string[]; + direction?: string | string[]; + forwarded_ip?: string | string[]; + iana_number?: string | string[]; + name?: string | string[]; + packets?: number | number[]; + protocol?: string | string[]; + transport?: string | string[]; + type?: string | string[]; + }; + observer?: { + geo?: { + city_name?: string | string[]; + continent_name?: string | string[]; + country_iso_code?: string | string[]; + country_name?: string | string[]; + location?: GeoPoint | GeoPoint[]; + name?: string | string[]; + region_iso_code?: string | string[]; + region_name?: string | string[]; + }; + hostname?: string | string[]; + ip?: string | string[]; + mac?: string | string[]; + name?: string | string[]; + os?: { + family?: string | string[]; + full?: string | string[]; + kernel?: string | string[]; + name?: string | string[]; + platform?: string | string[]; + version?: string | string[]; + }; + product?: string | string[]; + serial_number?: string | string[]; + type?: string | string[]; + vendor?: string | string[]; + version?: string | string[]; + }; + organization?: { + id?: string | string[]; + name?: string | string[]; + }; + os?: { + family?: string | string[]; + full?: string | string[]; + kernel?: string | string[]; + name?: string | string[]; + platform?: string | string[]; + version?: string | string[]; + }; + package?: { + architecture?: string | string[]; + checksum?: string | string[]; + description?: string | string[]; + install_scope?: string | string[]; + installed?: string | string[]; + license?: string | string[]; + name?: string | string[]; + path?: string | string[]; + size?: number | number[]; + version?: string | string[]; + }; + process?: { + args?: string | string[]; + executable?: string | string[]; + hash?: { + md5?: string | string[]; + sha1?: string | string[]; + sha256?: string | string[]; + sha512?: string | string[]; + }; + name?: string | string[]; + pgid?: number | number[]; + pid?: number | number[]; + ppid?: number | number[]; + start?: string | string[]; + thread?: { + id?: number | number[]; + name?: string | string[]; + }; + title?: string | string[]; + uptime?: number | number[]; + working_directory?: string | string[]; + }; + related?: { + ip?: string | string[]; + }; + server?: { + address?: string | string[]; + as?: { + number?: number | number[]; + organization?: { + name?: string | string[]; + }; + }; + bytes?: number | number[]; + domain?: string | string[]; + geo?: { + city_name?: string | string[]; + continent_name?: string | string[]; + country_iso_code?: string | string[]; + country_name?: string | string[]; + location?: GeoPoint | GeoPoint[]; + name?: string | string[]; + region_iso_code?: string | string[]; + region_name?: string | string[]; + }; + ip?: string | string[]; + mac?: string | string[]; + nat?: { + ip?: string | string[]; + port?: number | number[]; + }; + packets?: number | number[]; + port?: number | number[]; + registered_domain?: string | string[]; + top_level_domain?: string | string[]; + user?: { + domain?: string | string[]; + email?: string | string[]; + full_name?: string | string[]; + group?: { + domain?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + hash?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + }; + service?: { + ephemeral_id?: string | string[]; + id?: string | string[]; + name?: string | string[]; + node?: { + name?: string | string[]; + }; + state?: string | string[]; + type?: string | string[]; + version?: string | string[]; + }; + source?: { + address?: string | string[]; + as?: { + number?: number | number[]; + organization?: { + name?: string | string[]; + }; + }; + bytes?: number | number[]; + domain?: string | string[]; + geo?: { + city_name?: string | string[]; + continent_name?: string | string[]; + country_iso_code?: string | string[]; + country_name?: string | string[]; + location?: GeoPoint | GeoPoint[]; + name?: string | string[]; + region_iso_code?: string | string[]; + region_name?: string | string[]; + }; + ip?: string | string[]; + mac?: string | string[]; + nat?: { + ip?: string | string[]; + port?: number | number[]; + }; + packets?: number | number[]; + port?: number | number[]; + registered_domain?: string | string[]; + top_level_domain?: string | string[]; + user?: { + domain?: string | string[]; + email?: string | string[]; + full_name?: string | string[]; + group?: { + domain?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + hash?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + }; + tags?: string | string[]; + threat?: { + framework?: string | string[]; + tactic?: { + id?: string | string[]; + name?: string | string[]; + reference?: string | string[]; + }; + technique?: { + id?: string | string[]; + name?: string | string[]; + reference?: string | string[]; + }; + }; + trace?: { + id?: string | string[]; + }; + transaction?: { + id?: string | string[]; + }; + url?: { + domain?: string | string[]; + extension?: string | string[]; + fragment?: string | string[]; + full?: string | string[]; + original?: string | string[]; + password?: string | string[]; + path?: string | string[]; + port?: number | number[]; + query?: string | string[]; + registered_domain?: string | string[]; + scheme?: string | string[]; + top_level_domain?: string | string[]; + username?: string | string[]; + }; + user?: { + domain?: string | string[]; + email?: string | string[]; + full_name?: string | string[]; + group?: { + domain?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + hash?: string | string[]; + id?: string | string[]; + name?: string | string[]; + }; + user_agent?: { + device?: { + name?: string | string[]; + }; + name?: string | string[]; + original?: string | string[]; + os?: { + family?: string | string[]; + full?: string | string[]; + kernel?: string | string[]; + name?: string | string[]; + platform?: string | string[]; + version?: string | string[]; + }; + version?: string | string[]; + }; + kibana?: { + username?: string | string[]; + space_id?: string | string[]; + uuid?: string | string[]; + }; +} + +// a config-schema describing the schema +export const EventSchemaGenerated = schema.maybe( + schema.object({ + '@timestamp': ecsDate(), + agent: schema.maybe( + schema.object({ + ephemeral_id: ecsString(), + id: ecsString(), + name: ecsString(), + type: ecsString(), + version: ecsString(), + }) + ), + as: schema.maybe( + schema.object({ + number: ecsNumber(), + organization: schema.maybe( + schema.object({ + name: ecsString(), + }) + ), + }) + ), + client: schema.maybe( + schema.object({ + address: ecsString(), + as: schema.maybe( + schema.object({ + number: ecsNumber(), + organization: schema.maybe( + schema.object({ + name: ecsString(), + }) + ), + }) + ), + bytes: ecsNumber(), + domain: ecsString(), + geo: schema.maybe( + schema.object({ + city_name: ecsString(), + continent_name: ecsString(), + country_iso_code: ecsString(), + country_name: ecsString(), + location: ecsGeoPoint(), + name: ecsString(), + region_iso_code: ecsString(), + region_name: ecsString(), + }) + ), + ip: ecsString(), + mac: ecsString(), + nat: schema.maybe( + schema.object({ + ip: ecsString(), + port: ecsNumber(), + }) + ), + packets: ecsNumber(), + port: ecsNumber(), + registered_domain: ecsString(), + top_level_domain: ecsString(), + user: schema.maybe( + schema.object({ + domain: ecsString(), + email: ecsString(), + full_name: ecsString(), + group: schema.maybe( + schema.object({ + domain: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + hash: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + }) + ), + cloud: schema.maybe( + schema.object({ + account: schema.maybe( + schema.object({ + id: ecsString(), + }) + ), + availability_zone: ecsString(), + instance: schema.maybe( + schema.object({ + id: ecsString(), + name: ecsString(), + }) + ), + machine: schema.maybe( + schema.object({ + type: ecsString(), + }) + ), + provider: ecsString(), + region: ecsString(), + }) + ), + container: schema.maybe( + schema.object({ + id: ecsString(), + image: schema.maybe( + schema.object({ + name: ecsString(), + tag: ecsString(), + }) + ), + labels: ecsOpenObject(), + name: ecsString(), + runtime: ecsString(), + }) + ), + destination: schema.maybe( + schema.object({ + address: ecsString(), + as: schema.maybe( + schema.object({ + number: ecsNumber(), + organization: schema.maybe( + schema.object({ + name: ecsString(), + }) + ), + }) + ), + bytes: ecsNumber(), + domain: ecsString(), + geo: schema.maybe( + schema.object({ + city_name: ecsString(), + continent_name: ecsString(), + country_iso_code: ecsString(), + country_name: ecsString(), + location: ecsGeoPoint(), + name: ecsString(), + region_iso_code: ecsString(), + region_name: ecsString(), + }) + ), + ip: ecsString(), + mac: ecsString(), + nat: schema.maybe( + schema.object({ + ip: ecsString(), + port: ecsNumber(), + }) + ), + packets: ecsNumber(), + port: ecsNumber(), + registered_domain: ecsString(), + top_level_domain: ecsString(), + user: schema.maybe( + schema.object({ + domain: ecsString(), + email: ecsString(), + full_name: ecsString(), + group: schema.maybe( + schema.object({ + domain: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + hash: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + }) + ), + dns: schema.maybe( + schema.object({ + answers: schema.maybe( + schema.object({ + class: ecsString(), + data: ecsString(), + name: ecsString(), + ttl: ecsNumber(), + type: ecsString(), + }) + ), + header_flags: ecsString(), + id: ecsString(), + op_code: ecsString(), + question: schema.maybe( + schema.object({ + class: ecsString(), + name: ecsString(), + registered_domain: ecsString(), + subdomain: ecsString(), + top_level_domain: ecsString(), + type: ecsString(), + }) + ), + resolved_ip: ecsString(), + response_code: ecsString(), + type: ecsString(), + }) + ), + ecs: schema.maybe( + schema.object({ + version: ecsString(), + }) + ), + error: schema.maybe( + schema.object({ + code: ecsString(), + id: ecsString(), + message: ecsString(), + stack_trace: ecsString(), + type: ecsString(), + }) + ), + event: schema.maybe( + schema.object({ + action: ecsString(), + category: ecsString(), + code: ecsString(), + created: ecsDate(), + dataset: ecsString(), + duration: ecsNumber(), + end: ecsDate(), + hash: ecsString(), + id: ecsString(), + kind: ecsString(), + module: ecsString(), + original: ecsString(), + outcome: ecsString(), + provider: ecsString(), + risk_score: ecsNumber(), + risk_score_norm: ecsNumber(), + sequence: ecsNumber(), + severity: ecsNumber(), + start: ecsDate(), + timezone: ecsString(), + type: ecsString(), + }) + ), + file: schema.maybe( + schema.object({ + accessed: ecsDate(), + created: ecsDate(), + ctime: ecsDate(), + device: ecsString(), + directory: ecsString(), + extension: ecsString(), + gid: ecsString(), + group: ecsString(), + hash: schema.maybe( + schema.object({ + md5: ecsString(), + sha1: ecsString(), + sha256: ecsString(), + sha512: ecsString(), + }) + ), + inode: ecsString(), + mode: ecsString(), + mtime: ecsDate(), + name: ecsString(), + owner: ecsString(), + path: ecsString(), + size: ecsNumber(), + target_path: ecsString(), + type: ecsString(), + uid: ecsString(), + }) + ), + geo: schema.maybe( + schema.object({ + city_name: ecsString(), + continent_name: ecsString(), + country_iso_code: ecsString(), + country_name: ecsString(), + location: ecsGeoPoint(), + name: ecsString(), + region_iso_code: ecsString(), + region_name: ecsString(), + }) + ), + group: schema.maybe( + schema.object({ + domain: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + hash: schema.maybe( + schema.object({ + md5: ecsString(), + sha1: ecsString(), + sha256: ecsString(), + sha512: ecsString(), + }) + ), + host: schema.maybe( + schema.object({ + architecture: ecsString(), + geo: schema.maybe( + schema.object({ + city_name: ecsString(), + continent_name: ecsString(), + country_iso_code: ecsString(), + country_name: ecsString(), + location: ecsGeoPoint(), + name: ecsString(), + region_iso_code: ecsString(), + region_name: ecsString(), + }) + ), + hostname: ecsString(), + id: ecsString(), + ip: ecsString(), + mac: ecsString(), + name: ecsString(), + os: schema.maybe( + schema.object({ + family: ecsString(), + full: ecsString(), + kernel: ecsString(), + name: ecsString(), + platform: ecsString(), + version: ecsString(), + }) + ), + type: ecsString(), + uptime: ecsNumber(), + user: schema.maybe( + schema.object({ + domain: ecsString(), + email: ecsString(), + full_name: ecsString(), + group: schema.maybe( + schema.object({ + domain: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + hash: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + }) + ), + http: schema.maybe( + schema.object({ + request: schema.maybe( + schema.object({ + body: schema.maybe( + schema.object({ + bytes: ecsNumber(), + content: ecsString(), + }) + ), + bytes: ecsNumber(), + method: ecsString(), + referrer: ecsString(), + }) + ), + response: schema.maybe( + schema.object({ + body: schema.maybe( + schema.object({ + bytes: ecsNumber(), + content: ecsString(), + }) + ), + bytes: ecsNumber(), + status_code: ecsNumber(), + }) + ), + version: ecsString(), + }) + ), + labels: ecsOpenObject(), + log: schema.maybe( + schema.object({ + level: ecsString(), + logger: ecsString(), + origin: schema.maybe( + schema.object({ + file: schema.maybe( + schema.object({ + line: ecsNumber(), + name: ecsString(), + }) + ), + function: ecsString(), + }) + ), + original: ecsString(), + syslog: schema.maybe( + schema.object({ + facility: schema.maybe( + schema.object({ + code: ecsNumber(), + name: ecsString(), + }) + ), + priority: ecsNumber(), + severity: schema.maybe( + schema.object({ + code: ecsNumber(), + name: ecsString(), + }) + ), + }) + ), + }) + ), + message: ecsString(), + network: schema.maybe( + schema.object({ + application: ecsString(), + bytes: ecsNumber(), + community_id: ecsString(), + direction: ecsString(), + forwarded_ip: ecsString(), + iana_number: ecsString(), + name: ecsString(), + packets: ecsNumber(), + protocol: ecsString(), + transport: ecsString(), + type: ecsString(), + }) + ), + observer: schema.maybe( + schema.object({ + geo: schema.maybe( + schema.object({ + city_name: ecsString(), + continent_name: ecsString(), + country_iso_code: ecsString(), + country_name: ecsString(), + location: ecsGeoPoint(), + name: ecsString(), + region_iso_code: ecsString(), + region_name: ecsString(), + }) + ), + hostname: ecsString(), + ip: ecsString(), + mac: ecsString(), + name: ecsString(), + os: schema.maybe( + schema.object({ + family: ecsString(), + full: ecsString(), + kernel: ecsString(), + name: ecsString(), + platform: ecsString(), + version: ecsString(), + }) + ), + product: ecsString(), + serial_number: ecsString(), + type: ecsString(), + vendor: ecsString(), + version: ecsString(), + }) + ), + organization: schema.maybe( + schema.object({ + id: ecsString(), + name: ecsString(), + }) + ), + os: schema.maybe( + schema.object({ + family: ecsString(), + full: ecsString(), + kernel: ecsString(), + name: ecsString(), + platform: ecsString(), + version: ecsString(), + }) + ), + package: schema.maybe( + schema.object({ + architecture: ecsString(), + checksum: ecsString(), + description: ecsString(), + install_scope: ecsString(), + installed: ecsDate(), + license: ecsString(), + name: ecsString(), + path: ecsString(), + size: ecsNumber(), + version: ecsString(), + }) + ), + process: schema.maybe( + schema.object({ + args: ecsString(), + executable: ecsString(), + hash: schema.maybe( + schema.object({ + md5: ecsString(), + sha1: ecsString(), + sha256: ecsString(), + sha512: ecsString(), + }) + ), + name: ecsString(), + pgid: ecsNumber(), + pid: ecsNumber(), + ppid: ecsNumber(), + start: ecsDate(), + thread: schema.maybe( + schema.object({ + id: ecsNumber(), + name: ecsString(), + }) + ), + title: ecsString(), + uptime: ecsNumber(), + working_directory: ecsString(), + }) + ), + related: schema.maybe( + schema.object({ + ip: ecsString(), + }) + ), + server: schema.maybe( + schema.object({ + address: ecsString(), + as: schema.maybe( + schema.object({ + number: ecsNumber(), + organization: schema.maybe( + schema.object({ + name: ecsString(), + }) + ), + }) + ), + bytes: ecsNumber(), + domain: ecsString(), + geo: schema.maybe( + schema.object({ + city_name: ecsString(), + continent_name: ecsString(), + country_iso_code: ecsString(), + country_name: ecsString(), + location: ecsGeoPoint(), + name: ecsString(), + region_iso_code: ecsString(), + region_name: ecsString(), + }) + ), + ip: ecsString(), + mac: ecsString(), + nat: schema.maybe( + schema.object({ + ip: ecsString(), + port: ecsNumber(), + }) + ), + packets: ecsNumber(), + port: ecsNumber(), + registered_domain: ecsString(), + top_level_domain: ecsString(), + user: schema.maybe( + schema.object({ + domain: ecsString(), + email: ecsString(), + full_name: ecsString(), + group: schema.maybe( + schema.object({ + domain: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + hash: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + }) + ), + service: schema.maybe( + schema.object({ + ephemeral_id: ecsString(), + id: ecsString(), + name: ecsString(), + node: schema.maybe( + schema.object({ + name: ecsString(), + }) + ), + state: ecsString(), + type: ecsString(), + version: ecsString(), + }) + ), + source: schema.maybe( + schema.object({ + address: ecsString(), + as: schema.maybe( + schema.object({ + number: ecsNumber(), + organization: schema.maybe( + schema.object({ + name: ecsString(), + }) + ), + }) + ), + bytes: ecsNumber(), + domain: ecsString(), + geo: schema.maybe( + schema.object({ + city_name: ecsString(), + continent_name: ecsString(), + country_iso_code: ecsString(), + country_name: ecsString(), + location: ecsGeoPoint(), + name: ecsString(), + region_iso_code: ecsString(), + region_name: ecsString(), + }) + ), + ip: ecsString(), + mac: ecsString(), + nat: schema.maybe( + schema.object({ + ip: ecsString(), + port: ecsNumber(), + }) + ), + packets: ecsNumber(), + port: ecsNumber(), + registered_domain: ecsString(), + top_level_domain: ecsString(), + user: schema.maybe( + schema.object({ + domain: ecsString(), + email: ecsString(), + full_name: ecsString(), + group: schema.maybe( + schema.object({ + domain: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + hash: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + }) + ), + tags: ecsString(), + threat: schema.maybe( + schema.object({ + framework: ecsString(), + tactic: schema.maybe( + schema.object({ + id: ecsString(), + name: ecsString(), + reference: ecsString(), + }) + ), + technique: schema.maybe( + schema.object({ + id: ecsString(), + name: ecsString(), + reference: ecsString(), + }) + ), + }) + ), + trace: schema.maybe( + schema.object({ + id: ecsString(), + }) + ), + transaction: schema.maybe( + schema.object({ + id: ecsString(), + }) + ), + url: schema.maybe( + schema.object({ + domain: ecsString(), + extension: ecsString(), + fragment: ecsString(), + full: ecsString(), + original: ecsString(), + password: ecsString(), + path: ecsString(), + port: ecsNumber(), + query: ecsString(), + registered_domain: ecsString(), + scheme: ecsString(), + top_level_domain: ecsString(), + username: ecsString(), + }) + ), + user: schema.maybe( + schema.object({ + domain: ecsString(), + email: ecsString(), + full_name: ecsString(), + group: schema.maybe( + schema.object({ + domain: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + hash: ecsString(), + id: ecsString(), + name: ecsString(), + }) + ), + user_agent: schema.maybe( + schema.object({ + device: schema.maybe( + schema.object({ + name: ecsString(), + }) + ), + name: ecsString(), + original: ecsString(), + os: schema.maybe( + schema.object({ + family: ecsString(), + full: ecsString(), + kernel: ecsString(), + name: ecsString(), + platform: ecsString(), + version: ecsString(), + }) + ), + version: ecsString(), + }) + ), + kibana: schema.maybe( + schema.object({ + username: ecsString(), + space_id: ecsString(), + uuid: ecsString(), + }) + ), + }) +); + +interface GeoPoint { + lat?: number; + lon?: number; +} + +function ecsGeoPoint() { + return schema.maybe( + schema.object({ + lat: ecsNumber(), + lon: ecsNumber(), + }) + ); +} + +function ecsString() { + return schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])); +} + +function ecsNumber() { + return schema.maybe(schema.oneOf([schema.number(), schema.arrayOf(schema.number())])); +} + +function ecsOpenObject() { + return schema.maybe(schema.any()); +} + +function ecsDate() { + return schema.maybe(schema.string({ validate: validateDate })); +} + +const ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; + +function validateDate(isoDate: string) { + if (ISO_DATE_PATTERN.test(isoDate)) return; + return 'string is not a valid ISO date: ' + isoDate; +} diff --git a/x-pack/plugins/event_log/kibana.json b/x-pack/plugins/event_log/kibana.json new file mode 100644 index 000000000000000..510d7b3d4a22100 --- /dev/null +++ b/x-pack/plugins/event_log/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "event_log", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["x-pack", "event_log"], + "server": true, + "ui": false +} diff --git a/x-pack/plugins/event_log/scripts/create_schemas.js b/x-pack/plugins/event_log/scripts/create_schemas.js new file mode 100755 index 000000000000000..bafb66f8cad68ae --- /dev/null +++ b/x-pack/plugins/event_log/scripts/create_schemas.js @@ -0,0 +1,338 @@ +#!/usr/bin/env node + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const fs = require('fs'); +const path = require('path'); + +const LineWriter = require('./lib/line_writer'); + +const PLUGIN_DIR = path.resolve(path.join(__dirname, '..')); +const ECS_SCHEMA_FILE = 'generated/elasticsearch/7/template.json'; +const EVENT_LOG_MAPPINGS_FILE = 'generated/mappings.json'; +const EVENT_LOG_CONFIG_SCHEMA_FILE = 'generated/schemas.ts'; + +function main() { + const ecsDir = getEcsDir(); + const ecsVersion = getEcsVersion(ecsDir); + + const ecsSchema = readEcsJSONFile(ecsDir, ECS_SCHEMA_FILE); + + // add our custom fields + ecsSchema.mappings.properties.kibana = { + properties: { + username: { + ignore_above: 1024, + type: 'keyword', + }, + space_id: { + ignore_above: 1024, + type: 'keyword', + }, + uuid: { + ignore_above: 1024, + type: 'keyword', + } + } + }; + + const elSchema = getEventLogSchema(ecsSchema); + + console.log(`generating files in ${PLUGIN_DIR}`); + writeEventLogMappings(elSchema); + writeEventLogConfigSchema(elSchema, ecsVersion); +} + +function writeEventLogMappings(elSchema) { + // fixObjectTypes(elSchema.mappings); + + const mappings = { + dynamic: 'strict', + properties: elSchema.mappings.properties + }; + + writeGeneratedFile(EVENT_LOG_MAPPINGS_FILE, JSON.stringify(mappings, null, 4)); + console.log('generated:', EVENT_LOG_MAPPINGS_FILE); +} + +function writeEventLogConfigSchema(elSchema, ecsVersion) { + let lineWriter; + + lineWriter = LineWriter.createLineWriter(); + generateSchemaLines(lineWriter, null, elSchema.mappings); + // last line will have an extraneous comma + const schemaLines = lineWriter.getContent().replace(/,$/, ''); + + lineWriter = LineWriter.createLineWriter(); + generateInterfaceLines(lineWriter, null, elSchema.mappings); + const interfaceLines = lineWriter.getContent().replace(/;$/, ''); + + const contents = getSchemaFileContents(ecsVersion, schemaLines, interfaceLines); + const schemaCode = `${contents}\n`; + + writeGeneratedFile(EVENT_LOG_CONFIG_SCHEMA_FILE, schemaCode); + console.log('generated:', EVENT_LOG_CONFIG_SCHEMA_FILE); +} + +const StringTypes = new Set(['string', 'keyword', 'text', 'ip']); +const NumberTypes = new Set(['long', 'integer', 'float']); + +function generateInterfaceLines(lineWriter, prop, mappings) { + const propKey = legalPropertyName(prop); + + if (StringTypes.has(mappings.type)) { + lineWriter.addLine(`${propKey}?: string | string[];`); + return; + } + + if (NumberTypes.has(mappings.type)) { + lineWriter.addLine(`${propKey}?: number | number[];`); + return; + } + + if (mappings.type === 'date') { + lineWriter.addLine(`${propKey}?: string | string[];`); + return; + } + + if (mappings.type === 'geo_point') { + lineWriter.addLine(`${propKey}?: GeoPoint | GeoPoint[];`); + return; + } + + if (mappings.type === 'object') { + lineWriter.addLine(`${propKey}?: Record;`); + return; + } + + // only handling objects for the rest of this function + if (mappings.properties == null) { + logError(`unknown properties to map: ${prop}: ${JSON.stringify(mappings)}`); + } + + // top-level object does not have a property name + if (prop == null) { + lineWriter.addLine(`{`); + + } else { + lineWriter.addLine(`${propKey}?: {`); + } + + // write the object properties + lineWriter.indent(); + for (const prop of Object.keys(mappings.properties)) { + generateInterfaceLines(lineWriter, prop, mappings.properties[prop]); + } + lineWriter.dedent(); + + lineWriter.addLine('};'); +} + +function generateSchemaLines(lineWriter, prop, mappings) { + const propKey = legalPropertyName(prop); + + if (StringTypes.has(mappings.type)) { + lineWriter.addLine(`${propKey}: ecsString(),`); + return; + } + + if (NumberTypes.has(mappings.type)) { + lineWriter.addLine(`${propKey}: ecsNumber(),`); + return; + } + + if (mappings.type === 'date') { + lineWriter.addLine(`${propKey}: ecsDate(),`); + return; + } + + if (mappings.type === 'geo_point') { + lineWriter.addLine(`${propKey}: ecsGeoPoint(),`); + return; + } + + if (mappings.type === 'object') { + lineWriter.addLine(`${propKey}: ecsOpenObject(),`); + return; + } + + // only handling objects for the rest of this function + if (mappings.properties == null) { + logError(`unknown properties to map: ${prop}: ${JSON.stringify(mappings)}`); + } + + // top-level object does not have a property name + if (prop == null) { + lineWriter.addLine(`schema.maybe(`); + lineWriter.indent(); + lineWriter.addLine(`schema.object({`); + + } else { + lineWriter.addLine(`${propKey}: schema.maybe(`); + lineWriter.indent(); + lineWriter.addLine(`schema.object({`); + } + + // write the object properties + lineWriter.indent(); + for (const prop of Object.keys(mappings.properties)) { + generateSchemaLines(lineWriter, prop, mappings.properties[prop]); + } + lineWriter.dedent(); + + lineWriter.addLine('})'); + lineWriter.dedent(); + lineWriter.addLine('),'); +} + +function legalPropertyName(prop) { + if (prop === '@timestamp') return `'@timestamp'`; + return prop; +} + +function getEventLogSchema(ecsSchema) { + return ecsSchema; +} + +function readEcsJSONFile(ecsDir, fileName) { + const contents = readEcsFile(ecsDir, fileName); + + let object; + try { + object = JSON.parse(contents); + } catch (err) { + logError(`ecs file is not JSON: ${fileName}: ${err.message}`); + } + + return object; +} + +function writeGeneratedFile(fileName, contents) { + const genFileName = path.join(PLUGIN_DIR, fileName); + try { + fs.writeFileSync(genFileName, contents); + } catch (err) { + logError(`error writing file: ${genFileName}: ${err.message}`); + } +} + +function readEcsFile(ecsDir, fileName) { + const ecsFile = path.resolve(path.join(ecsDir, fileName)); + + let contents; + try { + contents = fs.readFileSync(ecsFile, { encoding: 'utf8' }); + } catch (err) { + logError(`ecs file not found: ${ecsFile}: ${err.message}`); + } + + return contents; +} + +function getEcsVersion(ecsDir) { + const contents = readEcsFile(ecsDir, 'version').trim(); + if (!contents.match(/^\d+\.\d+\.\d+$/)) { + logError(`ecs is not at a stable version: : ${contents}`); + } + + return contents; +} + +function getEcsDir() { + const ecsDir = path.resolve(path.join(__dirname, '../../../../../ecs')); + + let stats; + let error; + try { + stats = fs.statSync(ecsDir); + } catch (err) { + error = err; + } + + if (error || !stats.isDirectory()) { + logError(`directory not found: ${ecsDir} - did you checkout elastic/ecs as a peer of this repo?`); + } + + return ecsDir; +} + +function logError(message) { + console.log(`error: ${message}`); + process.exit(1); +} + +const SchemaFileTemplate = ` +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// ---------------------------------- WARNING ---------------------------------- +// this file was generated, and should not be edited by hand +// ---------------------------------- WARNING ---------------------------------- + +// provides TypeScript and config-schema interfaces for ECS for use with +// the event log + +import { schema } from '@kbn/config-schema'; + +export const ECS_VERSION_GENERATED = '%%ECS_VERSION%%'; + +// a typescript interface describing the schema +export interface IEventGenerated %%INTERFACE%% + +// a config-schema describing the schema +export const EventSchemaGenerated = %%SCHEMA%%; + +interface GeoPoint { + lat?: number; + lon?: number; +} + +function ecsGeoPoint() { + return schema.maybe( + schema.object({ + lat: ecsNumber(), + lon: ecsNumber(), + }) + ); +} + +function ecsString() { + return schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])); +} + +function ecsNumber() { + return schema.maybe(schema.oneOf([schema.number(), schema.arrayOf(schema.number())])); +} + +function ecsOpenObject() { + return schema.maybe(schema.any()); +} + +function ecsDate() { + return schema.maybe(schema.string({ validate: validateDate })); +} + +const ISO_DATE_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/; + +function validateDate(isoDate: string) { + if (ISO_DATE_PATTERN.test(isoDate)) return; + return 'string is not a valid ISO date: ' + isoDate; +} +`.trim(); + +function getSchemaFileContents(ecsVersion, schemaLines, interfaceLines) { + return SchemaFileTemplate + .replace('%%ECS_VERSION%%', ecsVersion) + .replace('%%SCHEMA%%', schemaLines) + .replace('%%INTERFACE%%', interfaceLines); +} + +// run as a command-line script +if (require.main === module) main(); diff --git a/x-pack/plugins/event_log/scripts/lib/line_writer.js b/x-pack/plugins/event_log/scripts/lib/line_writer.js new file mode 100644 index 000000000000000..2421e16a45d1ac6 --- /dev/null +++ b/x-pack/plugins/event_log/scripts/lib/line_writer.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +module.exports = { + createLineWriter, +}; + +class LineWriter { + constructor() { + this._indent = ''; + this._lines = []; + } + + addLine(line) { + this._lines.push(`${this._indent}${line}`); + } + + indent() { + this._indent = `${this._indent} `; + } + + dedent() { + this._indent = this._indent.substr(2); + } + + getContent() { + return this._lines.join('\n'); + } +} + +function createLineWriter() { + return new LineWriter(); +} diff --git a/x-pack/plugins/event_log/server/config_schema.ts b/x-pack/plugins/event_log/server/config_schema.ts new file mode 100644 index 000000000000000..cb4f0d73c86f14e --- /dev/null +++ b/x-pack/plugins/event_log/server/config_schema.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); diff --git a/x-pack/plugins/event_log/server/es/context.ts b/x-pack/plugins/event_log/server/es/context.ts new file mode 100644 index 000000000000000..11dac52567895b1 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/context.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { Logger, ClusterClient } from 'src/core/server'; + +import { EsNames, getEsNames } from './names'; +import { initializeEs } from './init'; +import { createReadySignal, ReadySignal } from '../lib/ready_signal'; + +export type EsClusterClient = Pick; + +export interface EsContext { + logger: Logger; + esNames: EsNames; + initialize(): void; + waitTillReady(): Promise; + callEs(operation: string, body?: any): Promise; +} + +export interface EsError { + readonly statusCode: number; + readonly message: string; +} + +export function createEsContext(params: EsContextCtorParams): EsContext { + return new EsContextImpl(params); +} + +type EsClusterClient$ = Observable; + +interface EsContextCtorParams { + logger: Logger; + clusterClient$: EsClusterClient$; + indexNameRoot: string; +} + +class EsContextImpl implements EsContext { + public readonly logger: Logger; + public readonly esNames: EsNames; + private readonly clusterClient$: EsClusterClient$; + private readonly readySignal: ReadySignal; + private initialized: boolean; + + constructor(params: EsContextCtorParams) { + this.logger = params.logger; + this.esNames = getEsNames(params.indexNameRoot); + this.clusterClient$ = params.clusterClient$; + this.readySignal = createReadySignal(); + this.initialized = false; + } + + initialize() { + // only run the initialization method once + if (this.initialized) return; + this.initialized = true; + + this.logger.debug('initializing EsContext'); + + setImmediate(async () => { + try { + await this._initialize(); + this.logger.info('readySignal.signal(true)'); + this.readySignal.signal(true); + } catch (err) { + this.logger.info('readySignal.signal(false)'); + this.readySignal.signal(false); + } + }); + } + + async waitTillReady(): Promise { + return await this.readySignal.wait(); + } + + async callEs(operation: string, body?: any): Promise { + const clusterClient = await this.clusterClient$.pipe(first()).toPromise(); + + try { + this.debug(`callEs(${operation}) calls:`, body); + const result = await clusterClient.callAsInternalUser(operation, body); + this.debug(`callEs(${operation}) result:`, result); + return result; + } catch (err) { + this.debug(`callEs(${operation}) error:`, { + message: err.message, + statusCode: err.statusCode, + }); + throw err; + } + } + + private async _initialize() { + await initializeEs(this); + } + + debug(message: string, object?: any) { + const objectString = object == null ? '' : JSON.stringify(object); + this.logger.info(`esContext: ${message} ${objectString}`); + } +} diff --git a/x-pack/plugins/event_log/server/es/documents.ts b/x-pack/plugins/event_log/server/es/documents.ts new file mode 100644 index 000000000000000..652f400cdebcb78 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/documents.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EsNames } from './names'; +import mappings from '../../generated/mappings.json'; + +export function getIndexTemplate(esNames: EsNames, ilmExists: boolean) { + const indexTemplateBody: any = { + index_patterns: [esNames.indexPattern], + aliases: { + [esNames.alias]: {}, + }, + settings: { + number_of_shards: 1, + number_of_replicas: 1, + 'index.lifecycle.name': esNames.ilmPolicy, + 'index.lifecycle.rollover_alias': esNames.alias, + }, + mappings, + }; + + if (!ilmExists) { + delete indexTemplateBody.settings['index.lifecycle.name']; + delete indexTemplateBody.settings['index.lifecycle.rollover_alias']; + } + + return indexTemplateBody; +} + +export function getIlmPolicy() { + return { + policy: { + phases: { + hot: { + actions: { + rollover: { + max_size: '5GB', + max_age: '30d', + // max_docs: 1, // you know, for testing + }, + }, + }, + }, + }, + }; +} diff --git a/x-pack/plugins/event_log/server/es/index.ts b/x-pack/plugins/event_log/server/es/index.ts new file mode 100644 index 000000000000000..a25d660239c3a21 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EsClusterClient, EsContext, createEsContext } from './context'; +export { EsClusterClient, EsContext, createEsContext }; diff --git a/x-pack/plugins/event_log/server/es/init.ts b/x-pack/plugins/event_log/server/es/init.ts new file mode 100644 index 000000000000000..faa57a8f728bbd6 --- /dev/null +++ b/x-pack/plugins/event_log/server/es/init.ts @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getIlmPolicy, getIndexTemplate } from './documents'; +import { EsContext } from './context'; + +export async function initializeEs(esContext: EsContext): Promise { + esContext.logger.info('initializing elasticsearch resources starting'); + + try { + await initializeEsResources(esContext); + } catch (err) { + esContext.logger.error(`error initializing elasticsearch resources: ${err.message}`); + return false; + } + + esContext.logger.info('initializing elasticsearch resources complete'); + return true; +} + +async function initializeEsResources(esContext: EsContext) { + const steps = new EsInitializationSteps(esContext); + let ilmExists: boolean; + + // create the ilm policy, if required + ilmExists = await steps.doesIlmPolicyExist(); + if (!ilmExists) { + ilmExists = await steps.createIlmPolicy(); + } + + if (!(await steps.doesIndexTemplateExist())) { + await steps.createIndexTemplate({ ilmExists }); + } + + if (!(await steps.doesInitialIndexExist())) { + await steps.createInitialIndex(); + } +} + +interface AddTemplateOpts { + ilmExists: boolean; +} + +class EsInitializationSteps { + private esContext: EsContext; + + constructor(esContext: EsContext) { + this.esContext = esContext; + } + + async doesIlmPolicyExist(): Promise { + const request = { + method: 'GET', + path: `_ilm/policy/${this.esContext.esNames.ilmPolicy}`, + }; + try { + await this.esContext.callEs('transport.request', request); + } catch (err) { + if (err.statusCode === 404) return false; + // TODO: remove following once kibana user can access ilm + if (err.statusCode === 403) return false; + + throw new Error(`error checking existance of ilm policy: ${err.message}`); + } + return true; + } + + async createIlmPolicy(): Promise { + const request = { + method: 'PUT', + path: `_ilm/policy/${this.esContext.esNames.ilmPolicy}`, + body: getIlmPolicy(), + }; + try { + await this.esContext.callEs('transport.request', request); + } catch (err) { + // TODO: remove following once kibana user can access ilm + if (err.statusCode === 403) return false; + throw new Error(`error creating ilm policy: ${err.message}`); + } + return true; + } + + async doesIndexTemplateExist(): Promise { + const name = this.esContext.esNames.indexTemplate; + let result; + try { + result = await this.esContext.callEs('indices.existsTemplate', { name }); + } catch (err) { + throw new Error(`error checking existance of index template: ${err.message}`); + } + return result as boolean; + } + + async createIndexTemplate(opts: AddTemplateOpts): Promise { + const templateBody = getIndexTemplate(this.esContext.esNames, opts.ilmExists); + const addTemplateParams = { + create: true, + name: this.esContext.esNames.indexTemplate, + body: templateBody, + }; + try { + await this.esContext.callEs('indices.putTemplate', addTemplateParams); + } catch (err) { + throw new Error(`error creating index template: ${err.message}`); + } + } + + async doesInitialIndexExist(): Promise { + const name = this.esContext.esNames.alias; + let result; + try { + result = await this.esContext.callEs('indices.existsAlias', { name }); + } catch (err) { + throw new Error(`error checking existance of initial index: ${err.message}`); + } + return result as boolean; + } + + async createInitialIndex(): Promise { + const index = this.esContext.esNames.initialIndex; + try { + await this.esContext.callEs('indices.create', { index }); + } catch (err) { + throw new Error(`error creating initial index: ${err.message}`); + } + } + + debug(message: string) { + this.esContext.logger.debug(message); + } + + warn(message: string) { + this.esContext.logger.warn(message); + } +} diff --git a/x-pack/plugins/event_log/server/es/names.ts b/x-pack/plugins/event_log/server/es/names.ts new file mode 100644 index 000000000000000..be737d23625f14e --- /dev/null +++ b/x-pack/plugins/event_log/server/es/names.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; + * you may not use this file except in compliance with the Elastic License. + */ + +const EVENT_LOG_NAME_SUFFIX = '-event-log'; + +export interface EsNames { + base: string; + alias: string; + ilmPolicy: string; + indexPattern: string; + initialIndex: string; + indexTemplate: string; +} + +export function getEsNames(baseName: string): EsNames { + const eventLogName = `${baseName}${EVENT_LOG_NAME_SUFFIX}`; + return { + base: baseName, + alias: eventLogName, + ilmPolicy: `${eventLogName}-policy`, + indexPattern: `${eventLogName}-*`, + initialIndex: `${eventLogName}-000001`, + indexTemplate: `${eventLogName}-template`, + }; +} diff --git a/x-pack/plugins/event_log/server/event_log_service.ts b/x-pack/plugins/event_log/server/event_log_service.ts new file mode 100644 index 000000000000000..ef5da3a6bcefb03 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_service.ts @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { Observable } from 'rxjs'; +import { ClusterClient } from 'src/core/server'; + +import { Plugin } from './plugin'; +import { EsContext } from './es'; +import { IEvent, IEventLogger, IEventLogService } from './types'; +import { EventLogger } from './event_logger'; +export type PluginClusterClient = Pick; +export type AdminClusterClient$ = Observable; + +type SystemLogger = Plugin['systemLogger']; + +interface EventLogServiceCtorParams { + enabled: boolean; + esContext: EsContext; + esBaseName: string; + systemLogger: SystemLogger; +} + +// note that clusterClient may be null, indicating we can't write to ES +export class EventLogService implements IEventLogService { + private systemLogger: SystemLogger; + private enabled: boolean; + private esContext: EsContext; + private registeredProviderActions: Map>; + + constructor({ enabled, esBaseName, systemLogger, esContext }: EventLogServiceCtorParams) { + this.enabled = enabled; + this.esContext = esContext; + this.systemLogger = systemLogger; + this.registeredProviderActions = new Map>(); + } + + isEnabled(): boolean { + return this.enabled; + } + + registerProviderActions(provider: string, actions: string[]): void { + if (this.registeredProviderActions.has(provider)) { + throw new Error(`provider already registered: "${provider}"`); + } + + this.registeredProviderActions.set(provider, new Set(actions)); + } + + isProviderActionRegistered(provider: string, action: string): boolean { + const actions = this.registeredProviderActions.get(provider); + if (actions == null) return false; + + if (actions.has(action)) return true; + + return false; + } + + getProviderActions() { + return new Map(this.registeredProviderActions.entries()); + } + + getLogger(initialProperties: Partial): IEventLogger { + return new EventLogger({ + esContext: this.esContext, + eventLogService: this, + initialProperties, + systemLogger: this.systemLogger, + }); + } +} diff --git a/x-pack/plugins/event_log/server/event_logger.mock.ts b/x-pack/plugins/event_log/server/event_logger.mock.ts new file mode 100644 index 000000000000000..be508ef5ba669e9 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_logger.mock.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEvent, IEventLogger } from './types'; + +export function createEventLoggerMock(): IEventLogger { + return { + logEvent(eventProperties: Partial): void {}, + }; +} diff --git a/x-pack/plugins/event_log/server/event_logger.ts b/x-pack/plugins/event_log/server/event_logger.ts new file mode 100644 index 000000000000000..29f6ae4c6e99a28 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_logger.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { merge } from 'lodash'; +import { IEvent, IEventLogger, IEventLogService, ECS_VERSION, EventSchema } from './types'; +import { Plugin } from './plugin'; +import { EsContext } from './es'; + +type SystemLogger = Plugin['systemLogger']; + +interface IEventLoggerCtorParams { + esContext: EsContext; + eventLogService: IEventLogService; + initialProperties: Partial; + systemLogger: SystemLogger; +} + +export function createNoopEventLogger(): IEventLogger { + return { + logEvent(eventProperties: Partial): void {}, + }; +} + +export class EventLogger implements IEventLogger { + private esContext: EsContext; + private eventLogService: IEventLogService; + private initialProperties: Partial; + private systemLogger: SystemLogger; + + constructor(ctorParams: IEventLoggerCtorParams) { + this.esContext = ctorParams.esContext; + this.eventLogService = ctorParams.eventLogService; + this.initialProperties = ctorParams.initialProperties; + this.systemLogger = ctorParams.systemLogger; + } + + // non-blocking, but spawns an async task to do the work + logEvent(eventProperties: Partial): void { + const event: Partial = {}; + + // merge the initial properties and event properties + merge(event, this.initialProperties, eventProperties); + + // add fixed properties + event['@timestamp'] = new Date().toISOString(); + if (event.ecs == null) event.ecs = {}; + event.ecs.version = ECS_VERSION; + + let validatedEvent: Partial; + try { + validatedEvent = validateEvent(this.eventLogService, event); + } catch (err) { + this.systemLogger.warn(`invalid event logged: ${err.message}`); + return; + } + + const doc = { + index: this.esContext.esNames.alias, + body: validatedEvent, + }; + + writeLogEvent(this.esContext, doc); + } +} + +const RequiredEventSchema = schema.object({ + provider: schema.string({ minLength: 1 }), + action: schema.string({ minLength: 1 }), +}); + +function validateEvent(eventLogService: IEventLogService, event: Partial): Partial { + if (event.event == null) { + throw new Error(`no "event" property`); + } + + // ensure there are provider/action properties in event as strings + const { provider, action } = event.event; + + // will throw an error if structure doesn't validate + RequiredEventSchema.validate({ provider, action }); + + const validatedProvider = provider as string; + const validatedAction = action as string; + + if (!eventLogService.isProviderActionRegistered(validatedProvider, validatedAction)) { + throw new Error(`unregistered provider/action: "${validatedProvider}" / "${validatedAction}"`); + } + + // could throw an error + EventSchema.validate(event); + + return event; +} + +function writeLogEvent(esContext: EsContext, doc: any): void { + // TODO: + // the setImmediate() on an async function is a little overkill, but, + // setImmediate() may be tweakable via node params, whereas async + // tweaking is in the v8 params realm, which is very dicey. + // Long-term, we should probably create an in-memory queue for this, so + // we can explictly see/set the queue lengths. + + // already verified this.clusterClient isn't null above + setImmediate(async () => { + try { + await writeLogEventDoc(esContext, doc); + } catch (err) { + esContext.logger.warn(`error writing event doc: ${err.message}`); + writeLogEventDocOnError(esContext, doc); + } + }); +} + +// whew, the thing that actually writes the event log document! +async function writeLogEventDoc(esContext: EsContext, doc: any) { + esContext.logger.info(`writing to event log: ${JSON.stringify(doc)}`); + await esContext.waitTillReady(); + await esContext.callEs('index', doc); + esContext.logger.info(`writing to event log complete`); +} + +// TODO: write log entry to a file +function writeLogEventDocOnError(esContext: EsContext, doc: any) { + esContext.logger.warn(`unable to write event doc: ${JSON.stringify(doc)}`); +} diff --git a/x-pack/plugins/event_log/server/index.ts b/x-pack/plugins/event_log/server/index.ts new file mode 100644 index 000000000000000..a46b0ad71fae117 --- /dev/null +++ b/x-pack/plugins/event_log/server/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/server'; +import { configSchema } from './config_schema'; +import { Plugin } from './plugin'; + +export * from './types'; +export const config = { schema: configSchema }; +export const plugin = (context: PluginInitializerContext) => new Plugin(context); diff --git a/x-pack/plugins/event_log/server/lib/ready_signal.ts b/x-pack/plugins/event_log/server/lib/ready_signal.ts new file mode 100644 index 000000000000000..2ea8e655089daa0 --- /dev/null +++ b/x-pack/plugins/event_log/server/lib/ready_signal.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; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ReadySignal { + wait(): Promise; + signal(value: T): void; +} + +export function createReadySignal(): ReadySignal { + let resolver: (value: T) => void; + + const promise = new Promise(resolve => { + resolver = resolve; + }); + + async function wait(): Promise { + return await promise; + } + + function signal(value: T) { + resolver(value); + } + + return { wait, signal }; +} diff --git a/x-pack/plugins/event_log/server/lib/retry.ts b/x-pack/plugins/event_log/server/lib/retry.ts new file mode 100644 index 000000000000000..6caaac45207912d --- /dev/null +++ b/x-pack/plugins/event_log/server/lib/retry.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface RetryOptions { + count: number; + delayMillis: number; +} + +// run a function a number of times, with delay in between, till it doesn't throw an error +export async function retry(options: RetryOptions, fn: () => any) { + let error: any; + + for (let attempt = 0; attempt < options.count; attempt++) { + let result: any; + let success: boolean = true; + + try { + result = await fn(); + } catch (err) { + error = err; + success = false; + } + + if (success) return result; + + await delay(options.delayMillis); + } + + throw error; +} + +async function delay(millis: number) { + return new Promise(resolve => { + setTimeout(resolve, millis); + }); +} diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts new file mode 100644 index 000000000000000..3e9c1fb852907ab --- /dev/null +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { + CoreSetup, + CoreStart, + Logger, + Plugin as CorePlugin, + PluginInitializerContext, + ClusterClient, +} from 'src/core/server'; + +import { EventLogConfigType, IEventLogService, IEventLogger } from './types'; +import { EventLogService } from './event_log_service'; +import { createEsContext, EsContext } from './es'; +import { addRoutes } from './routes'; + +export type PluginClusterClient = Pick; + +// TODO - figure out how to get ${kibana.index} for `.kibana` +const KIBANA_INDEX = '.kibana'; +const ES_BASE_NAME = `${KIBANA_INDEX}-event-log`; + +const PROVIDER = 'event_log'; +const ACTIONS = { + starting: 'starting', + stopping: 'stopping', +}; + +export class Plugin implements CorePlugin { + private readonly config$: Observable; + private systemLogger: Logger; + private esContext?: EsContext; + private eventLogger?: IEventLogger; + private enabled?: boolean; + + constructor(private readonly context: PluginInitializerContext) { + this.systemLogger = this.context.logger.get(); + this.config$ = this.context.config.create(); + } + + async setup(core: CoreSetup): Promise { + this.systemLogger.debug('setting up plugin'); + + const config = await this.config$.pipe(first()).toPromise(); + this.enabled = config.enabled; + + this.esContext = createEsContext({ + logger: this.systemLogger, + // TODO: get index prefix from config.get(kibana.index) + indexNameRoot: '.kibana', + clusterClient$: core.elasticsearch.adminClient$, + }); + + const eventLogService = new EventLogService({ + enabled: this.enabled, + esContext: this.esContext, + esBaseName: ES_BASE_NAME, + systemLogger: this.systemLogger, + }); + + addRoutes({ + router: core.http.createRouter(), + esContext: this.esContext, + eventLogService, + }); + + eventLogService.registerProviderActions(PROVIDER, Object.values(ACTIONS)); + + this.eventLogger = eventLogService.getLogger({ + event: { provider: PROVIDER }, + }); + + return eventLogService; + } + + async start(core: CoreStart) { + this.systemLogger.debug('starting plugin'); + + // launches initialization async + this.esContext!.initialize(); + + // will log the event after initialization + this.eventLogger!.logEvent({ + event: { action: ACTIONS.starting }, + }); + } + + stop() { + this.systemLogger.debug('stopping plugin'); + + // note that it's unlikely this event would ever be written, + // when Kibana is actuaelly stopping, as it's written asynchronously + this.eventLogger!.logEvent({ + event: { action: ACTIONS.stopping }, + }); + } +} diff --git a/x-pack/plugins/event_log/server/routes/index.ts b/x-pack/plugins/event_log/server/routes/index.ts new file mode 100644 index 000000000000000..79afad664fbaed7 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from '../../../../../src/core/server'; +import { EsContext } from '../es'; +import { IEventLogService } from '../types'; + +import { addSearchEndpoint } from './search'; +import { addProviderActionsEndpoint } from './provider_actions'; + +export interface DefineRoutesParams { + router: IRouter; + eventLogService: IEventLogService; + esContext: EsContext; +} + +export function addRoutes(params: DefineRoutesParams) { + addSearchEndpoint(params); + addProviderActionsEndpoint(params); +} diff --git a/x-pack/plugins/event_log/server/routes/provider_actions.ts b/x-pack/plugins/event_log/server/routes/provider_actions.ts new file mode 100644 index 000000000000000..3bbed2168885b7f --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/provider_actions.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DefineRoutesParams } from './index'; + +export function addProviderActionsEndpoint(params: DefineRoutesParams) { + const { router, eventLogService } = params; + + // TODO: add tags for access restrictions + router.get( + { path: '/api/event_log/provider_actions', validate: false }, + async (context, request, response) => { + const providerActionsMap = eventLogService.getProviderActions(); + const result: Record = {}; + for (const provider of providerActionsMap.keys()) { + result[provider] = Array.from(providerActionsMap.get(provider)!); + } + return response.ok({ body: result }); + } + ); +} diff --git a/x-pack/plugins/event_log/server/routes/search.ts b/x-pack/plugins/event_log/server/routes/search.ts new file mode 100644 index 000000000000000..4fd0d83b515fd12 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/search.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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DefineRoutesParams } from './index'; + +export function addSearchEndpoint(params: DefineRoutesParams) { + const { router, esContext } = params; + + // TODO: add tags for access restrictions + router.post( + { path: '/api/event_log/_search', validate: false }, + async (context, request, response) => { + // takes same args as the es `search` api, but we override the `index` param + const searchBody = Object.assign({}, request.body, { index: esContext.esNames.alias }); + await esContext.waitTillReady(); + const searchResponse = await esContext.callEs('search', searchBody); + return response.ok({ body: searchResponse }); + } + ); +} diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts new file mode 100644 index 000000000000000..9f21347517fbe99 --- /dev/null +++ b/x-pack/plugins/event_log/server/types.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TypeOf } from '@kbn/config-schema'; +import { configSchema } from './config_schema'; + +import { IEventGenerated, EventSchemaGenerated, ECS_VERSION_GENERATED } from '../generated/schemas'; + +export type IEvent = IEventGenerated; +export const ECS_VERSION = ECS_VERSION_GENERATED; +export const EventSchema = EventSchemaGenerated; +export type EventLogConfigType = TypeOf; + +// the object exposed by plugin.setup() +export interface IEventLogService { + isEnabled(): boolean; + registerProviderActions(provider: string, actions: string[]): void; + isProviderActionRegistered(provider: string, action: string): boolean; + getProviderActions(): Map>; + + getLogger(properties: Partial): IEventLogger; +} + +export interface IEventLogger { + logEvent(properties: Partial): void; +} + +/* +export interface IEventLogSearchOptions { + startDate?: Date; + endDate?: Date; + pluginId?: string; + spaceId?: string; + username?: string; + type?: string; + subType?: string; + tags: string[]; +} + +export interface IEventLogSearchResults { + page: number; + perPage: number; + total: number; + events: IEvent[]; +} +*/