From b27d70c6b5396c60911926103c80dcc5b938aee2 Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Sun, 24 May 2026 11:39:50 -0400 Subject: [PATCH 01/11] Integration scaffolding --- packages/backstage/LICENSE.txt | 93 +++++++ packages/backstage/_dev/build/docs/README.md | 116 ++++++++ packages/backstage/changelog.yml | 6 + .../agent/stream/filestream.yml.hbs | 44 +++ .../elasticsearch/ingest_pipeline/default.yml | 19 ++ .../audit_logs/fields/base-fields.yml | 12 + .../data_stream/audit_logs/manifest.yml | 256 ++++++++++++++++++ packages/backstage/docs/README.md | 116 ++++++++ packages/backstage/img/sample-logo.svg | 1 + packages/backstage/img/sample-screenshot.png | Bin 0 -> 18849 bytes packages/backstage/manifest.yml | 37 +++ packages/backstage/sample_event.json | 3 + 12 files changed, 703 insertions(+) create mode 100644 packages/backstage/LICENSE.txt create mode 100644 packages/backstage/_dev/build/docs/README.md create mode 100644 packages/backstage/changelog.yml create mode 100644 packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs create mode 100644 packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml create mode 100644 packages/backstage/data_stream/audit_logs/fields/base-fields.yml create mode 100644 packages/backstage/data_stream/audit_logs/manifest.yml create mode 100644 packages/backstage/docs/README.md create mode 100644 packages/backstage/img/sample-logo.svg create mode 100644 packages/backstage/img/sample-screenshot.png create mode 100644 packages/backstage/manifest.yml create mode 100644 packages/backstage/sample_event.json diff --git a/packages/backstage/LICENSE.txt b/packages/backstage/LICENSE.txt new file mode 100644 index 00000000000..809108b857f --- /dev/null +++ b/packages/backstage/LICENSE.txt @@ -0,0 +1,93 @@ +Elastic License 2.0 + +URL: https://www.elastic.co/licensing/elastic-license + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. + +## Copyright License + +The licensor grants you a non-exclusive, royalty-free, worldwide, +non-sublicensable, non-transferable license to use, copy, distribute, make +available, and prepare derivative works of the software, in each case subject to +the limitations and conditions below. + +## Limitations + +You may not provide the software to third parties as a hosted or managed +service, where the service provides users with access to any substantial set of +the features or functionality of the software. + +You may not move, change, disable, or circumvent the license key functionality +in the software, and you may not remove or obscure any functionality in the +software that is protected by the license key. + +You may not alter, remove, or obscure any licensing, copyright, or other notices +of the licensor in the software. Any use of the licensor’s trademarks is subject +to applicable law. + +## Patents + +The licensor grants you a license, under any patent claims the licensor can +license, or becomes able to license, to make, have made, use, sell, offer for +sale, import and have imported the software, in each case subject to the +limitations and conditions in this license. This license does not cover any +patent claims that you cause to be infringed by modifications or additions to +the software. If you or your company make any written claim that the software +infringes or contributes to infringement of any patent, your patent license for +the software granted under these terms ends immediately. If your company makes +such a claim, your patent license ends immediately for work on behalf of your +company. + +## Notices + +You must ensure that anyone who gets a copy of any part of the software from you +also gets a copy of these terms. + +If you modify the software, you must include in any modified copies of the +software prominent notices stating that you have modified the software. + +## No Other Rights + +These terms do not imply any licenses other than those expressly granted in +these terms. + +## Termination + +If you use the software in violation of these terms, such use is not licensed, +and your licenses will automatically terminate. If the licensor provides you +with a notice of your violation, and you cease all violation of this license no +later than 30 days after you receive that notice, your licenses will be +reinstated retroactively. However, if you violate these terms after such +reinstatement, any additional violation of these terms will cause your licenses +to terminate automatically and permanently. + +## No Liability + +*As far as the law allows, the software comes as is, without any warranty or +condition, and the licensor will not be liable to you for any damages arising +out of these terms or the use or nature of the software, under any kind of +legal claim.* + +## Definitions + +The **licensor** is the entity offering these terms, and the **software** is the +software the licensor makes available under these terms, including any portion +of it. + +**you** refers to the individual or entity agreeing to these terms. + +**your company** is any legal entity, sole proprietorship, or other kind of +organization that you work for, plus all organizations that have control over, +are under the control of, or are under common control with that +organization. **control** means ownership of substantially all the assets of an +entity, or the power to direct its management and policies by vote, contract, or +otherwise. Control can be direct or indirect. + +**your licenses** are all the licenses granted to you for the software under +these terms. + +**use** means anything you do with the software requiring one of your licenses. + +**trademark** means trademarks, service marks, and similar rights. diff --git a/packages/backstage/_dev/build/docs/README.md b/packages/backstage/_dev/build/docs/README.md new file mode 100644 index 00000000000..981f8d95587 --- /dev/null +++ b/packages/backstage/_dev/build/docs/README.md @@ -0,0 +1,116 @@ +{{- generatedHeader }} +{{/* +This template can be used as a starting point for writing documentation for your new integration. For each section, fill in the details +described in the comments. + +Find more detailed documentation guidelines in https://www.elastic.co/docs/extend/integrations/documentation-guidelines +*/}} +# Backstage Integration for Elastic + +## Overview +{{/* Complete this section with a short summary of what data this integration collects and what use cases it enables */}} +The Backstage integration for Elastic enables collection of ... +This integration facilitates ... + +### Compatibility +{{/* Complete this section with information on what 3rd party software or hardware versions this integration is compatible with */}} +This integration is compatible with ... + +### How it works +{{/* Add a high level overview on how this integration works. For example, does it collect data from API calls or recieving data from a network or file.*/}} + +## What data does this integration collect? +{{/* Complete this section with information on what types of data the integration collects, and link to reference documentation if available */}} +The Backstage integration collects log messages of the following types: +* ... + +### Supported use cases +{{/* Add details on the use cases that can be enabled by using this integration. Explain why a user would want to install and use this integration. */}} + +## What do I need to use this integration? +{{/* List any vendor-specific prerequisites needed before starting to install the integration. */}} + +## How do I deploy this integration? + +### Agent-based deployment + +Elastic Agent must be installed. For more details, check the Elastic Agent [installation instructions](docs-content://reference/fleet/install-elastic-agents.md). You can install only one Elastic Agent per host. + +Elastic Agent is required to stream data from the syslog or log file receiver and ship the data to Elastic, where the events will then be processed via the integration's ingest pipelines. + +{{/* If agentless is available for this integration, we'll want to include that here as well. +### Agentless deployment + +Agentless deployments are only supported in Elastic Serverless and Elastic Cloud environments. Agentless deployments provide a means to ingest data while avoiding the orchestration, management, and maintenance needs associated with standard ingest infrastructure. Using an agentless deployment makes manual agent deployment unnecessary, allowing you to focus on your data instead of the agent that collects it. + +For more information, refer to [Agentless integrations](https://www.elastic.co/guide/en/serverless/current/security-agentless-integrations.html) and [Agentless integrations FAQ](https://www.elastic.co/guide/en/serverless/current/agentless-integration-troubleshooting.html) +*/}} + +### Onboard / configure +{{/* List the steps that will need to be followed in order to completely set up a working integration. +For integrations that support multiple input types, be sure to add steps for all inputs. +*/}} + +### Validation +{{/* How can the user test whether the integration is working? Including example commands or test files if applicable */}} + +## Troubleshooting + +For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). +{{/* +Add any vendor specific troubleshooting here. + +Are there common issues or “gotchas” for deploying this integration? If so, how can they be resolved? +If applicable, links to the third-party software’s troubleshooting documentation. +*/}} + +## Scaling + +For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. +{{/* Add any vendor specific scaling information here */}} + +## Reference +{{/* Repeat for each data stream of the current type +### {Data stream name} + +The `{data stream name}` data stream provides events from {source} of the following types: {list types}. + +For each data_stream_name, include an optional summary of the datastream, the exported fields reference table and the sample event. + +The fields template function will be replaced by a generated list of all fields from the `fields/` directory of the data stream when building the integration. + +#### {data stream name} fields + +To include a generated list of fields from the `fields/` directory, uncomment and use: +{{ fields "data_stream_name" }} + +The event template function will be replace by a sample event, taken from `sample_event.json`, when building this integration. + +To include a sample event from `sample_event.json`, uncomment and use: +{{ event "data_stream_name" }} + +*/}} + +{{/* Export ILM Policies + This accepts a list of data stream names as arguments, and will export the ILM Policies + for each given data stream name. If no arguments are provided, all ILM Policies will be + exported. + + If there are no ILM Policies defined, this will be an empty string. +*/}} +{{ ilm }} + +{{/* Export Transforms + This will export the transforms used by this integration. + If there are no transforms defined, this will be an empty string. +*/}} +{{ transform }} + +### Inputs used +{{/* All inputs used by this package will be automatically listed here. */}} +{{ inputDocs }} + +### API usage +{{/* For integrations that use APIs to collect data, document all the APIs that are used, and link to relevent information */}} +These APIs are used with this integration: +* ... diff --git a/packages/backstage/changelog.yml b/packages/backstage/changelog.yml new file mode 100644 index 00000000000..bb0320a5243 --- /dev/null +++ b/packages/backstage/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "0.0.1" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/integrations/pull/1 # FIXME Replace with the real PR link diff --git a/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs b/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs new file mode 100644 index 00000000000..3bede63284a --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs @@ -0,0 +1,44 @@ +paths: +{{#each paths as |path|}} + - {{path}} +{{/each}} +{{#if exclude_files}} +prospector.scanner.exclude_files: +{{#each exclude_files as |pattern f|}} + - {{pattern}} +{{/each}} +{{/if}} +{{#if multiline_json}} +multiline.pattern: '^{' +multiline.negate: true +multiline.match: after +multiline.max_lines: 5000 +multiline.timeout: 10 +{{/if}} +{{#if custom}} +{{custom}} +{{/if}} + +{{#if tags.length}} +tags: +{{#each tags as |tag|}} +- {{tag}} +{{/each}} +{{#if preserve_original_event}} +- preserve_original_event +{{/if}} +{{else}} +{{#if preserve_original_event}} +tags: +- preserve_original_event +{{/if}} +{{/if}} + +{{#contains "forwarded" tags}} +publisher_pipeline.disable_host: true +{{/contains}} + +{{#if processors}} +processors: +{{processors}} +{{/if}} \ No newline at end of file diff --git a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml new file mode 100644 index 00000000000..ee87f90ea1a --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml @@ -0,0 +1,19 @@ +--- +description: Pipeline for processing sample logs +processors: +- set: + tag: set_sample_field + field: sample_field + value: "1" +on_failure: +- set: + field: error.message + value: >- + Processor '{{{ _ingest.on_failure_processor_type }}}' + with tag '{{{ _ingest.on_failure_processor_tag }}}' + in pipeline '{{{ _ingest.pipeline }}}' + failed with message '{{{ _ingest.on_failure_message }}}' +- set: + field: event.kind + tag: set_pipeline_error_to_event_kind + value: pipeline_error diff --git a/packages/backstage/data_stream/audit_logs/fields/base-fields.yml b/packages/backstage/data_stream/audit_logs/fields/base-fields.yml new file mode 100644 index 00000000000..7c798f4534c --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/fields/base-fields.yml @@ -0,0 +1,12 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: '@timestamp' + type: date + description: Event timestamp. diff --git a/packages/backstage/data_stream/audit_logs/manifest.yml b/packages/backstage/data_stream/audit_logs/manifest.yml new file mode 100644 index 00000000000..3c54751aee3 --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/manifest.yml @@ -0,0 +1,256 @@ +title: "Backstage audit logs" +type: logs +streams: + - input: filestream + title: "logs via filestream" + description: |- + Collect logs with filestream + template_path: filestream.yml.hbs + vars: + - name: paths + type: text + title: "Paths" + multi: true + required: true + show_user: true + default: + - /var/log/*.log + - name: data_stream.dataset + type: text + title: "Dataset name" + description: |- + Dataset to write data to. Changing the dataset will send the data to a different index. You can't use `-` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html). + required: true + show_user: true + default: filestream.generic + - name: pipeline + type: text + title: "Ingest Pipeline" + description: |- + The Ingest Node pipeline ID to be used by the integration. + show_user: true + - name: parsers + type: yaml + title: "Parsers" + description: |- + This option expects a list of parsers that the log line has to go through. For more information see [Parsers](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#_parsers) + show_user: true + default: | + #- ndjson: + # target: "" + # message_key: msg + #- multiline: + # type: count + # count_lines: 3 + - name: exclude_files + type: text + title: "Exclude Files" + description: |- + A list of regular expressions to match the files that you want Elastic Agent to ignore. By default no files are excluded. + multi: true + show_user: true + default: + - \.gz$ + - name: include_files + type: text + title: "Include Files" + description: |- + A list of regular expressions to match the files that you want Elastic Agent to include. If a list of regexes is provided, only the files that are allowed by the patterns are harvested. + multi: true + show_user: true + - name: processors + type: yaml + title: "Processors" + description: |- + Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details. + - name: tags + type: text + title: "Tags" + description: |- + Tags to include in the published event + multi: true + show_user: true + - name: encoding + type: text + title: "Encoding" + description: |- + The file encoding to use for reading data that contains international characters. For a full list of valid encodings, see the [Documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#_encoding_2) + - name: recursive_glob + type: bool + title: "Recursive Glob" + description: |- + Enable expanding `**` into recursive glob patterns. With this feature enabled, the rightmost `**` in each path is expanded into a fixed number of glob patterns. For example: `/foo/**` expands to `/foo`, `/foo/*`, `/foo/*/*`, and so on. If enabled it expands a single `**` into a 8-level deep `*` pattern. + This feature is enabled by default. Set prospector.scanner.recursive_glob to false to disable it. + default: true + - name: symlinks + type: bool + title: "Enable symlinks" + description: |- + The symlinks option allows Elastic Agent to harvest symlinks in addition to regular files. When harvesting symlinks, Elastic Agent opens and reads the original file even though it reports the path of the symlink. + **Because this option may lead to data loss, it is disabled by default.** + - name: resend_on_touch + type: bool + title: "Resend on touch" + description: |- + If this option is enabled a file is resent if its size has not changed but its modification time has changed to a later time than before. It is disabled by default to avoid accidentally resending files. + - name: check_interval + type: text + title: "Check Interval" + description: |- + How often Elastic Agent checks for new files in the paths that are specified for harvesting. For example Specify 1s to scan the directory as frequently as possible without causing Elastic Agent to scan too frequently. **We do not recommend to set this value <1s.** + - name: ignore_older + type: text + title: "Ignore Older" + description: |- + If this option is enabled, Elastic Agent ignores any files that were modified before the specified timespan. You can use time strings like 2h (2 hours) and 5m (5 minutes). The default is 0, which disables the setting. + You must set Ignore Older to be greater than On State Change Inactive. + For more information, please see the [Documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#filebeat-input-filestream-ignore-older) + - name: ignore_inactive + type: text + title: "Ignore Inactive" + description: |- + If this option is enabled, Elastic Agent ignores every file that has not been updated since the selected time. Possible options are since_first_start and since_last_start. + - name: close_on_state_changed_inactive + type: text + title: "Close on State Changed Inactive" + description: |- + When this option is enabled, Elastic Agent closes the file handle if a file has not been harvested for the specified duration. The counter for the defined period starts when the last log line was read by the harvester. It is not based on the modification time of the file. If the closed file changes again, a new harvester is started and the latest changes will be picked up after Check Interval has elapsed. + - name: close_on_state_changed_renamed + type: bool + title: "Close on State Changed Renamed" + description: |- + **Only use this option if you understand that data loss is a potential side effect.** + When this option is enabled, Elastic Agent closes the file handler when a file is renamed. This happens, for example, when rotating files. By default, the harvester stays open and keeps reading the file because the file handler does not depend on the file name. + - name: close_on_state_changed_removed + type: bool + title: "Close on State Changed Removed" + description: |- + When this option is enabled, Elastic Agent closes the harvester when a file is removed. Normally a file should only be removed after it’s inactive for the duration specified by close.on_state_change.inactive. + - name: close_reader_eof + type: bool + title: "Close Reader EOF" + description: |- + **Only use this option if you understand that data loss is a potential side effect.** + When this option is enabled, Elastic Agent closes a file as soon as the end of a file is reached. This is useful when your files are only written once and not updated from time to time. For example, this happens when you are writing every single log event to a new file. This option is disabled by default. + - name: close_reader_after_interval + type: text + title: "Close Reader After Interval" + description: |- + **Only use this option if you understand that data loss is a potential side effect. Another side effect is that multiline events might not be completely sent before the timeout expires.** + This option is particularly useful in case the output is blocked, which makes Elastic Agent keep open file handlers even for files that were deleted from the disk. + For more information see the [documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#filebeat-input-filestream-close-timeout). + - name: clean_inactive + type: text + title: "Clean Inactive" + description: |- + **Only use this option if you understand that data loss is a potential side effect.** + When this option is enabled, Elastic Agent removes the state of a file after the specified period of inactivity has elapsed. + E.g: "30m", Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". By default cleaning inactive states is disabled, -1 is used to disable it. + default: -1 + - name: clean_removed + type: bool + title: "Clean Removed" + description: |- + When this option is enabled, Elastic Agent cleans files from the registry if they cannot be found on disk anymore under the last known name. + **You must disable this option if you also disable Close Removed.** + - name: harvester_limit + type: integer + title: "Harvester Limit" + description: |- + The harvester_limit option limits the number of harvesters + that are started in parallel for one input. This directly + relates to the maximum number of file handlers that are + opened. The default is 0 (no limit). + default: 0 + - name: backoff_init + type: text + title: "Backoff Init" + description: |- + The backoff option defines how long Elastic Agent waits before checking a file again after EOF is reached. The default is 1s. + - name: backoff_max + type: text + title: "Backoff Max" + description: |- + The maximum time for Elastic Agent to wait before checking a file again after EOF is reached. The default is 10s. + **Requirement: Set Backoff Max to be greater than or equal to Backoff Init and less than or equal to Check Interval (Backoff Init <= Backoff Max <= Check Interval).** + - name: fingerprint + type: bool + title: "File identity: Fingerprint" + description: |- + **Changing file_identity methods between runs may result in + duplicated events in the output.** + Uses a fingerprint generated from the first few bytes (1k is + the default, this can be configured via Fingerprint offset + and length) to identify a file instead inode + device ID. + Refer to https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#_file_identity_2 + for more details. If this option is disabled (and 'Native + file identity is not enabled'), Elastic-Agent < 9.0.0 will + use Native as the file identity, and >= 9.0.0 will use + Fingerprint with the default offset and length. + default: true + - name: fingerprint_offset + type: integer + title: "File identity: Fingerprint offset" + description: |- + Offset from the beginning of the file to start calculating + the fingerprint. The default is 0. Only used when the + fingerprint file identity is selected + default: 0 + - name: fingerprint_length + type: integer + title: "File identity: Fingerprint length" + description: |- + The number of bytes used to calculate the fingerprint. The + default is 1024. Only used when the fingerprint file + identity is selected. + default: 1024 + - name: file_identity_native + type: bool + title: "File identity: Native" + description: |- + **Changing file_identity methods between runs may result in + duplicated events in the output.** + Uses a native identifier for files, on most Unix-like + file systems this is the inode + device ID. On file systems + that do not support inode, the native equivalent is used. + If you enable this option you **MUST disable Fingerprint + file identity**. Refer to + https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-filestream + for more details. + default: false + - name: rotation_external_strategy_copytruncate + type: yaml + title: "Rotation Strategy" + description: "If the log rotating application copies the contents of the active file and then truncates the original file, use these options to help Elastic Agent to read files correctly.\nSet the option suffix_regex so Elastic Agent can tell active and rotated files apart. \nThere are two supported suffix types in the input: numberic and date." + - name: exclude_lines + type: text + title: "Exclude Lines" + description: |- + A list of regular expressions to match the lines that you want Elastic Agent to exclude. Elastic Agent drops any lines that match a regular expression in the list. By default, no lines are dropped. Empty lines are ignored. + multi: true + - name: include_lines + type: text + title: "Include Lines" + description: |- + A list of regular expressions to match the lines that you want Elastic Agent to include. Elastic Agent exports only the lines that match a regular expression in the list. By default, all lines are exported. Empty lines are ignored. + multi: true + - name: buffer_size + type: text + title: "Buffer Size" + description: |- + The size in bytes of the buffer that each harvester uses when fetching a file. The default is 16384. + - name: message_max_bytes + type: text + title: "Message Max Bytes" + description: |- + The maximum number of bytes that a single log message can have. All bytes after mesage_max_bytes are discarded and not sent. The default is 10MB (10485760). + - name: condition + type: text + title: "Condition" + description: |- + Condition to filter when to collect this input. See [Dynamic Input Configuration](https://www.elastic.co/guide/en/fleet/current/dynamic-input-configuration.html) for details. + show_user: true +elasticsearch: + index_template: + mappings: + subobjects: false diff --git a/packages/backstage/docs/README.md b/packages/backstage/docs/README.md new file mode 100644 index 00000000000..981f8d95587 --- /dev/null +++ b/packages/backstage/docs/README.md @@ -0,0 +1,116 @@ +{{- generatedHeader }} +{{/* +This template can be used as a starting point for writing documentation for your new integration. For each section, fill in the details +described in the comments. + +Find more detailed documentation guidelines in https://www.elastic.co/docs/extend/integrations/documentation-guidelines +*/}} +# Backstage Integration for Elastic + +## Overview +{{/* Complete this section with a short summary of what data this integration collects and what use cases it enables */}} +The Backstage integration for Elastic enables collection of ... +This integration facilitates ... + +### Compatibility +{{/* Complete this section with information on what 3rd party software or hardware versions this integration is compatible with */}} +This integration is compatible with ... + +### How it works +{{/* Add a high level overview on how this integration works. For example, does it collect data from API calls or recieving data from a network or file.*/}} + +## What data does this integration collect? +{{/* Complete this section with information on what types of data the integration collects, and link to reference documentation if available */}} +The Backstage integration collects log messages of the following types: +* ... + +### Supported use cases +{{/* Add details on the use cases that can be enabled by using this integration. Explain why a user would want to install and use this integration. */}} + +## What do I need to use this integration? +{{/* List any vendor-specific prerequisites needed before starting to install the integration. */}} + +## How do I deploy this integration? + +### Agent-based deployment + +Elastic Agent must be installed. For more details, check the Elastic Agent [installation instructions](docs-content://reference/fleet/install-elastic-agents.md). You can install only one Elastic Agent per host. + +Elastic Agent is required to stream data from the syslog or log file receiver and ship the data to Elastic, where the events will then be processed via the integration's ingest pipelines. + +{{/* If agentless is available for this integration, we'll want to include that here as well. +### Agentless deployment + +Agentless deployments are only supported in Elastic Serverless and Elastic Cloud environments. Agentless deployments provide a means to ingest data while avoiding the orchestration, management, and maintenance needs associated with standard ingest infrastructure. Using an agentless deployment makes manual agent deployment unnecessary, allowing you to focus on your data instead of the agent that collects it. + +For more information, refer to [Agentless integrations](https://www.elastic.co/guide/en/serverless/current/security-agentless-integrations.html) and [Agentless integrations FAQ](https://www.elastic.co/guide/en/serverless/current/agentless-integration-troubleshooting.html) +*/}} + +### Onboard / configure +{{/* List the steps that will need to be followed in order to completely set up a working integration. +For integrations that support multiple input types, be sure to add steps for all inputs. +*/}} + +### Validation +{{/* How can the user test whether the integration is working? Including example commands or test files if applicable */}} + +## Troubleshooting + +For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). +{{/* +Add any vendor specific troubleshooting here. + +Are there common issues or “gotchas” for deploying this integration? If so, how can they be resolved? +If applicable, links to the third-party software’s troubleshooting documentation. +*/}} + +## Scaling + +For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. +{{/* Add any vendor specific scaling information here */}} + +## Reference +{{/* Repeat for each data stream of the current type +### {Data stream name} + +The `{data stream name}` data stream provides events from {source} of the following types: {list types}. + +For each data_stream_name, include an optional summary of the datastream, the exported fields reference table and the sample event. + +The fields template function will be replaced by a generated list of all fields from the `fields/` directory of the data stream when building the integration. + +#### {data stream name} fields + +To include a generated list of fields from the `fields/` directory, uncomment and use: +{{ fields "data_stream_name" }} + +The event template function will be replace by a sample event, taken from `sample_event.json`, when building this integration. + +To include a sample event from `sample_event.json`, uncomment and use: +{{ event "data_stream_name" }} + +*/}} + +{{/* Export ILM Policies + This accepts a list of data stream names as arguments, and will export the ILM Policies + for each given data stream name. If no arguments are provided, all ILM Policies will be + exported. + + If there are no ILM Policies defined, this will be an empty string. +*/}} +{{ ilm }} + +{{/* Export Transforms + This will export the transforms used by this integration. + If there are no transforms defined, this will be an empty string. +*/}} +{{ transform }} + +### Inputs used +{{/* All inputs used by this package will be automatically listed here. */}} +{{ inputDocs }} + +### API usage +{{/* For integrations that use APIs to collect data, document all the APIs that are used, and link to relevent information */}} +These APIs are used with this integration: +* ... diff --git a/packages/backstage/img/sample-logo.svg b/packages/backstage/img/sample-logo.svg new file mode 100644 index 00000000000..6268dd88f3b --- /dev/null +++ b/packages/backstage/img/sample-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/backstage/img/sample-screenshot.png b/packages/backstage/img/sample-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..d7a56a3ecc078c38636698cefba33f86291dd178 GIT binary patch literal 18849 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

}qal5$moSACwfNXLXG5|3R0AtBcN` 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 literal 0 HcmV?d00001 diff --git a/packages/backstage/manifest.yml b/packages/backstage/manifest.yml new file mode 100644 index 00000000000..63364c40df1 --- /dev/null +++ b/packages/backstage/manifest.yml @@ -0,0 +1,37 @@ +format_version: 3.6.2 +name: backstage +title: "Backstage" +version: 0.0.1 +source: + license: "Elastic-2.0" +description: "Fetches audit logs from spotify backstage for security analysis" +type: integration +categories: + - custom + - security +conditions: + kibana: + version: "^9.3.3" + elastic: + subscription: "basic" +screenshots: + - src: /img/sample-screenshot.png + title: Sample screenshot + size: 600x600 + type: image/png +icons: + - src: /img/sample-logo.svg + title: Sample logo + size: 32x32 + type: image/svg+xml +policy_templates: + - name: sample + title: Sample logs + description: Collect sample logs + inputs: + - type: logfile + title: Collect sample logs from instances + description: Collecting sample logs +owner: + github: elastic/integrations + type: elastic diff --git a/packages/backstage/sample_event.json b/packages/backstage/sample_event.json new file mode 100644 index 00000000000..bdf90044196 --- /dev/null +++ b/packages/backstage/sample_event.json @@ -0,0 +1,3 @@ +{ + "description": "This is an example sample-event for Backstage. Replace it with a real sample event. Hint: If system tests exist, running `elastic-package test system --generate` will generate this file." +} From 332cae8b3a496bee7afc2ac948724c3951a4888d Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Wed, 3 Jun 2026 11:48:36 -0400 Subject: [PATCH 02/11] parse user agent --- .../backstage/_dev/build/docs/test/pipeline/test-auditor.log | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/backstage/_dev/build/docs/test/pipeline/test-auditor.log diff --git a/packages/backstage/_dev/build/docs/test/pipeline/test-auditor.log b/packages/backstage/_dev/build/docs/test/pipeline/test-auditor.log new file mode 100644 index 00000000000..fbe9a3c8980 --- /dev/null +++ b/packages/backstage/_dev/build/docs/test/pipeline/test-auditor.log @@ -0,0 +1 @@ +{"actor":{"hostname":"localhost","ip":"127.0.0.1","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"fields":["metadata","kind","spec.profile"],"filter":["kind=group,relations.hasMember=user:default/guest"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest"},"service":"backstage","severityLevel":"low","status":"initiated","timestamp":"2026-05-22T20:30:05.131Z"} From ff7ffcf7fa52bb230e9865c3d576d1b153a5ed9f Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Wed, 3 Jun 2026 11:49:03 -0400 Subject: [PATCH 03/11] parse user agent --- .../elasticsearch/ingest_pipeline/default.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml index ee87f90ea1a..eb8c669f349 100644 --- a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml +++ b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml @@ -1,10 +1,12 @@ --- -description: Pipeline for processing sample logs +description: Pipeline for backstage audit logs processors: - set: - tag: set_sample_field - field: sample_field - value: "1" + field: ecs.version + value: '9.4.0' +- user_agent: + tag: extract_user_agent + ignore_missing: true on_failure: - set: field: error.message From e12890657eaccdcd7c3e752f7024f1e8961a27b3 Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Fri, 5 Jun 2026 00:10:16 -0400 Subject: [PATCH 04/11] Initial draft of ingest pipeline for backstage audit logs, based on one catalog-entity fetch event --- packages/backstage/_dev/build/build.yml | 3 + .../_dev}/test/pipeline/test-auditor.log | 0 .../pipeline/test-auditor.log-expected.json | 72 ++++++++ .../elasticsearch/ingest_pipeline/default.yml | 170 +++++++++++++++++- .../audit_logs/fields/backstage.yml | 12 ++ packages/backstage/docs/README.md | 86 +++------ 6 files changed, 285 insertions(+), 58 deletions(-) create mode 100644 packages/backstage/_dev/build/build.yml rename packages/backstage/{_dev/build/docs => data_stream/audit_logs/_dev}/test/pipeline/test-auditor.log (100%) create mode 100644 packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log-expected.json create mode 100644 packages/backstage/data_stream/audit_logs/fields/backstage.yml diff --git a/packages/backstage/_dev/build/build.yml b/packages/backstage/_dev/build/build.yml new file mode 100644 index 00000000000..ac76b327e47 --- /dev/null +++ b/packages/backstage/_dev/build/build.yml @@ -0,0 +1,3 @@ +dependencies: + ecs: + reference: "git@v9.4.0" diff --git a/packages/backstage/_dev/build/docs/test/pipeline/test-auditor.log b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log similarity index 100% rename from packages/backstage/_dev/build/docs/test/pipeline/test-auditor.log rename to packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log-expected.json b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log-expected.json new file mode 100644 index 00000000000..3e843f42bfc --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log-expected.json @@ -0,0 +1,72 @@ +{ + "expected": [ + { + "@timestamp": "2026-05-22T20:30:05.131Z", + "backstage": { + "audit": { + "severity_level": "low" + } + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "catalog.entity-fetch:initiated", + "code": "entity-fetch", + "module": "catalog", + "original": "{\"actor\":{\"hostname\":\"localhost\",\"ip\":\"127.0.0.1\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch\",\"meta\":{\"query\":{\"fields\":[\"metadata\",\"kind\",\"spec.profile\"],\"filter\":[\"kind=group,relations.hasMember=user:default/guest\"]},\"queryType\":\"all\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\",\"timestamp\":\"2026-05-22T20:30:05.131Z\"}", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": { + "meta": { + "query": { + "fields": [ + "metadata", + "kind", + "spec.profile" + ], + "filter": [ + "kind=group,relations.hasMember=user:default/guest" + ] + }, + "queryType": "all" + } + }, + "log": { + "level": "info" + }, + "message": "catalog.entity-fetch", + "related": { + "hosts": [ + "localhost" + ], + "ip": [ + "127.0.0.1" + ] + }, + "url": { + "original": "/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", + "path": "/api/catalog/entities", + "query": "fields=metadata,kind,spec.profile&filter=kind=group,relations.hasMember=user:default/guest" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Chrome", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36", + "os": { + "full": "Mac OS X 10.15.7", + "name": "Mac OS X", + "version": "10.15.7" + }, + "version": "148.0.0.0" + } + } + ] +} diff --git a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml index eb8c669f349..fb5100e4606 100644 --- a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml +++ b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml @@ -2,11 +2,179 @@ description: Pipeline for backstage audit logs processors: - set: + tag: set_ecs_version field: ecs.version value: '9.4.0' +- rename: + field: message + tag: rename_message_to_event_original + target_field: event.original + ignore_missing: true + description: >- + Renames the original `message` field to `event.original` to store a copy of the original message. + The `event.original` field is not touched if the document already has one. + if: ctx.event?.original == null +- remove: + field: message + tag: remove_message + ignore_missing: true + description: The `message` field is no longer required if the document has an `event.original` field. + if: ctx.event?.original != null +- json: + field: event.original + target_field: json + tag: parse_event_original_json + if: ctx.event?.original != null +- drop: + if: ctx.json.isAuditEvent == null + tag: drop_if_not_audit_event + description: >- + Drops messages that do not contain the isAuditEvent key +- set: + field: event.category + value: 'api' + if: ctx.json.message == "catalog.entity" +- date: + field: json.timestamp + target_field: '@timestamp' + formats: + - ISO8601 + tag: extract_audit_timestamp + if: ctx.json?.timestamp != null +- rename: + field: json.message + target_field: message + tag: rename_json_message +- rename: + field: json.severityLevel + target_field: backstage.audit.severity_level + tag: rename_audit_severity_level +- set: + field: event.severity + value: 3 + if: 'ctx.backstage.audit?.severity_level == "low"' +- set: + field: event.severity + value: 5 + if: 'ctx.backstage.audit?.severity_level == "medium"' +- set: + field: event.severity + value: 7 + if: 'ctx.backstage.audit?.severity_level == "high"' +- set: + field: event.type + value: 'access' + if: 'ctx.json.status == "initiated" && ctx.json.message == "catalog.entity-fetch"' +- set: + field: event.action + value: "{{{message}}}:{{{json.status}}}" +- rename: + field: json.plugin + target_field: event.module + tag: rename_json_plugin_to_event_module +- rename: + field: json.eventId + target_field: event.code + ignore_missing: true + tag: rename_eventId +- rename: + field: json.level + target_field: log.level + if: ctx.json?.level != null +- uri_parts: + field: json.request.url + tag: decode_request_uri + description: Decode the path for the requested URL against backstage +- rename: + field: json.actor.userAgent + target_field: user_agent.original + ignore_missing: true + tag: rename_actor_user_agent - user_agent: - tag: extract_user_agent + field: user_agent.original + tag: actor_user_agent + ignore_missing: true + if: ctx.user_agent?.original != null +- rename: + field: json.request.method + target_field: http.request.method + ignore_missing: true + tag: rename_http_request_method +- append: + field: related.hosts + copy_from: json.actor.hostname + ignore_empty_values: true + description: Append the hostname of the actor who initiated the request to backstage to the related hostnames array + tag: append_request_hostname_ecs_related +- append: + field: related.ip + copy_from: json.actor.ip + ignore_empty_values: true + description: Append the hostname of the actor who initiated the request to backstage to the related hostnames array + tag: append_request_hostname_ecs_related +- remove: + field: json.timestamp + ignore_missing: true + tag: remove_json_timestamp +- remove: + field: json.user_agent + ignore_missing: true + tag: remove_json_useragent +- remove: + field: json.isAuditEvent + ignore_missing: true + tag: remove_json_isauditevent +- remove: + field: json.request.url + ignore_missing: true + tag: remove_json_request +- remove: + field: url.domain + ignore_missing: true + tag: remove_null_url_domain + if: ctx.url.domain == null +- remove: + field: url.scheme + ignore_missing: true + tag: remove_null_url_scheme + if: ctx.url.scheme == null +- remove: + field: json.request + ignore_missing: true + tag: remove_empty_json_request +- remove: + field: json.actor.hostname + ignore_missing: true + tag: remove_json_actor_hostname +- remove: + field: json.actor.ip + ignore_missing: true + tag: remove_json_actor_hostname +- remove: + field: json.actor + ignore_missing: true + tag: remove_json_empty_actor + if: ctx.json?.actor?.size() == 0 +- remove: + field: json.message + ignore_missing: true + tag: remove_json_message +- remove: + field: json.service + ignore_missing: true + tag: remove_json_service + description: >- + This field should have been removed at the filebeat level + because of OTEL conventions. +- remove: + field: json.status + ignore_missing: true + tag: remove_json_status +# TODO: See what we can do with meta data +- remove: + field: json.meta ignore_missing: true + tag: remove_json_status on_failure: - set: field: error.message diff --git a/packages/backstage/data_stream/audit_logs/fields/backstage.yml b/packages/backstage/data_stream/audit_logs/fields/backstage.yml new file mode 100644 index 00000000000..fac93b6206a --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/fields/backstage.yml @@ -0,0 +1,12 @@ +- name: backstage + type: group + fields: + - name: audit + type: group + fields: + - name: severity_level + type: keyword + description: The severity level of the audit log registered by Backstage + - name: plugin + type: keyword + description: The plugin in backstage related to the audit log diff --git a/packages/backstage/docs/README.md b/packages/backstage/docs/README.md index 981f8d95587..498ae42de15 100644 --- a/packages/backstage/docs/README.md +++ b/packages/backstage/docs/README.md @@ -1,34 +1,30 @@ -{{- generatedHeader }} -{{/* -This template can be used as a starting point for writing documentation for your new integration. For each section, fill in the details -described in the comments. + + -Find more detailed documentation guidelines in https://www.elastic.co/docs/extend/integrations/documentation-guidelines -*/}} # Backstage Integration for Elastic ## Overview -{{/* Complete this section with a short summary of what data this integration collects and what use cases it enables */}} + The Backstage integration for Elastic enables collection of ... This integration facilitates ... ### Compatibility -{{/* Complete this section with information on what 3rd party software or hardware versions this integration is compatible with */}} + This integration is compatible with ... ### How it works -{{/* Add a high level overview on how this integration works. For example, does it collect data from API calls or recieving data from a network or file.*/}} + ## What data does this integration collect? -{{/* Complete this section with information on what types of data the integration collects, and link to reference documentation if available */}} + The Backstage integration collects log messages of the following types: * ... ### Supported use cases -{{/* Add details on the use cases that can be enabled by using this integration. Explain why a user would want to install and use this integration. */}} + ## What do I need to use this integration? -{{/* List any vendor-specific prerequisites needed before starting to install the integration. */}} + ## How do I deploy this integration? @@ -38,79 +34,55 @@ Elastic Agent must be installed. For more details, check the Elastic Agent [inst Elastic Agent is required to stream data from the syslog or log file receiver and ship the data to Elastic, where the events will then be processed via the integration's ingest pipelines. -{{/* If agentless is available for this integration, we'll want to include that here as well. -### Agentless deployment - -Agentless deployments are only supported in Elastic Serverless and Elastic Cloud environments. Agentless deployments provide a means to ingest data while avoiding the orchestration, management, and maintenance needs associated with standard ingest infrastructure. Using an agentless deployment makes manual agent deployment unnecessary, allowing you to focus on your data instead of the agent that collects it. -For more information, refer to [Agentless integrations](https://www.elastic.co/guide/en/serverless/current/security-agentless-integrations.html) and [Agentless integrations FAQ](https://www.elastic.co/guide/en/serverless/current/agentless-integration-troubleshooting.html) -*/}} ### Onboard / configure -{{/* List the steps that will need to be followed in order to completely set up a working integration. -For integrations that support multiple input types, be sure to add steps for all inputs. -*/}} + ### Validation -{{/* How can the user test whether the integration is working? Including example commands or test files if applicable */}} + ## Troubleshooting For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). -{{/* -Add any vendor specific troubleshooting here. -Are there common issues or “gotchas” for deploying this integration? If so, how can they be resolved? -If applicable, links to the third-party software’s troubleshooting documentation. -*/}} ## Scaling For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. -{{/* Add any vendor specific scaling information here */}} + ## Reference -{{/* Repeat for each data stream of the current type -### {Data stream name} -The `{data stream name}` data stream provides events from {source} of the following types: {list types}. -For each data_stream_name, include an optional summary of the datastream, the exported fields reference table and the sample event. -The fields template function will be replaced by a generated list of all fields from the `fields/` directory of the data stream when building the integration. -#### {data stream name} fields +### Data streams using ILM policies -To include a generated list of fields from the `fields/` directory, uncomment and use: -{{ fields "data_stream_name" }} -The event template function will be replace by a sample event, taken from `sample_event.json`, when building this integration. -To include a sample event from `sample_event.json`, uncomment and use: -{{ event "data_stream_name" }} -*/}} -{{/* Export ILM Policies - This accepts a list of data stream names as arguments, and will export the ILM Policies - for each given data stream name. If no arguments are provided, all ILM Policies will be - exported. - - If there are no ILM Policies defined, this will be an empty string. -*/}} -{{ ilm }} +### Inputs used -{{/* Export Transforms - This will export the transforms used by this integration. - If there are no transforms defined, this will be an empty string. -*/}} -{{ transform }} +These inputs can be used with this integration: +

+filestream + +## Setup + +For more details about the Filestream input settings, check the [Filebeat documentation](https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-filestream). + + +### Collecting logs from Filestream + +To collect logs via Filestream, select **Collect logs via Filestream** and configure the following parameters: + +- Filestream paths: The full path to the related log file. +
-### Inputs used -{{/* All inputs used by this package will be automatically listed here. */}} -{{ inputDocs }} ### API usage -{{/* For integrations that use APIs to collect data, document all the APIs that are used, and link to relevent information */}} + These APIs are used with this integration: * ... From 7e3dd4a24520dc9048da369325741007cfe22c82 Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Tue, 30 Jun 2026 12:13:54 -0400 Subject: [PATCH 05/11] Integration setup with ingest pipeline setup with pipeline tests and system tests. --- .../_dev/deploy/docker/docker-compose.yml | 11 + .../docker/sample_logs/test-audit-events.log} | 0 .../_dev/test/pipeline/test-audit.log | 11 + .../pipeline/test-audit.log-expected.json | 642 ++++++++++++++++++ .../pipeline/test-auditor.log-expected.json | 72 -- .../_dev/test/system/test-default-config.yml | 14 + .../agent/stream/filestream.yml.hbs | 2 +- .../elasticsearch/ingest_pipeline/default.yml | 482 ++++++++----- .../data_stream/audit_logs/fields/agent.yml | 45 ++ .../audit_logs/fields/backstage.yml | 34 +- .../audit_logs/fields/base-fields.yml | 5 +- .../data_stream/audit_logs/manifest.yml | 295 ++------ .../data_stream/audit_logs/sample_event.json | 120 ++++ packages/backstage/manifest.yml | 29 +- 14 files changed, 1248 insertions(+), 514 deletions(-) create mode 100644 packages/backstage/_dev/deploy/docker/docker-compose.yml rename packages/backstage/{data_stream/audit_logs/_dev/test/pipeline/test-auditor.log => _dev/deploy/docker/sample_logs/test-audit-events.log} (100%) create mode 100644 packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log create mode 100644 packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log-expected.json delete mode 100644 packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log-expected.json create mode 100644 packages/backstage/data_stream/audit_logs/_dev/test/system/test-default-config.yml create mode 100644 packages/backstage/data_stream/audit_logs/fields/agent.yml create mode 100644 packages/backstage/data_stream/audit_logs/sample_event.json diff --git a/packages/backstage/_dev/deploy/docker/docker-compose.yml b/packages/backstage/_dev/deploy/docker/docker-compose.yml new file mode 100644 index 00000000000..6cbf3d974cf --- /dev/null +++ b/packages/backstage/_dev/deploy/docker/docker-compose.yml @@ -0,0 +1,11 @@ +version: "2.3" +services: + backstage-audit: + image: alpine + hostname: backstage-audit + # environment: + # SERVICE_LOGS_DIR: "./test_service_logs" + volumes: + - ./sample_logs:/sample_logs:ro + - ${SERVICE_LOGS_DIR}:/var/log/ + command: /bin/sh -c "cp /sample_logs/* /var/log/" diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log b/packages/backstage/_dev/deploy/docker/sample_logs/test-audit-events.log similarity index 100% rename from packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log rename to packages/backstage/_dev/deploy/docker/sample_logs/test-audit-events.log diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log new file mode 100644 index 00000000000..be83aa91dce --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log @@ -0,0 +1,11 @@ +{"actor":{"hostname":"localhost","ip":"127.0.0.1","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"fields":["metadata","kind","spec.profile"],"filter":["kind=group,relations.hasMember=user:default/guest"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest"},"service":"backstage","severityLevel":"low","status":"initiated","timestamp":"2026-05-22T20:30:05.131Z"} +{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.35.249.216","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0"},"eventId":"entity-facets","isAuditEvent":true,"level":"info","message":"catalog.entity-facets","plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entity-facets?facet=metadata.namespace&filter=kind%3Dcomponent"},"service":"backstage","severityLevel":"low","status":"initiated"} +{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.248.50.223","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0"},"eventId":"entity-facets","isAuditEvent":true,"level":"info","message":"catalog.entity-facets","plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entity-facets?facet=metadata.tags&filter=kind%3Dcomponent"},"service":"backstage","severityLevel":"low","status":"succeeded"} +{"actor":{"hostname":"backstage.backstage.svc.cluster.local","actorId":"backstage-proxy","ip":"::ffff:10.188.154.218","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"filter":["kind=Resource,spec.type=github-repository,spec.implementation.apiVersion=github.***.dev/v1"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities/?filter=kind%3DResource%2Cspec.type%3Dgithub-repository%2Cspec.implementation.apiVersion%3Dgithub.***.dev%2Fv1"},"service":"backstage","severityLevel":"low","status":"initiated"} +{"actor":{"hostname":"backstage.backstage.svc.cluster.local","actorId":"backstage-proxy","ip":"::ffff:10.159.165.31","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"filter":["kind=NothingOfThisKind"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities?filter=kind%3DNothingOfThisKind"},"service":"backstage","severityLevel":"low","status":"succeeded"} +{"actor":{"hostname":"backstage.backstage.svc.cluster.local","actorId":"backstage-proxy","ip":"::ffff:10.158.110.116","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch No entity named 'octocat' found, with kind 'user' in namespace 'default'","meta":{"entityRef":"user:default/octocat","queryType":"by-name"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities/by-name/user/default/octocat?"},"service":"backstage","severityLevel":"low","status":"failed"} +{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.35.249.216","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task","meta":{"actionType":"stream","taskId":"b1d1549e-4a46-4159-a1af-293cc89eecae"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream"},"service":"backstage","severityLevel":"low","status":"initiated"} +{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.35.249.216","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task","meta":{"actionType":"stream","taskId":"b1d1549e-4a46-4159-a1af-293cc89eecae"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream"},"service":"backstage","severityLevel":"low","status":"succeeded"} +{"actor":{"actorId":"plugin:scaffolder"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task Could not find main. {data={\"what\":\"main\"}}","meta":{"actionType":"execution","templateRef":"template:default/register-in-catalog","createdBy":"user:default/octocat","taskParameters":{"owner":"group:default/platform-engineering","securityLevel":"high","createBoilerplateInGuestRepo":false,"division":"engineering","org":"platform","name":"ansible-***-cloud-enterprise","mainBranch":"main","githubOrg":"***"},"taskId":"3a03bcc9-48b3-4ecf-98c4-2a98caf9be85"},"plugin":"scaffolder","service":"backstage","severityLevel":"medium","status":"failed"} +{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.134.20.225","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"template-parameter-schema","isAuditEvent":true,"level":"info","message":"scaffolder.template-parameter-schema","meta":{"templateRef":"template:default/starter-kit"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema"},"service":"backstage","severityLevel":"low","status":"initiated"} +{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.134.20.225","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"template-parameter-schema","isAuditEvent":true,"level":"info","message":"scaffolder.template-parameter-schema","meta":{"templateRef":"Template:default/starter-kit"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema"},"service":"backstage","severityLevel":"low","status":"succeeded"} diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log-expected.json b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log-expected.json new file mode 100644 index 00000000000..81fc071ffb7 --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log-expected.json @@ -0,0 +1,642 @@ +{ + "expected": [ + { + "@timestamp": "2026-05-22T20:30:05.131Z", + "_tmp": {}, + "backstage": { + "query": { + "fields": [ + "metadata", + "kind", + "spec.profile" + ], + "filter": { + "kind": "group", + "relations": { + "hasMember": "user:default/guest" + } + }, + "type": "all" + }, + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "catalog.entity-fetch:initiated", + "code": "entity-fetch", + "module": "catalog", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "catalog.entity-fetch", + "related": { + "hosts": [ + "localhost" + ], + "ip": [ + "127.0.0.1" + ] + }, + "url": { + "original": "http://localhost/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", + "path": "/api/catalog/entities", + "query": "fields=metadata,kind,spec.profile&filter=kind=group,relations.hasMember=user:default/guest" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Chrome", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36", + "os": { + "full": "Mac OS X 10.15.7", + "name": "Mac OS X", + "version": "10.15.7" + }, + "version": "148.0.0.0" + } + }, + { + "_tmp": {}, + "backstage": { + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "catalog.entity-facets:initiated", + "code": "entity-facets", + "module": "catalog", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "catalog.entity-facets", + "related": { + "hosts": [ + "backstage.***.dev" + ], + "ip": [ + "::ffff:10.35.249.216" + ], + "user": [ + "user:default/octocat" + ] + }, + "url": { + "original": "http://localhost/api/catalog/entity-facets?facet=metadata.namespace&filter=kind%3Dcomponent", + "path": "/api/catalog/entity-facets", + "query": "facet=metadata.namespace&filter=kind=component" + }, + "user": { + "id": "user:default/octocat" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Firefox", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0", + "os": { + "full": "Mac OS X 10.15", + "name": "Mac OS X", + "version": "10.15" + }, + "version": "152.0" + } + }, + { + "_tmp": {}, + "backstage": { + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "catalog.entity-facets:succeeded", + "code": "entity-facets", + "module": "catalog", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "catalog.entity-facets", + "related": { + "hosts": [ + "backstage.***.dev" + ], + "ip": [ + "::ffff:10.248.50.223" + ], + "user": [ + "user:default/octocat" + ] + }, + "url": { + "original": "http://localhost/api/catalog/entity-facets?facet=metadata.tags&filter=kind%3Dcomponent", + "path": "/api/catalog/entity-facets", + "query": "facet=metadata.tags&filter=kind=component" + }, + "user": { + "id": "user:default/octocat" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Firefox", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0", + "os": { + "full": "Mac OS X 10.15", + "name": "Mac OS X", + "version": "10.15" + }, + "version": "152.0" + } + }, + { + "_tmp": {}, + "backstage": { + "query": { + "filter": { + "kind": "Resource", + "spec": { + "implementation": { + "apiVersion": "github.***.dev/v1" + }, + "type": "github-repository" + } + }, + "type": "all" + }, + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "catalog.entity-fetch:initiated", + "code": "entity-fetch", + "module": "catalog", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "catalog.entity-fetch", + "related": { + "hosts": [ + "backstage.backstage.svc.cluster.local" + ], + "ip": [ + "::ffff:10.188.154.218" + ], + "user": [ + "backstage-proxy" + ] + }, + "url": { + "original": "http://localhost/api/catalog/entities/?filter=kind%3DResource%2Cspec.type%3Dgithub-repository%2Cspec.implementation.apiVersion%3Dgithub.***.dev%2Fv1", + "path": "/api/catalog/entities/", + "query": "filter=kind=Resource,spec.type=github-repository,spec.implementation.apiVersion=github.***.dev/v1" + }, + "user": { + "id": "backstage-proxy" + }, + "user_agent": { + "device": { + "name": "Other" + }, + "name": "Other", + "original": "api-service" + } + }, + { + "_tmp": {}, + "backstage": { + "query": { + "filter": { + "kind": "NothingOfThisKind" + }, + "type": "all" + }, + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "catalog.entity-fetch:succeeded", + "code": "entity-fetch", + "module": "catalog", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "catalog.entity-fetch", + "related": { + "hosts": [ + "backstage.backstage.svc.cluster.local" + ], + "ip": [ + "::ffff:10.159.165.31" + ], + "user": [ + "backstage-proxy" + ] + }, + "url": { + "original": "http://localhost/api/catalog/entities?filter=kind%3DNothingOfThisKind", + "path": "/api/catalog/entities", + "query": "filter=kind=NothingOfThisKind" + }, + "user": { + "id": "backstage-proxy" + }, + "user_agent": { + "device": { + "name": "Other" + }, + "name": "Other", + "original": "api-service" + } + }, + { + "_tmp": {}, + "backstage": { + "meta": { + "entityRef": "user:default/octocat" + }, + "query": { + "type": "by-name" + }, + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "catalog.entity-fetch No entity named 'octocat' found, with kind 'user' in namespace 'default':failed", + "code": "entity-fetch", + "module": "catalog", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "catalog.entity-fetch No entity named 'octocat' found, with kind 'user' in namespace 'default'", + "related": { + "hosts": [ + "backstage.backstage.svc.cluster.local" + ], + "ip": [ + "::ffff:10.158.110.116" + ], + "user": [ + "backstage-proxy" + ] + }, + "url": { + "original": "http://localhost/api/catalog/entities/by-name/user/default/octocat?", + "path": "/api/catalog/entities/by-name/user/default/octocat", + "query": "" + }, + "user": { + "id": "backstage-proxy" + }, + "user_agent": { + "device": { + "name": "Other" + }, + "name": "Other", + "original": "api-service" + } + }, + { + "_tmp": {}, + "backstage": { + "meta": { + "actionType": "stream", + "taskId": "b1d1549e-4a46-4159-a1af-293cc89eecae" + }, + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "scaffolder.task:initiated", + "code": "task", + "module": "scaffolder", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "scaffolder.task", + "related": { + "hosts": [ + "backstage.***.dev" + ], + "ip": [ + "::ffff:10.35.249.216" + ], + "user": [ + "user:default/octocat" + ] + }, + "url": { + "original": "http://localhost/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream", + "path": "/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream" + }, + "user": { + "id": "user:default/octocat" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Chrome", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36", + "os": { + "full": "Mac OS X 10.15.7", + "name": "Mac OS X", + "version": "10.15.7" + }, + "version": "149.0.0.0" + } + }, + { + "_tmp": {}, + "backstage": { + "meta": { + "actionType": "stream", + "taskId": "b1d1549e-4a46-4159-a1af-293cc89eecae" + }, + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "scaffolder.task:succeeded", + "code": "task", + "module": "scaffolder", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "scaffolder.task", + "related": { + "hosts": [ + "backstage.***.dev" + ], + "ip": [ + "::ffff:10.35.249.216" + ], + "user": [ + "user:default/octocat" + ] + }, + "url": { + "original": "http://localhost/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream", + "path": "/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream" + }, + "user": { + "id": "user:default/octocat" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Chrome", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36", + "os": { + "full": "Mac OS X 10.15.7", + "name": "Mac OS X", + "version": "10.15.7" + }, + "version": "149.0.0.0" + } + }, + { + "backstage": { + "meta": { + "actionType": "execution", + "createdBy": "user:default/octocat", + "taskId": "3a03bcc9-48b3-4ecf-98c4-2a98caf9be85", + "taskParameters": { + "createBoilerplateInGuestRepo": false, + "division": "engineering", + "githubOrg": "***", + "mainBranch": "main", + "name": "ansible-***-cloud-enterprise", + "org": "platform", + "owner": "group:default/platform-engineering", + "securityLevel": "high" + }, + "templateRef": "template:default/register-in-catalog" + }, + "severity_level": "medium" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "scaffolder.task Could not find main. {data={\"what\":\"main\"}}:failed", + "code": "task", + "module": "scaffolder", + "severity": 5 + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "scaffolder.task Could not find main. {data={\"what\":\"main\"}}", + "related": { + "user": [ + "plugin:scaffolder" + ] + }, + "user": { + "id": "plugin:scaffolder" + } + }, + { + "_tmp": {}, + "backstage": { + "meta": { + "templateRef": "template:default/starter-kit" + }, + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "scaffolder.template-parameter-schema:initiated", + "code": "template-parameter-schema", + "module": "scaffolder", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "scaffolder.template-parameter-schema", + "related": { + "hosts": [ + "backstage.***.dev" + ], + "ip": [ + "::ffff:10.134.20.225" + ], + "user": [ + "user:default/octocat" + ] + }, + "url": { + "original": "http://localhost/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema", + "path": "/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema" + }, + "user": { + "id": "user:default/octocat" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Chrome", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36", + "os": { + "full": "Mac OS X 10.15.7", + "name": "Mac OS X", + "version": "10.15.7" + }, + "version": "149.0.0.0" + } + }, + { + "_tmp": {}, + "backstage": { + "meta": { + "templateRef": "Template:default/starter-kit" + }, + "severity_level": "low" + }, + "ecs": { + "version": "9.4.0" + }, + "event": { + "action": "scaffolder.template-parameter-schema:succeeded", + "code": "template-parameter-schema", + "module": "scaffolder", + "severity": 3 + }, + "http": { + "request": { + "method": "GET" + } + }, + "json": {}, + "log": { + "level": "info" + }, + "message": "scaffolder.template-parameter-schema", + "related": { + "hosts": [ + "backstage.***.dev" + ], + "ip": [ + "::ffff:10.134.20.225" + ], + "user": [ + "user:default/octocat" + ] + }, + "url": { + "original": "http://localhost/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema", + "path": "/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema" + }, + "user": { + "id": "user:default/octocat" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Chrome", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36", + "os": { + "full": "Mac OS X 10.15.7", + "name": "Mac OS X", + "version": "10.15.7" + }, + "version": "149.0.0.0" + } + } + ] +} diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log-expected.json b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log-expected.json deleted file mode 100644 index 3e843f42bfc..00000000000 --- a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-auditor.log-expected.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "expected": [ - { - "@timestamp": "2026-05-22T20:30:05.131Z", - "backstage": { - "audit": { - "severity_level": "low" - } - }, - "ecs": { - "version": "9.4.0" - }, - "event": { - "action": "catalog.entity-fetch:initiated", - "code": "entity-fetch", - "module": "catalog", - "original": "{\"actor\":{\"hostname\":\"localhost\",\"ip\":\"127.0.0.1\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch\",\"meta\":{\"query\":{\"fields\":[\"metadata\",\"kind\",\"spec.profile\"],\"filter\":[\"kind=group,relations.hasMember=user:default/guest\"]},\"queryType\":\"all\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\",\"timestamp\":\"2026-05-22T20:30:05.131Z\"}", - "severity": 3 - }, - "http": { - "request": { - "method": "GET" - } - }, - "json": { - "meta": { - "query": { - "fields": [ - "metadata", - "kind", - "spec.profile" - ], - "filter": [ - "kind=group,relations.hasMember=user:default/guest" - ] - }, - "queryType": "all" - } - }, - "log": { - "level": "info" - }, - "message": "catalog.entity-fetch", - "related": { - "hosts": [ - "localhost" - ], - "ip": [ - "127.0.0.1" - ] - }, - "url": { - "original": "/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", - "path": "/api/catalog/entities", - "query": "fields=metadata,kind,spec.profile&filter=kind=group,relations.hasMember=user:default/guest" - }, - "user_agent": { - "device": { - "name": "Mac" - }, - "name": "Chrome", - "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36", - "os": { - "full": "Mac OS X 10.15.7", - "name": "Mac OS X", - "version": "10.15.7" - }, - "version": "148.0.0.0" - } - } - ] -} diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/system/test-default-config.yml b/packages/backstage/data_stream/audit_logs/_dev/test/system/test-default-config.yml new file mode 100644 index 00000000000..112fcc16b22 --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/_dev/test/system/test-default-config.yml @@ -0,0 +1,14 @@ +wait_for_data_timeout: 1m +input: filestream +service: backstage-audit +data_stream: + vars: + preserve_original_event: true + paths: + - "{{SERVICE_LOGS_DIR}}/*.log" + custom: | + prospector.scanner.fingerprint.enabled: false + prospector.scanner.fingerprint.offset: 0 + prospector.scanner.fingerprint.length: 10 +agent: + user: root diff --git a/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs b/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs index 3bede63284a..ef30e327ace 100644 --- a/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs +++ b/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs @@ -41,4 +41,4 @@ publisher_pipeline.disable_host: true {{#if processors}} processors: {{processors}} -{{/if}} \ No newline at end of file +{{/if}} diff --git a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml index fb5100e4606..9eb18ecc988 100644 --- a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml +++ b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml @@ -1,189 +1,301 @@ --- description: Pipeline for backstage audit logs processors: -- set: - tag: set_ecs_version - field: ecs.version - value: '9.4.0' -- rename: - field: message - tag: rename_message_to_event_original - target_field: event.original - ignore_missing: true - description: >- - Renames the original `message` field to `event.original` to store a copy of the original message. - The `event.original` field is not touched if the document already has one. - if: ctx.event?.original == null -- remove: - field: message - tag: remove_message - ignore_missing: true - description: The `message` field is no longer required if the document has an `event.original` field. - if: ctx.event?.original != null -- json: - field: event.original - target_field: json - tag: parse_event_original_json - if: ctx.event?.original != null -- drop: - if: ctx.json.isAuditEvent == null - tag: drop_if_not_audit_event - description: >- - Drops messages that do not contain the isAuditEvent key -- set: - field: event.category - value: 'api' - if: ctx.json.message == "catalog.entity" -- date: - field: json.timestamp - target_field: '@timestamp' - formats: - - ISO8601 - tag: extract_audit_timestamp - if: ctx.json?.timestamp != null -- rename: - field: json.message - target_field: message - tag: rename_json_message -- rename: - field: json.severityLevel - target_field: backstage.audit.severity_level - tag: rename_audit_severity_level -- set: - field: event.severity - value: 3 - if: 'ctx.backstage.audit?.severity_level == "low"' -- set: - field: event.severity - value: 5 - if: 'ctx.backstage.audit?.severity_level == "medium"' -- set: - field: event.severity - value: 7 - if: 'ctx.backstage.audit?.severity_level == "high"' -- set: - field: event.type - value: 'access' - if: 'ctx.json.status == "initiated" && ctx.json.message == "catalog.entity-fetch"' -- set: - field: event.action - value: "{{{message}}}:{{{json.status}}}" -- rename: - field: json.plugin - target_field: event.module - tag: rename_json_plugin_to_event_module -- rename: - field: json.eventId - target_field: event.code - ignore_missing: true - tag: rename_eventId -- rename: - field: json.level - target_field: log.level - if: ctx.json?.level != null -- uri_parts: - field: json.request.url - tag: decode_request_uri - description: Decode the path for the requested URL against backstage -- rename: - field: json.actor.userAgent - target_field: user_agent.original - ignore_missing: true - tag: rename_actor_user_agent -- user_agent: - field: user_agent.original - tag: actor_user_agent - ignore_missing: true - if: ctx.user_agent?.original != null -- rename: - field: json.request.method - target_field: http.request.method - ignore_missing: true - tag: rename_http_request_method -- append: - field: related.hosts - copy_from: json.actor.hostname - ignore_empty_values: true - description: Append the hostname of the actor who initiated the request to backstage to the related hostnames array - tag: append_request_hostname_ecs_related -- append: - field: related.ip - copy_from: json.actor.ip - ignore_empty_values: true - description: Append the hostname of the actor who initiated the request to backstage to the related hostnames array - tag: append_request_hostname_ecs_related -- remove: - field: json.timestamp - ignore_missing: true - tag: remove_json_timestamp -- remove: - field: json.user_agent - ignore_missing: true - tag: remove_json_useragent -- remove: - field: json.isAuditEvent - ignore_missing: true - tag: remove_json_isauditevent -- remove: - field: json.request.url - ignore_missing: true - tag: remove_json_request -- remove: - field: url.domain - ignore_missing: true - tag: remove_null_url_domain - if: ctx.url.domain == null -- remove: - field: url.scheme - ignore_missing: true - tag: remove_null_url_scheme - if: ctx.url.scheme == null -- remove: - field: json.request - ignore_missing: true - tag: remove_empty_json_request -- remove: - field: json.actor.hostname - ignore_missing: true - tag: remove_json_actor_hostname -- remove: - field: json.actor.ip - ignore_missing: true - tag: remove_json_actor_hostname -- remove: - field: json.actor - ignore_missing: true - tag: remove_json_empty_actor - if: ctx.json?.actor?.size() == 0 -- remove: - field: json.message - ignore_missing: true - tag: remove_json_message -- remove: - field: json.service - ignore_missing: true - tag: remove_json_service - description: >- - This field should have been removed at the filebeat level - because of OTEL conventions. -- remove: - field: json.status - ignore_missing: true - tag: remove_json_status -# TODO: See what we can do with meta data -- remove: - field: json.meta - ignore_missing: true - tag: remove_json_status + - set: + tag: set_ecs_version + field: ecs.version + value: "9.4.0" + description: Sets the ECS version this pipeline targets. + - rename: + field: message + tag: rename_message_to_event_original + target_field: event.original + ignore_missing: true + description: >- + Renames the original `message` field to `event.original` to store a copy of the original message. + The `event.original` field is not touched if the document already has one. + if: ctx.event?.original == null + - remove: + field: message + tag: remove_message + ignore_missing: true + description: The `message` field is no longer required if the document has an `event.original` field. + if: ctx.event?.original != null + - json: + field: event.original + target_field: json + tag: parse_event_original_json + description: Parses the JSON-encoded `event.original` into the temporary `json` object for field extraction. + if: ctx.event?.original != null + - drop: + if: ctx.json.isAuditEvent == null + tag: drop_if_not_audit_event + description: >- + Drops messages that do not contain the isAuditEvent key + # TODO: review event.category value against ECS conventions and a broader sample of production logs + - set: + tag: set_event_category_a981526b + field: event.category + value: "api" + description: Sets `event.category` to `api` for catalog entity events. + if: ctx.json.message == "catalog.entity" + - date: + field: json.timestamp + target_field: "@timestamp" + formats: + - ISO8601 + tag: extract_audit_timestamp + description: Parses the audit `json.timestamp` (ISO8601) into `@timestamp`. + if: ctx.json?.timestamp != null + - rename: + field: json.message + target_field: message + tag: rename_json_message + description: Promotes the audit `json.message` to the top-level `message` field. + - rename: + field: json.severityLevel + target_field: backstage.severity_level + tag: rename_audit_severity_level + description: Sets the custom vendor field `backstage.severity_level` that defines the audit log's severity level. + - set: + tag: set_event_severity_0113ff8d + field: event.severity + value: 3 + description: Sets `event.severity` to 3 when the audit log's severity level is `low`. + if: 'ctx.backstage?.severity_level == "low"' + - set: + tag: set_event_severity_0716ffd2 + field: event.severity + value: 5 + description: Sets `event.severity` to 5 when the audit log's severity level is `medium`. + if: 'ctx.backstage?.severity_level == "medium"' + - set: + tag: set_event_severity_f5f4e875 + field: event.severity + value: 7 + description: Sets `event.severity` to 7 when the audit log's severity level is `high`. + if: 'ctx.backstage?.severity_level == "high"' + # TODO: expand event.type mapping based on all documented values of event message and status + - set: + tag: set_event_type_b24b0c9e + field: event.type + value: "access" + description: Sets `event.type` to `access` when a catalog entity fetch is initiated. + if: 'ctx.json.status == "initiated" && ctx.json.message == "catalog.entity-fetch"' + - set: + tag: set_event_action_8e4cef53 + field: event.action + value: "{{{message}}}:{{{json.status}}}" + description: Builds `event.action` by combining the audit message and status as `message:status`. + - rename: + field: json.plugin + target_field: event.module + tag: rename_json_plugin_to_event_module + description: Maps the Backstage plugin where the event originated from to the ECS `event.module` field. + - rename: + field: json.eventId + target_field: event.code + ignore_missing: true + tag: rename_eventId + description: >- + Maps the Backstage `json.eventId` to `event.code`. In Backstage, `eventId` represents a logical + group of related operations (e.g., `entity-fetch`, `location-mutate`), used here as the event + identification code per ECS conventions. + - rename: + tag: rename_json_level_to_log_level_69028571 + field: json.level + target_field: log.level + description: >- + Maps the Backstage logger `json.level` (e.g., `info`, `warn`, `error`) to the ECS `log.level` field. + This reflects the Winston log level at which the audit event was emitted, distinct from `backstage.severity_level`. + if: ctx.json?.level != null + - set: + field: _tmp.request_url + value: "http://localhost{{{json.request.url}}}" + tag: set_tmp_request_url + description: >- + Prepend a dummy scheme and host so uri_parts can parse a path-only or path with query strings URL + into ECS url.* fields. The artificial url.scheme and url.domain are removed afterward. + if: ctx.json?.request?.url != null + - uri_parts: + field: _tmp.request_url + tag: decode_request_uri + description: Decode the path for the requested URL against backstage + if: ctx._tmp?.request_url != null + - remove: + field: [_tmp.request_url, url.scheme, url.domain] + ignore_missing: true + tag: remove_tmp_url_artifacts + description: Removes the temporary `_tmp.request_url` and the artificial `url.scheme`/`url.domain` that were prepended to allow `uri_parts` to parse a path-only URL. + - rename: + field: json.actor.userAgent + target_field: user_agent.original + ignore_missing: true + tag: rename_actor_user_agent + description: Maps the raw user agent string from the Backstage actor to `user_agent.original` for subsequent parsing by the `user_agent` processor. + - rename: + field: json.actor.actorId + target_field: user.id + ignore_missing: true + tag: rename_actor_actorId + description: Maps the Backstage actor identifier to `user.id`. In Backstage, `actorId` is the unique identity of the principal who triggered the audit event (e.g., a user ref or service account). + - append: + field: related.user + copy_from: user.id + ignore_empty_values: true + tag: append_actor_id_related_user + description: Appends the Backstage actor identifier to `related.user` to support correlation across all user-related fields. + - user_agent: + field: user_agent.original + tag: actor_user_agent + ignore_missing: true + description: Parses the raw `user_agent.original` string into structured ECS `user_agent.*` fields (name, version, OS, device). + if: ctx.user_agent?.original != null + - rename: + field: json.request.method + target_field: http.request.method + ignore_missing: true + tag: rename_http_request_method + description: Maps the HTTP method of the inbound Backstage API request (e.g., `GET`, `POST`) to the ECS `http.request.method` field. + - append: + field: related.hosts + copy_from: json.actor.hostname + ignore_empty_values: true + description: Append the hostname of the actor who initiated the request to backstage to the related hostnames array + tag: append_request_hostname_ecs_related + - append: + field: related.ip + copy_from: json.actor.ip + ignore_empty_values: true + description: Appends the IP address of the actor who initiated the request to the `related.ip` array. + tag: append_related_ip_8c67e2b0 + - foreach: + field: json.meta.query.filter + if: ctx.json?.meta?.query?.filter instanceof List + ignore_missing: true + tag: foreach_meta_query_filter + description: >- + Iterates over each filter string in the Backstage catalog query and splits `key=value` pairs + into structured fields under `backstage.query.filter`. Backstage catalog filters follow the + format `kind=Component,metadata.name=foo`. + processor: + kv: + field: _ingest._value + target_field: backstage.query.filter + field_split: "," + value_split: "=" + on_failure: + - append: + field: error.message + value: '{{{_ingest.on_failure_message}}}' + - remove: + field: json.meta.query.filter + ignore_missing: true + tag: remove_meta_query_filter_source + description: Removes the raw `json.meta.query.filter` array after its entries have been parsed into `backstage.query.filter`. + - rename: + field: json.meta.query.fields + target_field: backstage.query.fields + ignore_missing: true + tag: rename_meta_query_fields + description: Maps the Backstage catalog query field selector to `backstage.query.fields`. This controls which entity fields are returned in the query response. + - rename: + field: json.meta.queryType + target_field: backstage.query.type + ignore_missing: true + tag: rename_meta_query_type + description: Maps the Backstage catalog `json.meta.queryType` to `backstage.query.type`, indicating the type of catalog query performed (e.g., entity lookup, batch fetch). + - remove: + field: json.timestamp + ignore_missing: true + tag: remove_json_timestamp + description: Removes `json.timestamp` after it has been parsed into `@timestamp`. + - remove: + field: json.user_agent + ignore_missing: true + tag: remove_json_useragent + description: Removes `json.user_agent` after it has been mapped to `user_agent.original`. + - remove: + field: json.isAuditEvent + ignore_missing: true + tag: remove_json_isauditevent + description: Removes the `json.isAuditEvent` gate field now that it has served its purpose of filtering non-audit log entries. + - remove: + field: json.request.url + ignore_missing: true + tag: remove_json_request + description: Removes `json.request.url` after it has been parsed into structured `url.*` ECS fields. + - remove: + field: json.request + ignore_missing: true + tag: remove_empty_json_request + description: Removes the now-empty `json.request` container after all its sub-fields have been extracted. + - remove: + field: json.actor.hostname + ignore_missing: true + tag: remove_json_actor_hostname + description: Removes `json.actor.hostname` after it has been appended to `related.hosts`. + - remove: + field: json.actor.ip + ignore_missing: true + tag: remove_json_actor_ip_46c2e907 + description: Removes `json.actor.ip` after it has been appended to `related.ip`. + - remove: + field: json.actor + ignore_missing: true + tag: remove_json_empty_actor + description: Removes the `json.actor` container once all actor fields have been extracted and it is empty. + if: ctx.json?.actor?.size() == 0 + - remove: + field: json.message + ignore_missing: true + tag: remove_json_message + description: Removes `json.message` after it has been promoted to the top-level `message` field. + - remove: + field: json.service + ignore_missing: true + tag: remove_json_service + description: >- + This field should have been removed at the filebeat level + because of OTEL conventions. + - remove: + field: json.status + ignore_missing: true + tag: remove_json_status + description: Removes `json.status` after it has been incorporated into `event.action`. In Backstage, `status` indicates the outcome of the audited operation (e.g., `initiated`, `succeeded`, `failed`). + - remove: + field: json.meta.query + ignore_missing: true + if: ctx.json?.meta?.query == null || ctx.json.meta.query.isEmpty() + tag: remove_empty_meta_query + description: Removes the `json.meta.query` container when it is null or empty after all query sub-fields have been extracted into `backstage.query.*`. + - rename: + field: json.meta + target_field: backstage.meta + ignore_missing: true + if: ctx.json?.meta != null && !ctx.json.meta.isEmpty() + tag: rename_meta_catchall + description: Moves any remaining `json.meta` fields into `backstage.meta` to preserve plugin-specific metadata that was not explicitly mapped to an ECS field. + - remove: + field: json.meta + ignore_missing: true + tag: remove_empty_json_meta + description: Removes the now-empty `json.meta` container after all its fields have been moved to `backstage.meta`. + - remove: + field: event.original + ignore_missing: true + if: ctx.tags == null || !(ctx.tags.contains('preserve_original_event')) + tag: remove_json_original_event_ss87a8429d + description: Removes the raw JSON string stored in `event.original` to reduce document size. Skipped when the `preserve_original_event` tag is set. on_failure: -- set: - field: error.message - value: >- - Processor '{{{ _ingest.on_failure_processor_type }}}' - with tag '{{{ _ingest.on_failure_processor_tag }}}' - in pipeline '{{{ _ingest.pipeline }}}' - failed with message '{{{ _ingest.on_failure_message }}}' -- set: - field: event.kind - tag: set_pipeline_error_to_event_kind - value: pipeline_error + - set: + field: error.message + value: >- + Processor '{{{ _ingest.on_failure_processor_type }}}' + with tag '{{{ _ingest.on_failure_processor_tag }}}' + in pipeline '{{{ _ingest.pipeline }}}' + failed with message '{{{ _ingest.on_failure_message }}}' + - set: + field: event.kind + tag: set_pipeline_error_to_event_kind + value: pipeline_error diff --git a/packages/backstage/data_stream/audit_logs/fields/agent.yml b/packages/backstage/data_stream/audit_logs/fields/agent.yml new file mode 100644 index 00000000000..0cd08ad062a --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/fields/agent.yml @@ -0,0 +1,45 @@ +- name: cloud + title: Cloud + group: 2 + description: Fields related to the cloud or infrastructure the events are coming from. + footnote: "Examples: If Metricbeat is running on an EC2 host and fetches data from its host, the cloud info contains the data about this machine. If Metricbeat runs on a remote machine outside the cloud and fetches data from a service running in the cloud, the field contains cloud data from the machine the service is running on." + type: group + fields: + - name: image.id + type: keyword + description: Image ID for the cloud instance. +- name: host + title: Host + group: 2 + description: "A host is defined as a general computing instance.\nECS host.* fields should be populated with details about the host on which the event happened, or from which the measurement was taken. Host types include hardware, virtual machines, Docker containers, and Kubernetes nodes." + type: group + fields: + - name: containerized + type: boolean + description: > + If the host is a container. + + - name: os.build + type: keyword + example: "18D109" + description: > + OS build information. + + - name: os.codename + type: keyword + example: "stretch" + description: > + OS codename, if any. + +- name: log.offset + type: long + description: Log offset +- name: log.file.device_id + type: keyword + description: Device Id of the log file this event came from. +- name: log.file.inode + type: keyword + description: Inode number of the log file. +- name: log.file.path + type: keyword + description: Path to the log file. diff --git a/packages/backstage/data_stream/audit_logs/fields/backstage.yml b/packages/backstage/data_stream/audit_logs/fields/backstage.yml index fac93b6206a..1f014627b87 100644 --- a/packages/backstage/data_stream/audit_logs/fields/backstage.yml +++ b/packages/backstage/data_stream/audit_logs/fields/backstage.yml @@ -1,12 +1,28 @@ - name: backstage type: group fields: - - name: audit - type: group - fields: - - name: severity_level - type: keyword - description: The severity level of the audit log registered by Backstage - - name: plugin - type: keyword - description: The plugin in backstage related to the audit log + - name: severity_level + type: keyword + description: The severity level of the audit log registered by Backstage + - name: plugin + type: keyword + description: The plugin in backstage related to the audit log + - name: meta + type: flattened + description: >- + Event-specific metadata emitted by the Backstage plugin. Shape varies per + event type; stored as `flattened` to preserve arbitrary keys without mapping + explosion. Well-known catalog-query keys are promoted to `backstage.audit.query.*`. + - name: query + type: group + fields: + - name: fields + type: keyword + description: The entity fields requested by the catalog query. + - name: type + type: keyword + description: The type of catalog query that was issued (e.g. "all"). + - name: filter + type: flattened + description: >- + The catalog query filter conditions as key/value pairs. Declared `flattened` to store arbitrary filter keys (e.g. kind, relations.hasMember) without mapping explosion. diff --git a/packages/backstage/data_stream/audit_logs/fields/base-fields.yml b/packages/backstage/data_stream/audit_logs/fields/base-fields.yml index 7c798f4534c..9a67151e631 100644 --- a/packages/backstage/data_stream/audit_logs/fields/base-fields.yml +++ b/packages/backstage/data_stream/audit_logs/fields/base-fields.yml @@ -7,6 +7,9 @@ - name: data_stream.namespace type: constant_keyword description: Data stream namespace. -- name: '@timestamp' +- name: "@timestamp" type: date description: Event timestamp. +- name: input.type + type: keyword + description: Input type diff --git a/packages/backstage/data_stream/audit_logs/manifest.yml b/packages/backstage/data_stream/audit_logs/manifest.yml index 3c54751aee3..c0b71ba3031 100644 --- a/packages/backstage/data_stream/audit_logs/manifest.yml +++ b/packages/backstage/data_stream/audit_logs/manifest.yml @@ -9,247 +9,90 @@ streams: vars: - name: paths type: text - title: "Paths" + title: Paths multi: true required: true show_user: true default: - - /var/log/*.log - - name: data_stream.dataset - type: text - title: "Dataset name" - description: |- - Dataset to write data to. Changing the dataset will send the data to a different index. You can't use `-` in the name of a dataset and only valid characters for [Elasticsearch index names](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html). + - /var/logs/*.log + description: Backstage logs + - name: preserve_original_event required: true show_user: true - default: filestream.generic - - name: pipeline - type: text - title: "Ingest Pipeline" - description: |- - The Ingest Node pipeline ID to be used by the integration. - show_user: true - - name: parsers - type: yaml - title: "Parsers" - description: |- - This option expects a list of parsers that the log line has to go through. For more information see [Parsers](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#_parsers) - show_user: true - default: | - #- ndjson: - # target: "" - # message_key: msg - #- multiline: - # type: count - # count_lines: 3 - - name: exclude_files + title: Preserve original event + description: Preserves a raw copy of the original event, added to the field `event.original`. + type: bool + multi: false + default: false + - name: tags type: text - title: "Exclude Files" - description: |- - A list of regular expressions to match the files that you want Elastic Agent to ignore. By default no files are excluded. + title: Tags multi: true - show_user: true + required: true + show_user: false default: - - \.gz$ - - name: include_files - type: text - title: "Include Files" - description: |- - A list of regular expressions to match the files that you want Elastic Agent to include. If a list of regexes is provided, only the files that are allowed by the patterns are harvested. - multi: true - show_user: true + - backstage - name: processors type: yaml - title: "Processors" - description: |- - Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/beats/filebeat/current/filtering-and-enhancing-data.html) for details. - - name: tags - type: text - title: "Tags" - description: |- - Tags to include in the published event - multi: true - show_user: true - - name: encoding - type: text - title: "Encoding" - description: |- - The file encoding to use for reading data that contains international characters. For a full list of valid encodings, see the [Documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#_encoding_2) - - name: recursive_glob - type: bool - title: "Recursive Glob" - description: |- - Enable expanding `**` into recursive glob patterns. With this feature enabled, the rightmost `**` in each path is expanded into a fixed number of glob patterns. For example: `/foo/**` expands to `/foo`, `/foo/*`, `/foo/*/*`, and so on. If enabled it expands a single `**` into a 8-level deep `*` pattern. - This feature is enabled by default. Set prospector.scanner.recursive_glob to false to disable it. - default: true - - name: symlinks - type: bool - title: "Enable symlinks" - description: |- - The symlinks option allows Elastic Agent to harvest symlinks in addition to regular files. When harvesting symlinks, Elastic Agent opens and reads the original file even though it reports the path of the symlink. - **Because this option may lead to data loss, it is disabled by default.** - - name: resend_on_touch - type: bool - title: "Resend on touch" - description: |- - If this option is enabled a file is resent if its size has not changed but its modification time has changed to a later time than before. It is disabled by default to avoid accidentally resending files. - - name: check_interval - type: text - title: "Check Interval" - description: |- - How often Elastic Agent checks for new files in the paths that are specified for harvesting. For example Specify 1s to scan the directory as frequently as possible without causing Elastic Agent to scan too frequently. **We do not recommend to set this value <1s.** - - name: ignore_older - type: text - title: "Ignore Older" - description: |- - If this option is enabled, Elastic Agent ignores any files that were modified before the specified timespan. You can use time strings like 2h (2 hours) and 5m (5 minutes). The default is 0, which disables the setting. - You must set Ignore Older to be greater than On State Change Inactive. - For more information, please see the [Documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#filebeat-input-filestream-ignore-older) - - name: ignore_inactive - type: text - title: "Ignore Inactive" - description: |- - If this option is enabled, Elastic Agent ignores every file that has not been updated since the selected time. Possible options are since_first_start and since_last_start. - - name: close_on_state_changed_inactive - type: text - title: "Close on State Changed Inactive" - description: |- - When this option is enabled, Elastic Agent closes the file handle if a file has not been harvested for the specified duration. The counter for the defined period starts when the last log line was read by the harvester. It is not based on the modification time of the file. If the closed file changes again, a new harvester is started and the latest changes will be picked up after Check Interval has elapsed. - - name: close_on_state_changed_renamed - type: bool - title: "Close on State Changed Renamed" - description: |- - **Only use this option if you understand that data loss is a potential side effect.** - When this option is enabled, Elastic Agent closes the file handler when a file is renamed. This happens, for example, when rotating files. By default, the harvester stays open and keeps reading the file because the file handler does not depend on the file name. - - name: close_on_state_changed_removed - type: bool - title: "Close on State Changed Removed" - description: |- - When this option is enabled, Elastic Agent closes the harvester when a file is removed. Normally a file should only be removed after it’s inactive for the duration specified by close.on_state_change.inactive. - - name: close_reader_eof - type: bool - title: "Close Reader EOF" - description: |- - **Only use this option if you understand that data loss is a potential side effect.** - When this option is enabled, Elastic Agent closes a file as soon as the end of a file is reached. This is useful when your files are only written once and not updated from time to time. For example, this happens when you are writing every single log event to a new file. This option is disabled by default. - - name: close_reader_after_interval - type: text - title: "Close Reader After Interval" - description: |- - **Only use this option if you understand that data loss is a potential side effect. Another side effect is that multiline events might not be completely sent before the timeout expires.** - This option is particularly useful in case the output is blocked, which makes Elastic Agent keep open file handlers even for files that were deleted from the disk. - For more information see the [documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#filebeat-input-filestream-close-timeout). - - name: clean_inactive - type: text - title: "Clean Inactive" - description: |- - **Only use this option if you understand that data loss is a potential side effect.** - When this option is enabled, Elastic Agent removes the state of a file after the specified period of inactivity has elapsed. - E.g: "30m", Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". By default cleaning inactive states is disabled, -1 is used to disable it. - default: -1 - - name: clean_removed - type: bool - title: "Clean Removed" - description: |- - When this option is enabled, Elastic Agent cleans files from the registry if they cannot be found on disk anymore under the last known name. - **You must disable this option if you also disable Close Removed.** + title: Processors + multi: false + required: false + show_user: false + description: Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/fleet/current/elastic-agent-processor-configuration.html) for details. - name: harvester_limit type: integer - title: "Harvester Limit" - description: |- - The harvester_limit option limits the number of harvesters - that are started in parallel for one input. This directly - relates to the maximum number of file handlers that are - opened. The default is 0 (no limit). - default: 0 - - name: backoff_init - type: text - title: "Backoff Init" - description: |- - The backoff option defines how long Elastic Agent waits before checking a file again after EOF is reached. The default is 1s. - - name: backoff_max - type: text - title: "Backoff Max" - description: |- - The maximum time for Elastic Agent to wait before checking a file again after EOF is reached. The default is 10s. - **Requirement: Set Backoff Max to be greater than or equal to Backoff Init and less than or equal to Check Interval (Backoff Init <= Backoff Max <= Check Interval).** - - name: fingerprint - type: bool - title: "File identity: Fingerprint" - description: |- - **Changing file_identity methods between runs may result in - duplicated events in the output.** - Uses a fingerprint generated from the first few bytes (1k is - the default, this can be configured via Fingerprint offset - and length) to identify a file instead inode + device ID. - Refer to https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#_file_identity_2 - for more details. If this option is disabled (and 'Native - file identity is not enabled'), Elastic-Agent < 9.0.0 will - use Native as the file identity, and >= 9.0.0 will use - Fingerprint with the default offset and length. - default: true - - name: fingerprint_offset - type: integer - title: "File identity: Fingerprint offset" - description: |- - Offset from the beginning of the file to start calculating - the fingerprint. The default is 0. Only used when the - fingerprint file identity is selected + title: Harvester Limit + description: Limits the number of files that are ingested in parallel. More details [here](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#filebeat-input-filestream-harvester-limit). + required: false + show_user: false default: 0 - - name: fingerprint_length - type: integer - title: "File identity: Fingerprint length" - description: |- - The number of bytes used to calculate the fingerprint. The - default is 1024. Only used when the fingerprint file - identity is selected. - default: 1024 - - name: file_identity_native - type: bool - title: "File identity: Native" - description: |- - **Changing file_identity methods between runs may result in - duplicated events in the output.** - Uses a native identifier for files, on most Unix-like - file systems this is the inode + device ID. On file systems - that do not support inode, the native equivalent is used. - If you enable this option you **MUST disable Fingerprint - file identity**. Refer to - https://www.elastic.co/docs/reference/beats/filebeat/filebeat-input-filestream - for more details. - default: false - - name: rotation_external_strategy_copytruncate + - name: close.on_state_change.inactive + type: text + title: File Handle Closure Duration + description: The duration after which the file handle is closed if the file has not been updated. More details [here](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#filebeat-input-filestream-close-inactive). + multi: false + required: false + show_user: false + default: 5m + - name: custom type: yaml - title: "Rotation Strategy" - description: "If the log rotating application copies the contents of the active file and then truncates the original file, use these options to help Elastic Agent to read files correctly.\nSet the option suffix_regex so Elastic Agent can tell active and rotated files apart. \nThere are two supported suffix types in the input: numberic and date." - - name: exclude_lines - type: text - title: "Exclude Lines" - description: |- - A list of regular expressions to match the lines that you want Elastic Agent to exclude. Elastic Agent drops any lines that match a regular expression in the list. By default, no lines are dropped. Empty lines are ignored. - multi: true - - name: include_lines - type: text - title: "Include Lines" - description: |- - A list of regular expressions to match the lines that you want Elastic Agent to include. Elastic Agent exports only the lines that match a regular expression in the list. By default, all lines are exported. Empty lines are ignored. - multi: true - - name: buffer_size - type: text - title: "Buffer Size" - description: |- - The size in bytes of the buffer that each harvester uses when fetching a file. The default is 16384. - - name: message_max_bytes - type: text - title: "Message Max Bytes" - description: |- - The maximum number of bytes that a single log message can have. All bytes after mesage_max_bytes are discarded and not sent. The default is 10MB (10485760). - - name: condition - type: text - title: "Condition" - description: |- - Condition to filter when to collect this input. See [Dynamic Input Configuration](https://www.elastic.co/guide/en/fleet/current/dynamic-input-configuration.html) for details. - show_user: true + title: Additional Filestream Configuration Options + required: false + show_user: false + description: >- + Configuration options that can be used to further change input configuration. Check the [Filebeat documentation](https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html) for more information. Be careful, this may break the integration. + default: |- + #encoding: plain + #prospector.scanner.recursive_glob: true + #prospector.scanner.symlinks: true + #prospector.scanner.include_files: [''] + #prospector.scanner.resend_on_touch: false + #prospector.scanner.check_interval: 10s + #prospector.scanner.fingerprint.enabled: false + #prospector.scanner.fingerprint.offset: 0 + #prospector.scanner.fingerprint.length: 1024 + #ignore_older: 0 + #ignore_inactive: '' + #close.on_state_change.inactive: 5m + #close.on_state_change.renamed: false + #close.on_state_change.removed: false + #close.reader.on_eof: false + #close.reader.after_interval: 0 + #clean_inactive: 0 + #clean_removed: true + #backoff.init: 2s + #backoff.max: 10s + #file_identity.native: ~ + #file_identity.path: '' + #file_identity.inode_marker.path: '' + #file_identity.fingerprint: ~ + #rotation.external.strategy.copytruncate.suffix_regex: '\.\d$' + #rotation.external.strategy.copytruncate.dateformat: '-20060102' + #include_lines: ['sometext'] + #exclude_lines: ['^DBG'] + #buffer_size: 16384 + #message_max_bytes: 1048576 + # TODO Check if we have more filestream specific variables to add in, double check with filestream docs elasticsearch: index_template: mappings: diff --git a/packages/backstage/data_stream/audit_logs/sample_event.json b/packages/backstage/data_stream/audit_logs/sample_event.json new file mode 100644 index 00000000000..fac7483d84e --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/sample_event.json @@ -0,0 +1,120 @@ +{ + "@timestamp": "2026-05-22T20:30:05.131Z", + "agent": { + "ephemeral_id": "ea32d543-c037-4007-ae5a-8fcbbea0ae1c", + "id": "c869b402-bcb5-4114-bcad-f012010d9c01", + "name": "elastic-agent-36554", + "type": "filebeat", + "version": "9.3.3" + }, + "backstage": { + "query": { + "fields": [ + "metadata", + "kind", + "spec.profile" + ], + "filter": { + "kind": "group", + "relations": { + "hasMember": "user:default/guest" + } + }, + "type": "all" + }, + "severity_level": "low" + }, + "data_stream": { + "dataset": "backstage.audit_logs", + "namespace": "24583", + "type": "logs" + }, + "ecs": { + "version": "9.4.0" + }, + "elastic_agent": { + "id": "c869b402-bcb5-4114-bcad-f012010d9c01", + "snapshot": false, + "version": "9.3.3" + }, + "event": { + "action": "catalog.entity-fetch:initiated", + "agent_id_status": "verified", + "code": "entity-fetch", + "dataset": "backstage.audit_logs", + "ingested": "2026-06-30T15:29:46Z", + "module": "catalog", + "original": "{\"actor\":{\"hostname\":\"localhost\",\"ip\":\"127.0.0.1\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch\",\"meta\":{\"query\":{\"fields\":[\"metadata\",\"kind\",\"spec.profile\"],\"filter\":[\"kind=group,relations.hasMember=user:default/guest\"]},\"queryType\":\"all\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\",\"timestamp\":\"2026-05-22T20:30:05.131Z\"}", + "severity": 3 + }, + "host": { + "architecture": "aarch64", + "containerized": false, + "hostname": "elastic-agent-36554", + "ip": [ + "172.21.0.2", + "172.18.0.4" + ], + "mac": [ + "4E-08-C5-12-BF-C2", + "AA-11-51-9A-46-F3" + ], + "name": "elastic-agent-36554", + "os": { + "family": "", + "kernel": "6.12.76-linuxkit", + "name": "Wolfi", + "platform": "wolfi", + "type": "linux", + "version": "20230201" + } + }, + "http": { + "request": { + "method": "GET" + } + }, + "input": { + "type": "filestream" + }, + "log": { + "file": { + "device_id": "44", + "inode": "96", + "path": "/tmp/service_logs/test-audit-events.log" + }, + "level": "info", + "offset": 0 + }, + "message": "catalog.entity-fetch", + "related": { + "hosts": [ + "localhost" + ], + "ip": [ + "127.0.0.1" + ] + }, + "tags": [ + "backstage", + "preserve_original_event" + ], + "url": { + "original": "http://localhost/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", + "path": "/api/catalog/entities", + "query": "fields=metadata,kind,spec.profile&filter=kind=group,relations.hasMember=user:default/guest" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Chrome", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36", + "os": { + "full": "Mac OS X 10.15.7", + "name": "Mac OS X", + "version": "10.15.7" + }, + "version": "148.0.0.0" + } +} diff --git a/packages/backstage/manifest.yml b/packages/backstage/manifest.yml index 63364c40df1..fee62c7af35 100644 --- a/packages/backstage/manifest.yml +++ b/packages/backstage/manifest.yml @@ -7,31 +7,20 @@ source: description: "Fetches audit logs from spotify backstage for security analysis" type: integration categories: - - custom + - observability - security + - web conditions: kibana: version: "^9.3.3" - elastic: - subscription: "basic" -screenshots: - - src: /img/sample-screenshot.png - title: Sample screenshot - size: 600x600 - type: image/png -icons: - - src: /img/sample-logo.svg - title: Sample logo - size: 32x32 - type: image/svg+xml policy_templates: - - name: sample - title: Sample logs - description: Collect sample logs + - name: backstage + title: Backstage logs + description: Collect audit logs that document user activity across backstage's platform and plugins inputs: - - type: logfile - title: Collect sample logs from instances - description: Collecting sample logs + - type: filestream + title: Backstage container logs + description: Collecting logs from backstage's docker container or k8s pod owner: - github: elastic/integrations + github: elastic/integration-experience type: elastic From 9793c091916124585fea9a25e06305f85529100f Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Sun, 5 Jul 2026 23:40:21 -0400 Subject: [PATCH 06/11] Add package icon and correct Kibana version constraint Replace scaffold placeholder icon/screenshot with the real Backstage icon, remove the stray package-root sample_event.json (belongs under the data stream instead), and correct the root manifest's Kibana version constraint from an overly narrow pinned version to the current package standard. --- packages/backstage/img/backstage-icon-color.png | Bin 0 -> 16795 bytes packages/backstage/img/sample-logo.svg | 1 - packages/backstage/img/sample-screenshot.png | Bin 18849 -> 0 bytes packages/backstage/manifest.yml | 7 ++++++- packages/backstage/sample_event.json | 3 --- 5 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 packages/backstage/img/backstage-icon-color.png delete mode 100644 packages/backstage/img/sample-logo.svg delete mode 100644 packages/backstage/img/sample-screenshot.png delete mode 100644 packages/backstage/sample_event.json diff --git a/packages/backstage/img/backstage-icon-color.png b/packages/backstage/img/backstage-icon-color.png new file mode 100644 index 0000000000000000000000000000000000000000..e0d3447c04e3bd4ada783e2ff45d301352f3fabb GIT binary patch literal 16795 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 literal 0 HcmV?d00001 diff --git a/packages/backstage/img/sample-logo.svg b/packages/backstage/img/sample-logo.svg deleted file mode 100644 index 6268dd88f3b..00000000000 --- a/packages/backstage/img/sample-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/packages/backstage/img/sample-screenshot.png b/packages/backstage/img/sample-screenshot.png deleted file mode 100644 index d7a56a3ecc078c38636698cefba33f86291dd178..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18849 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

}qal5$moSACwfNXLXG5|3R0AtBcN` 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 diff --git a/packages/backstage/manifest.yml b/packages/backstage/manifest.yml index fee62c7af35..8e516436d50 100644 --- a/packages/backstage/manifest.yml +++ b/packages/backstage/manifest.yml @@ -12,7 +12,12 @@ categories: - web conditions: kibana: - version: "^9.3.3" + version: "^8.19.0 || ^9.1.0" +icons: + - src: /img/backstage-icon-color.png + title: Backstage + size: 202x256 + type: image/png policy_templates: - name: backstage title: Backstage logs diff --git a/packages/backstage/sample_event.json b/packages/backstage/sample_event.json deleted file mode 100644 index bdf90044196..00000000000 --- a/packages/backstage/sample_event.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "description": "This is an example sample-event for Backstage. Replace it with a real sample event. Hint: If system tests exist, running `elastic-package test system --generate` will generate this file." -} From b5318aff2c29fc1f45d72e155121cdfb42fc114c Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Sun, 5 Jul 2026 23:41:00 -0400 Subject: [PATCH 07/11] Fix changelog PR link Replace the placeholder link with the real pull request URL. --- packages/backstage/changelog.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backstage/changelog.yml b/packages/backstage/changelog.yml index bb0320a5243..88f901fe0e2 100644 --- a/packages/backstage/changelog.yml +++ b/packages/backstage/changelog.yml @@ -3,4 +3,4 @@ changes: - description: Initial draft of the package type: enhancement - link: https://github.com/elastic/integrations/pull/1 # FIXME Replace with the real PR link + link: https://github.com/elastic/integrations/pull/19408 From 729947bccb53ae13dcd268d37caad1c91c767bf0 Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Sun, 5 Jul 2026 23:42:34 -0400 Subject: [PATCH 08/11] Rework ingest pipeline for ECS categorization, outcome mapping, and field cleanup - Set event.kind/category/type as proper ECS arrays via append, and derive event.outcome from the Backstage audit status (initiated -> unknown, succeeded -> success, failed -> failure). - Guard the isAuditEvent drop check with null-safe navigation so events without a parsed json object are dropped cleanly instead of throwing. - Map the actor's IP to the primary ECS source.ip field (in addition to related.ip for correlation) rather than only ever appending it to related.ip. - Remove processors left over from earlier refactors that no longer do anything: two dead `remove` steps targeting field paths that never exist (json.user_agent, json.message) and a third that became dead once the actor IP is now consumed by a rename into source.ip. - Add ignore_missing to renames that would otherwise throw if their source field is ever absent on a valid audit event. - Split fields/agent.yml into agent.yml (Elastic Agent metadata) and beats.yml (Filebeat-emitted fields), matching the convention used by other filestream-based packages. - Add fields/ecs.yml declaring every ECS field the pipeline sets (event.kind/category/type/outcome/severity/action/provider/code, log.level, url.*, user_agent.original, user.id, related.*, http.request.method, source.ip) so they resolve correctly and show up in the generated documentation. - Drop backstage.plugin (superseded by event.provider) and fix the backstage.meta description to reference the field path the pipeline actually populates (backstage.query.*, not backstage.audit.query.*). --- .../elasticsearch/ingest_pipeline/default.yml | 138 ++++++++++++------ .../data_stream/audit_logs/fields/agent.yml | 16 +- .../audit_logs/fields/backstage.yml | 45 +++--- .../audit_logs/fields/base-fields.yml | 21 +-- .../data_stream/audit_logs/fields/beats.yml | 15 ++ .../data_stream/audit_logs/fields/ecs.yml | 38 +++++ 6 files changed, 178 insertions(+), 95 deletions(-) create mode 100644 packages/backstage/data_stream/audit_logs/fields/beats.yml create mode 100644 packages/backstage/data_stream/audit_logs/fields/ecs.yml diff --git a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml index 9eb18ecc988..5f490d0cfa9 100644 --- a/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml +++ b/packages/backstage/data_stream/audit_logs/elasticsearch/ingest_pipeline/default.yml @@ -28,17 +28,60 @@ processors: description: Parses the JSON-encoded `event.original` into the temporary `json` object for field extraction. if: ctx.event?.original != null - drop: - if: ctx.json.isAuditEvent == null + if: ctx.json?.isAuditEvent == null tag: drop_if_not_audit_event description: >- Drops messages that do not contain the isAuditEvent key - # TODO: review event.category value against ECS conventions and a broader sample of production logs - set: - tag: set_event_category_a981526b + tag: set_event_kind + field: event.kind + value: event + description: Sets `event.kind` to `event` for Backstage audit events. + - append: + tag: append_event_category_api field: event.category - value: "api" - description: Sets `event.category` to `api` for catalog entity events. - if: ctx.json.message == "catalog.entity" + value: api + allow_duplicates: false + description: Sets `event.category` to `api` for Backstage audit events. + - append: + tag: append_event_type_access + field: event.type + value: access + allow_duplicates: false + description: Sets `event.type` to `access` when the audit event includes request details. + if: ctx.json?.request != null + - append: + tag: append_event_type_start + field: event.type + value: start + allow_duplicates: false + description: Sets `event.type` to `start` for initiated Backstage audit operations. + if: 'ctx.json?.status == "initiated"' + - append: + tag: append_event_type_end + field: event.type + value: end + allow_duplicates: false + description: Sets `event.type` to `end` for completed Backstage audit operations. + if: 'ctx.json?.status == "succeeded" || ctx.json?.status == "failed"' + - set: + tag: set_event_outcome_unknown + field: event.outcome + value: unknown + description: Sets `event.outcome` to `unknown` while a Backstage audit operation is initiated. + if: 'ctx.json?.status == "initiated"' + - set: + tag: set_event_outcome_success + field: event.outcome + value: success + description: Sets `event.outcome` to `success` for succeeded Backstage audit operations. + if: 'ctx.json?.status == "succeeded"' + - set: + tag: set_event_outcome_failure + field: event.outcome + value: failure + description: Sets `event.outcome` to `failure` for failed Backstage audit operations. + if: 'ctx.json?.status == "failed"' - date: field: json.timestamp target_field: "@timestamp" @@ -50,11 +93,13 @@ processors: - rename: field: json.message target_field: message + ignore_missing: true tag: rename_json_message description: Promotes the audit `json.message` to the top-level `message` field. - rename: field: json.severityLevel target_field: backstage.severity_level + ignore_missing: true tag: rename_audit_severity_level description: Sets the custom vendor field `backstage.severity_level` that defines the audit log's severity level. - set: @@ -75,23 +120,18 @@ processors: value: 7 description: Sets `event.severity` to 7 when the audit log's severity level is `high`. if: 'ctx.backstage?.severity_level == "high"' - # TODO: expand event.type mapping based on all documented values of event message and status - set: - tag: set_event_type_b24b0c9e - field: event.type - value: "access" - description: Sets `event.type` to `access` when a catalog entity fetch is initiated. - if: 'ctx.json.status == "initiated" && ctx.json.message == "catalog.entity-fetch"' - - set: - tag: set_event_action_8e4cef53 + tag: set_event_action field: event.action - value: "{{{message}}}:{{{json.status}}}" - description: Builds `event.action` by combining the audit message and status as `message:status`. + value: "{{{json.eventId}}}:{{{json.status}}}" + description: Builds `event.action` from the stable Backstage event ID and status. + if: ctx.json?.eventId != null && ctx.json?.status != null - rename: field: json.plugin - target_field: event.module - tag: rename_json_plugin_to_event_module - description: Maps the Backstage plugin where the event originated from to the ECS `event.module` field. + target_field: event.provider + ignore_missing: true + tag: rename_json_plugin_to_event_provider + description: Maps the Backstage plugin where the event originated from to the ECS `event.provider` field. - rename: field: json.eventId target_field: event.code @@ -122,6 +162,12 @@ processors: tag: decode_request_uri description: Decode the path for the requested URL against backstage if: ctx._tmp?.request_url != null + - set: + field: url.original + copy_from: json.request.url + tag: set_url_original_from_source + description: Restores `url.original` to the unmodified path-only URL observed in the Backstage audit event. + if: ctx.json?.request?.url != null - remove: field: [_tmp.request_url, url.scheme, url.domain] ignore_missing: true @@ -163,11 +209,17 @@ processors: ignore_empty_values: true description: Append the hostname of the actor who initiated the request to backstage to the related hostnames array tag: append_request_hostname_ecs_related + - rename: + field: json.actor.ip + target_field: source.ip + ignore_missing: true + tag: rename_actor_ip_to_source_ip + description: Maps the IP address of the actor that made the audited API call to the ECS `source.ip` field. - append: field: related.ip - copy_from: json.actor.ip + copy_from: source.ip ignore_empty_values: true - description: Appends the IP address of the actor who initiated the request to the `related.ip` array. + description: Appends the actor's IP address to the `related.ip` array for cross-field correlation. tag: append_related_ip_8c67e2b0 - foreach: field: json.meta.query.filter @@ -210,11 +262,6 @@ processors: ignore_missing: true tag: remove_json_timestamp description: Removes `json.timestamp` after it has been parsed into `@timestamp`. - - remove: - field: json.user_agent - ignore_missing: true - tag: remove_json_useragent - description: Removes `json.user_agent` after it has been mapped to `user_agent.original`. - remove: field: json.isAuditEvent ignore_missing: true @@ -235,22 +282,12 @@ processors: ignore_missing: true tag: remove_json_actor_hostname description: Removes `json.actor.hostname` after it has been appended to `related.hosts`. - - remove: - field: json.actor.ip - ignore_missing: true - tag: remove_json_actor_ip_46c2e907 - description: Removes `json.actor.ip` after it has been appended to `related.ip`. - remove: field: json.actor ignore_missing: true tag: remove_json_empty_actor description: Removes the `json.actor` container once all actor fields have been extracted and it is empty. if: ctx.json?.actor?.size() == 0 - - remove: - field: json.message - ignore_missing: true - tag: remove_json_message - description: Removes `json.message` after it has been promoted to the top-level `message` field. - remove: field: json.service ignore_missing: true @@ -282,20 +319,31 @@ processors: tag: remove_empty_json_meta description: Removes the now-empty `json.meta` container after all its fields have been moved to `backstage.meta`. - remove: - field: event.original + field: _tmp + ignore_missing: true + tag: remove_empty_tmp + description: Removes the empty temporary pipeline container. + if: ctx._tmp != null && ctx._tmp.isEmpty() + - remove: + field: json ignore_missing: true - if: ctx.tags == null || !(ctx.tags.contains('preserve_original_event')) - tag: remove_json_original_event_ss87a8429d - description: Removes the raw JSON string stored in `event.original` to reduce document size. Skipped when the `preserve_original_event` tag is set. + tag: remove_empty_json + description: Removes the empty parsed JSON container. + if: ctx.json != null && ctx.json.isEmpty() on_failure: - - set: + - append: + tag: append_pipeline_error_message field: error.message value: >- - Processor '{{{ _ingest.on_failure_processor_type }}}' - with tag '{{{ _ingest.on_failure_processor_tag }}}' - in pipeline '{{{ _ingest.pipeline }}}' - failed with message '{{{ _ingest.on_failure_message }}}' + Pipeline '{{{ _ingest.pipeline }}}' failed at processor '{{{ _ingest.on_failure_processor_type }}}' + {{{#_ingest.on_failure_processor_tag}}}with tag '{{{ _ingest.on_failure_processor_tag }}}' + {{{/_ingest.on_failure_processor_tag}}}failed with message '{{{ _ingest.on_failure_message }}}' - set: field: event.kind tag: set_pipeline_error_to_event_kind value: pipeline_error + - append: + tag: append_preserve_original_event_on_failure + field: tags + value: preserve_original_event + allow_duplicates: false diff --git a/packages/backstage/data_stream/audit_logs/fields/agent.yml b/packages/backstage/data_stream/audit_logs/fields/agent.yml index 0cd08ad062a..4c7426c9e8a 100644 --- a/packages/backstage/data_stream/audit_logs/fields/agent.yml +++ b/packages/backstage/data_stream/audit_logs/fields/agent.yml @@ -28,18 +28,4 @@ - name: os.codename type: keyword example: "stretch" - description: > - OS codename, if any. - -- name: log.offset - type: long - description: Log offset -- name: log.file.device_id - type: keyword - description: Device Id of the log file this event came from. -- name: log.file.inode - type: keyword - description: Inode number of the log file. -- name: log.file.path - type: keyword - description: Path to the log file. + description: OS codename, if any. diff --git a/packages/backstage/data_stream/audit_logs/fields/backstage.yml b/packages/backstage/data_stream/audit_logs/fields/backstage.yml index 1f014627b87..223cad2773f 100644 --- a/packages/backstage/data_stream/audit_logs/fields/backstage.yml +++ b/packages/backstage/data_stream/audit_logs/fields/backstage.yml @@ -1,28 +1,23 @@ - name: backstage type: group fields: - - name: severity_level - type: keyword - description: The severity level of the audit log registered by Backstage - - name: plugin - type: keyword - description: The plugin in backstage related to the audit log - - name: meta - type: flattened - description: >- - Event-specific metadata emitted by the Backstage plugin. Shape varies per - event type; stored as `flattened` to preserve arbitrary keys without mapping - explosion. Well-known catalog-query keys are promoted to `backstage.audit.query.*`. - - name: query - type: group - fields: - - name: fields - type: keyword - description: The entity fields requested by the catalog query. - - name: type - type: keyword - description: The type of catalog query that was issued (e.g. "all"). - - name: filter - type: flattened - description: >- - The catalog query filter conditions as key/value pairs. Declared `flattened` to store arbitrary filter keys (e.g. kind, relations.hasMember) without mapping explosion. + - name: severity_level + type: keyword + description: The severity level of the audit log registered by Backstage. + - name: meta + type: flattened + description: >- + Event-specific metadata emitted by the Backstage plugin. Shape varies per event type; stored as `flattened` to preserve arbitrary keys without mapping explosion. Well-known catalog-query keys are promoted to `backstage.query.*`. + - name: query + type: group + fields: + - name: fields + type: keyword + description: The entity fields requested by the catalog query. + - name: type + type: keyword + description: The type of catalog query that was issued (e.g. "all"). + - name: filter + type: flattened + description: >- + The catalog query filter conditions as key/value pairs. diff --git a/packages/backstage/data_stream/audit_logs/fields/base-fields.yml b/packages/backstage/data_stream/audit_logs/fields/base-fields.yml index 9a67151e631..cf9ce6f9009 100644 --- a/packages/backstage/data_stream/audit_logs/fields/base-fields.yml +++ b/packages/backstage/data_stream/audit_logs/fields/base-fields.yml @@ -1,15 +1,16 @@ - name: data_stream.type - type: constant_keyword - description: Data stream type. + external: ecs - name: data_stream.dataset - type: constant_keyword - description: Data stream dataset. + external: ecs - name: data_stream.namespace + external: ecs +- name: event.module + external: ecs + type: constant_keyword + value: backstage +- name: event.dataset + external: ecs type: constant_keyword - description: Data stream namespace. + value: backstage.audit_logs - name: "@timestamp" - type: date - description: Event timestamp. -- name: input.type - type: keyword - description: Input type + external: ecs diff --git a/packages/backstage/data_stream/audit_logs/fields/beats.yml b/packages/backstage/data_stream/audit_logs/fields/beats.yml new file mode 100644 index 00000000000..df38c3d612d --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/fields/beats.yml @@ -0,0 +1,15 @@ +- name: input.type + type: keyword + description: Input type. +- name: log.offset + type: long + description: Log offset. +- name: log.file.device_id + type: keyword + description: Device ID of the log file this event came from. +- name: log.file.inode + type: keyword + description: Inode number of the log file. +- name: log.file.path + type: keyword + description: Path to the log file. diff --git a/packages/backstage/data_stream/audit_logs/fields/ecs.yml b/packages/backstage/data_stream/audit_logs/fields/ecs.yml new file mode 100644 index 00000000000..dce51f828d5 --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/fields/ecs.yml @@ -0,0 +1,38 @@ +- name: event.kind + external: ecs +- name: event.category + external: ecs +- name: event.type + external: ecs +- name: event.outcome + external: ecs +- name: event.severity + external: ecs +- name: event.action + external: ecs +- name: event.provider + external: ecs +- name: event.code + external: ecs +- name: log.level + external: ecs +- name: url.original + external: ecs +- name: url.path + external: ecs +- name: url.query + external: ecs +- name: user_agent.original + external: ecs +- name: user.id + external: ecs +- name: related.user + external: ecs +- name: related.hosts + external: ecs +- name: related.ip + external: ecs +- name: http.request.method + external: ecs +- name: source.ip + external: ecs From 83a3bfa756bb527ca779d8701f51a08306535f86 Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Sun, 5 Jul 2026 23:43:09 -0400 Subject: [PATCH 09/11] Fix filestream input template and default log path, add missing input vars - Fix a stray leading space before prospector.scanner.symlinks that broke the rendered YAML for every deployment (follow.symlinked_files defaults to true, so this key was emitted by default). - Correct the paths variable's default from /var/logs/*.log (plural) to /var/log/*.log, matching standard convention and this package's own docker-based system test deployment. - Add exclude_files, parsers, and follow.symlinked_files stream vars so the template's corresponding Handlebars blocks are backed by real, documented manifest variables. --- .../agent/stream/filestream.yml.hbs | 21 ++++++++---- .../data_stream/audit_logs/manifest.yml | 32 +++++++++++++++++-- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs b/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs index ef30e327ace..bf1c7eb9317 100644 --- a/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs +++ b/packages/backstage/data_stream/audit_logs/agent/stream/filestream.yml.hbs @@ -8,12 +8,9 @@ prospector.scanner.exclude_files: - {{pattern}} {{/each}} {{/if}} -{{#if multiline_json}} -multiline.pattern: '^{' -multiline.negate: true -multiline.match: after -multiline.max_lines: 5000 -multiline.timeout: 10 +{{#if parsers}} +parsers: +{{parsers}} {{/if}} {{#if custom}} {{custom}} @@ -42,3 +39,15 @@ publisher_pipeline.disable_host: true processors: {{processors}} {{/if}} + +{{#if harvester_limit}} +harvester_limit: {{harvester_limit}} +{{/if}} + +{{#if close.on_state_change.inactive}} +close.on_state_change.inactive: {{close.on_state_change.inactive}} +{{/if}} + +{{#if follow.symlinked_files}} +prospector.scanner.symlinks: {{follow.symlinked_files}} +{{/if}} diff --git a/packages/backstage/data_stream/audit_logs/manifest.yml b/packages/backstage/data_stream/audit_logs/manifest.yml index c0b71ba3031..2a75bd73bd1 100644 --- a/packages/backstage/data_stream/audit_logs/manifest.yml +++ b/packages/backstage/data_stream/audit_logs/manifest.yml @@ -14,8 +14,18 @@ streams: required: true show_user: true default: - - /var/logs/*.log + - /var/log/*.log description: Backstage logs + - name: exclude_files + type: text + title: Exclude files + description: >- + Regex patterns for file paths to exclude from collection. Example: `\.gz$` excludes compressed rotated logs. + multi: true + required: false + show_user: false + default: + - '\.gz$' - name: preserve_original_event required: true show_user: true @@ -39,6 +49,17 @@ streams: required: false show_user: false description: Processors are used to reduce the number of fields in the exported event or to enhance the event with metadata. This executes in the agent before the logs are parsed. See [Processors](https://www.elastic.co/guide/en/fleet/current/elastic-agent-processor-configuration.html) for details. + - name: parsers + type: yaml + title: Parsers + description: | + Optional filestream parser configuration. Leave empty for standard Backstage JSONL audit logs; + the ingest pipeline parses each raw line from message/event.original. + Use multiline only when JSON objects are pretty-printed across multiple lines. See + https://www.elastic.co/guide/en/beats/filebeat/current/filebeat-input-filestream.html#_parsers + required: false + show_user: true + multi: false - name: harvester_limit type: integer title: Harvester Limit @@ -54,6 +75,13 @@ streams: required: false show_user: false default: 5m + - name: follow.symlinked_files + type: bool + description: Set to false so the agent silently skips symlinked files. Defaults to true. + multi: false + required: false + show_user: true + default: true - name: custom type: yaml title: Additional Filestream Configuration Options @@ -73,7 +101,6 @@ streams: #prospector.scanner.fingerprint.length: 1024 #ignore_older: 0 #ignore_inactive: '' - #close.on_state_change.inactive: 5m #close.on_state_change.renamed: false #close.on_state_change.removed: false #close.reader.on_eof: false @@ -92,7 +119,6 @@ streams: #exclude_lines: ['^DBG'] #buffer_size: 16384 #message_max_bytes: 1048576 - # TODO Check if we have more filestream specific variables to add in, double check with filestream docs elasticsearch: index_template: mappings: From 237d67cf2cce00809d650fda80bb83057555adad Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Sun, 5 Jul 2026 23:44:11 -0400 Subject: [PATCH 10/11] Anonymize pipeline test fixture and add common test config Replace real-looking hostnames, internal IPs, and identifiers in the pipeline test log with RFC 5737 documentation IPs and example.com/ example.internal placeholders. Add test-common-config.yml so every pipeline test fixture exercises the preserve_original_event tag, matching the retention behavior configured in the pipeline. Regenerate the expected pipeline output to match the current pipeline and anonymized input. --- .../_dev/test/pipeline/test-audit.log | 20 +- .../pipeline/test-audit.log-expected.json | 358 +++++++++++++----- .../_dev/test/pipeline/test-common-config.yml | 3 + 3 files changed, 268 insertions(+), 113 deletions(-) create mode 100644 packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-common-config.yml diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log index be83aa91dce..3e97853a404 100644 --- a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log +++ b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log @@ -1,11 +1,11 @@ {"actor":{"hostname":"localhost","ip":"127.0.0.1","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"fields":["metadata","kind","spec.profile"],"filter":["kind=group,relations.hasMember=user:default/guest"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest"},"service":"backstage","severityLevel":"low","status":"initiated","timestamp":"2026-05-22T20:30:05.131Z"} -{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.35.249.216","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0"},"eventId":"entity-facets","isAuditEvent":true,"level":"info","message":"catalog.entity-facets","plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entity-facets?facet=metadata.namespace&filter=kind%3Dcomponent"},"service":"backstage","severityLevel":"low","status":"initiated"} -{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.248.50.223","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0"},"eventId":"entity-facets","isAuditEvent":true,"level":"info","message":"catalog.entity-facets","plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entity-facets?facet=metadata.tags&filter=kind%3Dcomponent"},"service":"backstage","severityLevel":"low","status":"succeeded"} -{"actor":{"hostname":"backstage.backstage.svc.cluster.local","actorId":"backstage-proxy","ip":"::ffff:10.188.154.218","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"filter":["kind=Resource,spec.type=github-repository,spec.implementation.apiVersion=github.***.dev/v1"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities/?filter=kind%3DResource%2Cspec.type%3Dgithub-repository%2Cspec.implementation.apiVersion%3Dgithub.***.dev%2Fv1"},"service":"backstage","severityLevel":"low","status":"initiated"} -{"actor":{"hostname":"backstage.backstage.svc.cluster.local","actorId":"backstage-proxy","ip":"::ffff:10.159.165.31","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"filter":["kind=NothingOfThisKind"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities?filter=kind%3DNothingOfThisKind"},"service":"backstage","severityLevel":"low","status":"succeeded"} -{"actor":{"hostname":"backstage.backstage.svc.cluster.local","actorId":"backstage-proxy","ip":"::ffff:10.158.110.116","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch No entity named 'octocat' found, with kind 'user' in namespace 'default'","meta":{"entityRef":"user:default/octocat","queryType":"by-name"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities/by-name/user/default/octocat?"},"service":"backstage","severityLevel":"low","status":"failed"} -{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.35.249.216","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task","meta":{"actionType":"stream","taskId":"b1d1549e-4a46-4159-a1af-293cc89eecae"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream"},"service":"backstage","severityLevel":"low","status":"initiated"} -{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.35.249.216","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task","meta":{"actionType":"stream","taskId":"b1d1549e-4a46-4159-a1af-293cc89eecae"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream"},"service":"backstage","severityLevel":"low","status":"succeeded"} -{"actor":{"actorId":"plugin:scaffolder"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task Could not find main. {data={\"what\":\"main\"}}","meta":{"actionType":"execution","templateRef":"template:default/register-in-catalog","createdBy":"user:default/octocat","taskParameters":{"owner":"group:default/platform-engineering","securityLevel":"high","createBoilerplateInGuestRepo":false,"division":"engineering","org":"platform","name":"ansible-***-cloud-enterprise","mainBranch":"main","githubOrg":"***"},"taskId":"3a03bcc9-48b3-4ecf-98c4-2a98caf9be85"},"plugin":"scaffolder","service":"backstage","severityLevel":"medium","status":"failed"} -{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.134.20.225","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"template-parameter-schema","isAuditEvent":true,"level":"info","message":"scaffolder.template-parameter-schema","meta":{"templateRef":"template:default/starter-kit"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema"},"service":"backstage","severityLevel":"low","status":"initiated"} -{"actor":{"hostname":"backstage.***.dev","actorId":"user:default/octocat","ip":"::ffff:10.134.20.225","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"template-parameter-schema","isAuditEvent":true,"level":"info","message":"scaffolder.template-parameter-schema","meta":{"templateRef":"Template:default/starter-kit"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema"},"service":"backstage","severityLevel":"low","status":"succeeded"} +{"actor":{"hostname":"backstage.example.internal","actorId":"user:default/alice.johnson","ip":"::ffff:198.51.100.10","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0"},"eventId":"entity-facets","isAuditEvent":true,"level":"info","message":"catalog.entity-facets","plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entity-facets?facet=metadata.namespace&filter=kind%3Dcomponent"},"service":"backstage","severityLevel":"low","status":"initiated"} +{"actor":{"hostname":"backstage.example.internal","actorId":"user:default/alice.johnson","ip":"::ffff:198.51.100.11","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0"},"eventId":"entity-facets","isAuditEvent":true,"level":"info","message":"catalog.entity-facets","plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entity-facets?facet=metadata.tags&filter=kind%3Dcomponent"},"service":"backstage","severityLevel":"low","status":"succeeded"} +{"actor":{"hostname":"backstage.example.internal","actorId":"backstage-proxy","ip":"::ffff:198.51.100.20","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"filter":["kind=Resource,spec.type=github-repository,spec.implementation.apiVersion=github.example.com/v1"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities/?filter=kind%3DResource%2Cspec.type%3Dgithub-repository%2Cspec.implementation.apiVersion%3Dgithub.example.com%2Fv1"},"service":"backstage","severityLevel":"low","status":"initiated"} +{"actor":{"hostname":"backstage.example.internal","actorId":"backstage-proxy","ip":"::ffff:198.51.100.21","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch","meta":{"query":{"filter":["kind=NothingOfThisKind"]},"queryType":"all"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities?filter=kind%3DNothingOfThisKind"},"service":"backstage","severityLevel":"low","status":"succeeded"} +{"actor":{"hostname":"backstage.example.internal","actorId":"backstage-proxy","ip":"::ffff:198.51.100.22","userAgent":"api-service"},"eventId":"entity-fetch","isAuditEvent":true,"level":"info","message":"catalog.entity-fetch No entity named 'alice.johnson' found, with kind 'user' in namespace 'default'","meta":{"entityRef":"user:default/alice.johnson","queryType":"by-name"},"plugin":"catalog","request":{"method":"GET","url":"/api/catalog/entities/by-name/user/default/alice.johnson?"},"service":"backstage","severityLevel":"low","status":"failed"} +{"actor":{"hostname":"backstage.example.internal","actorId":"user:default/alice.johnson","ip":"::ffff:198.51.100.10","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task","meta":{"actionType":"stream","taskId":"b1d1549e-4a46-4159-a1af-293cc89eecae"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream"},"service":"backstage","severityLevel":"low","status":"initiated"} +{"actor":{"hostname":"backstage.example.internal","actorId":"user:default/alice.johnson","ip":"::ffff:198.51.100.10","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task","meta":{"actionType":"stream","taskId":"b1d1549e-4a46-4159-a1af-293cc89eecae"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream"},"service":"backstage","severityLevel":"low","status":"succeeded"} +{"actor":{"actorId":"plugin:scaffolder"},"eventId":"task","isAuditEvent":true,"level":"info","message":"scaffolder.task Could not find main. {data={\"what\":\"main\"}}","meta":{"actionType":"execution","templateRef":"template:default/register-in-catalog","createdBy":"user:default/alice.johnson","taskParameters":{"owner":"group:default/example-platform-engineering","securityLevel":"high","createBoilerplateInGuestRepo":false,"division":"engineering","org":"platform","name":"ansible-example-cloud-enterprise","mainBranch":"main","githubOrg":"example-org"},"taskId":"3a03bcc9-48b3-4ecf-98c4-2a98caf9be85"},"plugin":"scaffolder","service":"backstage","severityLevel":"medium","status":"failed"} +{"actor":{"hostname":"backstage.example.internal","actorId":"user:default/alice.johnson","ip":"::ffff:198.51.100.12","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"template-parameter-schema","isAuditEvent":true,"level":"info","message":"scaffolder.template-parameter-schema","meta":{"templateRef":"template:default/starter-kit"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema"},"service":"backstage","severityLevel":"low","status":"initiated"} +{"actor":{"hostname":"backstage.example.internal","actorId":"user:default/alice.johnson","ip":"::ffff:198.51.100.12","userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},"eventId":"template-parameter-schema","isAuditEvent":true,"level":"info","message":"scaffolder.template-parameter-schema","meta":{"templateRef":"Template:default/starter-kit"},"plugin":"scaffolder","request":{"method":"GET","url":"/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema"},"service":"backstage","severityLevel":"low","status":"succeeded"} diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log-expected.json b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log-expected.json index 81fc071ffb7..08303f7bd74 100644 --- a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log-expected.json +++ b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-audit.log-expected.json @@ -2,7 +2,6 @@ "expected": [ { "@timestamp": "2026-05-22T20:30:05.131Z", - "_tmp": {}, "backstage": { "query": { "fields": [ @@ -24,17 +23,26 @@ "version": "9.4.0" }, "event": { - "action": "catalog.entity-fetch:initiated", + "action": "entity-fetch:initiated", + "category": [ + "api" + ], "code": "entity-fetch", - "module": "catalog", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"localhost\",\"ip\":\"127.0.0.1\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch\",\"meta\":{\"query\":{\"fields\":[\"metadata\",\"kind\",\"spec.profile\"],\"filter\":[\"kind=group,relations.hasMember=user:default/guest\"]},\"queryType\":\"all\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\",\"timestamp\":\"2026-05-22T20:30:05.131Z\"}", + "outcome": "unknown", + "provider": "catalog", + "severity": 3, + "type": [ + "access", + "start" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, @@ -47,8 +55,14 @@ "127.0.0.1" ] }, + "source": { + "ip": "127.0.0.1" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", + "original": "/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", "path": "/api/catalog/entities", "query": "fields=metadata,kind,spec.profile&filter=kind=group,relations.hasMember=user:default/guest" }, @@ -67,7 +81,6 @@ } }, { - "_tmp": {}, "backstage": { "severity_level": "low" }, @@ -75,39 +88,54 @@ "version": "9.4.0" }, "event": { - "action": "catalog.entity-facets:initiated", + "action": "entity-facets:initiated", + "category": [ + "api" + ], "code": "entity-facets", - "module": "catalog", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"user:default/alice.johnson\",\"ip\":\"::ffff:198.51.100.10\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0\"},\"eventId\":\"entity-facets\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-facets\",\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entity-facets?facet=metadata.namespace&filter=kind%3Dcomponent\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\"}", + "outcome": "unknown", + "provider": "catalog", + "severity": 3, + "type": [ + "access", + "start" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, "message": "catalog.entity-facets", "related": { "hosts": [ - "backstage.***.dev" + "backstage.example.internal" ], "ip": [ - "::ffff:10.35.249.216" + "::ffff:198.51.100.10" ], "user": [ - "user:default/octocat" + "user:default/alice.johnson" ] }, + "source": { + "ip": "::ffff:198.51.100.10" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/catalog/entity-facets?facet=metadata.namespace&filter=kind%3Dcomponent", + "original": "/api/catalog/entity-facets?facet=metadata.namespace&filter=kind%3Dcomponent", "path": "/api/catalog/entity-facets", "query": "facet=metadata.namespace&filter=kind=component" }, "user": { - "id": "user:default/octocat" + "id": "user:default/alice.johnson" }, "user_agent": { "device": { @@ -124,7 +152,6 @@ } }, { - "_tmp": {}, "backstage": { "severity_level": "low" }, @@ -132,39 +159,54 @@ "version": "9.4.0" }, "event": { - "action": "catalog.entity-facets:succeeded", + "action": "entity-facets:succeeded", + "category": [ + "api" + ], "code": "entity-facets", - "module": "catalog", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"user:default/alice.johnson\",\"ip\":\"::ffff:198.51.100.11\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:152.0) Gecko/20100101 Firefox/152.0\"},\"eventId\":\"entity-facets\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-facets\",\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entity-facets?facet=metadata.tags&filter=kind%3Dcomponent\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"succeeded\"}", + "outcome": "success", + "provider": "catalog", + "severity": 3, + "type": [ + "access", + "end" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, "message": "catalog.entity-facets", "related": { "hosts": [ - "backstage.***.dev" + "backstage.example.internal" ], "ip": [ - "::ffff:10.248.50.223" + "::ffff:198.51.100.11" ], "user": [ - "user:default/octocat" + "user:default/alice.johnson" ] }, + "source": { + "ip": "::ffff:198.51.100.11" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/catalog/entity-facets?facet=metadata.tags&filter=kind%3Dcomponent", + "original": "/api/catalog/entity-facets?facet=metadata.tags&filter=kind%3Dcomponent", "path": "/api/catalog/entity-facets", "query": "facet=metadata.tags&filter=kind=component" }, "user": { - "id": "user:default/octocat" + "id": "user:default/alice.johnson" }, "user_agent": { "device": { @@ -181,14 +223,13 @@ } }, { - "_tmp": {}, "backstage": { "query": { "filter": { "kind": "Resource", "spec": { "implementation": { - "apiVersion": "github.***.dev/v1" + "apiVersion": "github.example.com/v1" }, "type": "github-repository" } @@ -201,36 +242,51 @@ "version": "9.4.0" }, "event": { - "action": "catalog.entity-fetch:initiated", + "action": "entity-fetch:initiated", + "category": [ + "api" + ], "code": "entity-fetch", - "module": "catalog", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"backstage-proxy\",\"ip\":\"::ffff:198.51.100.20\",\"userAgent\":\"api-service\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch\",\"meta\":{\"query\":{\"filter\":[\"kind=Resource,spec.type=github-repository,spec.implementation.apiVersion=github.example.com/v1\"]},\"queryType\":\"all\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities/?filter=kind%3DResource%2Cspec.type%3Dgithub-repository%2Cspec.implementation.apiVersion%3Dgithub.example.com%2Fv1\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\"}", + "outcome": "unknown", + "provider": "catalog", + "severity": 3, + "type": [ + "access", + "start" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, "message": "catalog.entity-fetch", "related": { "hosts": [ - "backstage.backstage.svc.cluster.local" + "backstage.example.internal" ], "ip": [ - "::ffff:10.188.154.218" + "::ffff:198.51.100.20" ], "user": [ "backstage-proxy" ] }, + "source": { + "ip": "::ffff:198.51.100.20" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/catalog/entities/?filter=kind%3DResource%2Cspec.type%3Dgithub-repository%2Cspec.implementation.apiVersion%3Dgithub.***.dev%2Fv1", + "original": "/api/catalog/entities/?filter=kind%3DResource%2Cspec.type%3Dgithub-repository%2Cspec.implementation.apiVersion%3Dgithub.example.com%2Fv1", "path": "/api/catalog/entities/", - "query": "filter=kind=Resource,spec.type=github-repository,spec.implementation.apiVersion=github.***.dev/v1" + "query": "filter=kind=Resource,spec.type=github-repository,spec.implementation.apiVersion=github.example.com/v1" }, "user": { "id": "backstage-proxy" @@ -244,7 +300,6 @@ } }, { - "_tmp": {}, "backstage": { "query": { "filter": { @@ -258,34 +313,49 @@ "version": "9.4.0" }, "event": { - "action": "catalog.entity-fetch:succeeded", + "action": "entity-fetch:succeeded", + "category": [ + "api" + ], "code": "entity-fetch", - "module": "catalog", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"backstage-proxy\",\"ip\":\"::ffff:198.51.100.21\",\"userAgent\":\"api-service\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch\",\"meta\":{\"query\":{\"filter\":[\"kind=NothingOfThisKind\"]},\"queryType\":\"all\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities?filter=kind%3DNothingOfThisKind\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"succeeded\"}", + "outcome": "success", + "provider": "catalog", + "severity": 3, + "type": [ + "access", + "end" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, "message": "catalog.entity-fetch", "related": { "hosts": [ - "backstage.backstage.svc.cluster.local" + "backstage.example.internal" ], "ip": [ - "::ffff:10.159.165.31" + "::ffff:198.51.100.21" ], "user": [ "backstage-proxy" ] }, + "source": { + "ip": "::ffff:198.51.100.21" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/catalog/entities?filter=kind%3DNothingOfThisKind", + "original": "/api/catalog/entities?filter=kind%3DNothingOfThisKind", "path": "/api/catalog/entities", "query": "filter=kind=NothingOfThisKind" }, @@ -301,10 +371,9 @@ } }, { - "_tmp": {}, "backstage": { "meta": { - "entityRef": "user:default/octocat" + "entityRef": "user:default/alice.johnson" }, "query": { "type": "by-name" @@ -315,35 +384,51 @@ "version": "9.4.0" }, "event": { - "action": "catalog.entity-fetch No entity named 'octocat' found, with kind 'user' in namespace 'default':failed", + "action": "entity-fetch:failed", + "category": [ + "api" + ], "code": "entity-fetch", - "module": "catalog", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"backstage-proxy\",\"ip\":\"::ffff:198.51.100.22\",\"userAgent\":\"api-service\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch No entity named 'alice.johnson' found, with kind 'user' in namespace 'default'\",\"meta\":{\"entityRef\":\"user:default/alice.johnson\",\"queryType\":\"by-name\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities/by-name/user/default/alice.johnson?\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"failed\"}", + "outcome": "failure", + "provider": "catalog", + "severity": 3, + "type": [ + "access", + "end" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, - "message": "catalog.entity-fetch No entity named 'octocat' found, with kind 'user' in namespace 'default'", + "message": "catalog.entity-fetch No entity named 'alice.johnson' found, with kind 'user' in namespace 'default'", "related": { "hosts": [ - "backstage.backstage.svc.cluster.local" + "backstage.example.internal" ], "ip": [ - "::ffff:10.158.110.116" + "::ffff:198.51.100.22" ], "user": [ "backstage-proxy" ] }, + "source": { + "ip": "::ffff:198.51.100.22" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/catalog/entities/by-name/user/default/octocat?", - "path": "/api/catalog/entities/by-name/user/default/octocat", + "extension": "johnson", + "original": "/api/catalog/entities/by-name/user/default/alice.johnson?", + "path": "/api/catalog/entities/by-name/user/default/alice.johnson", "query": "" }, "user": { @@ -358,7 +443,6 @@ } }, { - "_tmp": {}, "backstage": { "meta": { "actionType": "stream", @@ -370,38 +454,53 @@ "version": "9.4.0" }, "event": { - "action": "scaffolder.task:initiated", + "action": "task:initiated", + "category": [ + "api" + ], "code": "task", - "module": "scaffolder", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"user:default/alice.johnson\",\"ip\":\"::ffff:198.51.100.10\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36\"},\"eventId\":\"task\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"scaffolder.task\",\"meta\":{\"actionType\":\"stream\",\"taskId\":\"b1d1549e-4a46-4159-a1af-293cc89eecae\"},\"plugin\":\"scaffolder\",\"request\":{\"method\":\"GET\",\"url\":\"/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\"}", + "outcome": "unknown", + "provider": "scaffolder", + "severity": 3, + "type": [ + "access", + "start" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, "message": "scaffolder.task", "related": { "hosts": [ - "backstage.***.dev" + "backstage.example.internal" ], "ip": [ - "::ffff:10.35.249.216" + "::ffff:198.51.100.10" ], "user": [ - "user:default/octocat" + "user:default/alice.johnson" ] }, + "source": { + "ip": "::ffff:198.51.100.10" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream", + "original": "/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream", "path": "/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream" }, "user": { - "id": "user:default/octocat" + "id": "user:default/alice.johnson" }, "user_agent": { "device": { @@ -418,7 +517,6 @@ } }, { - "_tmp": {}, "backstage": { "meta": { "actionType": "stream", @@ -430,38 +528,53 @@ "version": "9.4.0" }, "event": { - "action": "scaffolder.task:succeeded", + "action": "task:succeeded", + "category": [ + "api" + ], "code": "task", - "module": "scaffolder", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"user:default/alice.johnson\",\"ip\":\"::ffff:198.51.100.10\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36\"},\"eventId\":\"task\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"scaffolder.task\",\"meta\":{\"actionType\":\"stream\",\"taskId\":\"b1d1549e-4a46-4159-a1af-293cc89eecae\"},\"plugin\":\"scaffolder\",\"request\":{\"method\":\"GET\",\"url\":\"/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"succeeded\"}", + "outcome": "success", + "provider": "scaffolder", + "severity": 3, + "type": [ + "access", + "end" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, "message": "scaffolder.task", "related": { "hosts": [ - "backstage.***.dev" + "backstage.example.internal" ], "ip": [ - "::ffff:10.35.249.216" + "::ffff:198.51.100.10" ], "user": [ - "user:default/octocat" + "user:default/alice.johnson" ] }, + "source": { + "ip": "::ffff:198.51.100.10" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream", + "original": "/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream", "path": "/api/scaffolder/v2/tasks/b1d1549e-4a46-4159-a1af-293cc89eecae/eventstream" }, "user": { - "id": "user:default/octocat" + "id": "user:default/alice.johnson" }, "user_agent": { "device": { @@ -481,16 +594,16 @@ "backstage": { "meta": { "actionType": "execution", - "createdBy": "user:default/octocat", + "createdBy": "user:default/alice.johnson", "taskId": "3a03bcc9-48b3-4ecf-98c4-2a98caf9be85", "taskParameters": { "createBoilerplateInGuestRepo": false, "division": "engineering", - "githubOrg": "***", + "githubOrg": "example-org", "mainBranch": "main", - "name": "ansible-***-cloud-enterprise", + "name": "ansible-example-cloud-enterprise", "org": "platform", - "owner": "group:default/platform-engineering", + "owner": "group:default/example-platform-engineering", "securityLevel": "high" }, "templateRef": "template:default/register-in-catalog" @@ -501,12 +614,20 @@ "version": "9.4.0" }, "event": { - "action": "scaffolder.task Could not find main. {data={\"what\":\"main\"}}:failed", + "action": "task:failed", + "category": [ + "api" + ], "code": "task", - "module": "scaffolder", - "severity": 5 + "kind": "event", + "original": "{\"actor\":{\"actorId\":\"plugin:scaffolder\"},\"eventId\":\"task\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"scaffolder.task Could not find main. {data={\\\"what\\\":\\\"main\\\"}}\",\"meta\":{\"actionType\":\"execution\",\"templateRef\":\"template:default/register-in-catalog\",\"createdBy\":\"user:default/alice.johnson\",\"taskParameters\":{\"owner\":\"group:default/example-platform-engineering\",\"securityLevel\":\"high\",\"createBoilerplateInGuestRepo\":false,\"division\":\"engineering\",\"org\":\"platform\",\"name\":\"ansible-example-cloud-enterprise\",\"mainBranch\":\"main\",\"githubOrg\":\"example-org\"},\"taskId\":\"3a03bcc9-48b3-4ecf-98c4-2a98caf9be85\"},\"plugin\":\"scaffolder\",\"service\":\"backstage\",\"severityLevel\":\"medium\",\"status\":\"failed\"}", + "outcome": "failure", + "provider": "scaffolder", + "severity": 5, + "type": [ + "end" + ] }, - "json": {}, "log": { "level": "info" }, @@ -516,12 +637,14 @@ "plugin:scaffolder" ] }, + "tags": [ + "preserve_original_event" + ], "user": { "id": "plugin:scaffolder" } }, { - "_tmp": {}, "backstage": { "meta": { "templateRef": "template:default/starter-kit" @@ -532,38 +655,53 @@ "version": "9.4.0" }, "event": { - "action": "scaffolder.template-parameter-schema:initiated", + "action": "template-parameter-schema:initiated", + "category": [ + "api" + ], "code": "template-parameter-schema", - "module": "scaffolder", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"user:default/alice.johnson\",\"ip\":\"::ffff:198.51.100.12\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36\"},\"eventId\":\"template-parameter-schema\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"scaffolder.template-parameter-schema\",\"meta\":{\"templateRef\":\"template:default/starter-kit\"},\"plugin\":\"scaffolder\",\"request\":{\"method\":\"GET\",\"url\":\"/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\"}", + "outcome": "unknown", + "provider": "scaffolder", + "severity": 3, + "type": [ + "access", + "start" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, "message": "scaffolder.template-parameter-schema", "related": { "hosts": [ - "backstage.***.dev" + "backstage.example.internal" ], "ip": [ - "::ffff:10.134.20.225" + "::ffff:198.51.100.12" ], "user": [ - "user:default/octocat" + "user:default/alice.johnson" ] }, + "source": { + "ip": "::ffff:198.51.100.12" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema", + "original": "/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema", "path": "/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema" }, "user": { - "id": "user:default/octocat" + "id": "user:default/alice.johnson" }, "user_agent": { "device": { @@ -580,7 +718,6 @@ } }, { - "_tmp": {}, "backstage": { "meta": { "templateRef": "Template:default/starter-kit" @@ -591,38 +728,53 @@ "version": "9.4.0" }, "event": { - "action": "scaffolder.template-parameter-schema:succeeded", + "action": "template-parameter-schema:succeeded", + "category": [ + "api" + ], "code": "template-parameter-schema", - "module": "scaffolder", - "severity": 3 + "kind": "event", + "original": "{\"actor\":{\"hostname\":\"backstage.example.internal\",\"actorId\":\"user:default/alice.johnson\",\"ip\":\"::ffff:198.51.100.12\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36\"},\"eventId\":\"template-parameter-schema\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"scaffolder.template-parameter-schema\",\"meta\":{\"templateRef\":\"Template:default/starter-kit\"},\"plugin\":\"scaffolder\",\"request\":{\"method\":\"GET\",\"url\":\"/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"succeeded\"}", + "outcome": "success", + "provider": "scaffolder", + "severity": 3, + "type": [ + "access", + "end" + ] }, "http": { "request": { "method": "GET" } }, - "json": {}, "log": { "level": "info" }, "message": "scaffolder.template-parameter-schema", "related": { "hosts": [ - "backstage.***.dev" + "backstage.example.internal" ], "ip": [ - "::ffff:10.134.20.225" + "::ffff:198.51.100.12" ], "user": [ - "user:default/octocat" + "user:default/alice.johnson" ] }, + "source": { + "ip": "::ffff:198.51.100.12" + }, + "tags": [ + "preserve_original_event" + ], "url": { - "original": "http://localhost/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema", + "original": "/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema", "path": "/api/scaffolder/v2/templates/default/template/starter-kit/parameter-schema" }, "user": { - "id": "user:default/octocat" + "id": "user:default/alice.johnson" }, "user_agent": { "device": { diff --git a/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-common-config.yml b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-common-config.yml new file mode 100644 index 00000000000..4da22641654 --- /dev/null +++ b/packages/backstage/data_stream/audit_logs/_dev/test/pipeline/test-common-config.yml @@ -0,0 +1,3 @@ +fields: + tags: + - preserve_original_event From 3b698290a7b4a44f6b14481539de132565ec371a Mon Sep 17 00:00:00 2001 From: Simon Beaulieu Date: Sun, 5 Jul 2026 23:44:31 -0400 Subject: [PATCH 11/11] Regenerate sample event and docs from updated pipeline and fields - Correct event.module -> event.provider in the doc template's plugin correlation guidance, matching the field the pipeline actually populates. - Fix the doc template's hardcoded paths default to /var/log/*.log. - Remove an incomplete sentence fragment left over from an earlier edit. - Regenerate data_stream/audit_logs/sample_event.json and docs/README.md via `elastic-package build` so the exported fields table and sample event reflect the current pipeline and ecs.yml. --- packages/backstage/_dev/build/docs/README.md | 128 ++++----- .../data_stream/audit_logs/sample_event.json | 45 +-- packages/backstage/docs/README.md | 266 ++++++++++++++++-- 3 files changed, 328 insertions(+), 111 deletions(-) diff --git a/packages/backstage/_dev/build/docs/README.md b/packages/backstage/_dev/build/docs/README.md index 981f8d95587..cdc42ed94c9 100644 --- a/packages/backstage/_dev/build/docs/README.md +++ b/packages/backstage/_dev/build/docs/README.md @@ -1,116 +1,106 @@ -{{- generatedHeader }} -{{/* -This template can be used as a starting point for writing documentation for your new integration. For each section, fill in the details -described in the comments. - -Find more detailed documentation guidelines in https://www.elastic.co/docs/extend/integrations/documentation-guidelines -*/}} # Backstage Integration for Elastic ## Overview -{{/* Complete this section with a short summary of what data this integration collects and what use cases it enables */}} -The Backstage integration for Elastic enables collection of ... -This integration facilitates ... + +The Backstage integration for Elastic collects audit log events emitted by [Backstage](https://backstage.io)'s built-in `auditor` service, giving you visibility into user actions, plugin activity, and catalog operations across your Backstage instance. Events are parsed and normalized into the Elastic Common Schema (ECS) so you can search, alert on, and visualize user and plugin activity across your developer platform. ### Compatibility -{{/* Complete this section with information on what 3rd party software or hardware versions this integration is compatible with */}} -This integration is compatible with ... + +This integration is compatible with Backstage backends built on `@backstage/backend-plugin-api` version `1.2.0` or later, the release that introduced the built-in `auditor` core service (implemented by default via `@backstage/backend-defaults`). No additional Backstage plugin is required — every Backstage backend created with `createBackend()` emits audit events through this service automatically. ### How it works -{{/* Add a high level overview on how this integration works. For example, does it collect data from API calls or recieving data from a network or file.*/}} + +Backstage's `auditor` service formats each audit event as a single JSON log line (message format `.`) through the root Winston logger, along with fields such as the acting user, HTTP request details, the originating plugin, event status, and severity level. This integration's filestream input reads those JSON lines from a log file — for example, captured container or pod output, or a file the Backstage backend writes to directly — and the ingest pipeline parses and maps them to ECS. ## What data does this integration collect? -{{/* Complete this section with information on what types of data the integration collects, and link to reference documentation if available */}} -The Backstage integration collects log messages of the following types: -* ... + +The Backstage integration collects audit log events of the following types, depending on which plugins emit `auditor` events in your Backstage instance: +* Catalog operations: entity fetch and catalog query events, including the requested fields and applied filters. +* User and plugin activity: the acting user or service, the plugin and event name, HTTP method and URL, and whether the operation succeeded, failed, or is still in progress. +* Severity-classified events: each event carries a Backstage-assigned severity level (`low`, `medium`, or `high`), mapped to `event.severity`. ### Supported use cases -{{/* Add details on the use cases that can be enabled by using this integration. Explain why a user would want to install and use this integration. */}} + +Integrating Backstage audit logs with the Elastic Stack lets you: +* Monitor user activity: track who accessed or modified catalog entities, and from where. +* Investigate plugin behavior: correlate events by `event.provider` (the Backstage plugin) and `event.code` (the audit event ID) to debug or audit plugin-specific actions. +* Support compliance and security monitoring: maintain a searchable, ECS-normalized record of administrative and catalog actions performed through Backstage. ## What do I need to use this integration? -{{/* List any vendor-specific prerequisites needed before starting to install the integration. */}} + +To use this integration, you'll need the following vendor prerequisites: +- A Backstage backend built with `createBackend()` from `@backstage/backend-defaults` (version `1.2.0` or later), so the `auditor` service is available and emitting events automatically. +- The audit log output written to a location the Elastic Agent can read as a file — for example, captured container/pod logs mounted into the Elastic Agent's filesystem, or a file the Backstage backend logs to directly. + +You'll also need the following Elastic prerequisites: +- Elastic Stack (Elasticsearch and Kibana) version `8.19.0` or later. +- An active Elastic Agent installed and enrolled in Fleet, with filesystem access to the Backstage audit log file(s). ## How do I deploy this integration? ### Agent-based deployment -Elastic Agent must be installed. For more details, check the Elastic Agent [installation instructions](docs-content://reference/fleet/install-elastic-agents.md). You can install only one Elastic Agent per host. +Elastic Agent must be installed on a host or container that has access to the Backstage audit log file(s). For more details, check the Elastic Agent [installation instructions](https://www.elastic.co/guide/en/fleet/current/elastic-agent-installation.html). You can install only one Elastic Agent per host. -Elastic Agent is required to stream data from the syslog or log file receiver and ship the data to Elastic, where the events will then be processed via the integration's ingest pipelines. +Elastic Agent is required to collect the log file(s) and ship the data to Elastic, where the events are then processed by this integration's ingest pipeline. -{{/* If agentless is available for this integration, we'll want to include that here as well. -### Agentless deployment +### Onboard / configure -Agentless deployments are only supported in Elastic Serverless and Elastic Cloud environments. Agentless deployments provide a means to ingest data while avoiding the orchestration, management, and maintenance needs associated with standard ingest infrastructure. Using an agentless deployment makes manual agent deployment unnecessary, allowing you to focus on your data instead of the agent that collects it. +1. In Kibana, navigate to **Management → Integrations**. +2. Search for **Backstage** and select the integration. +3. Click **Add Backstage**. +4. Configure **Paths** to point to the location of the Backstage audit log file(s) — for example, a mounted volume or captured container log path. The default is `/var/log/*.log`. +5. (Optional) Configure **Exclude files** to skip rotated or compressed log files. The default already excludes `.gz` files. +6. (Optional) Configure **Parsers** if your Backstage audit log lines are pretty-printed across multiple lines rather than one JSON object per line. See the field's description for `ndjson` and `multiline` examples. +7. Assign the integration to an Elastic Agent policy and click **Save and continue**. -For more information, refer to [Agentless integrations](https://www.elastic.co/guide/en/serverless/current/security-agentless-integrations.html) and [Agentless integrations FAQ](https://www.elastic.co/guide/en/serverless/current/agentless-integration-troubleshooting.html) -*/}} +### Validation -### Onboard / configure -{{/* List the steps that will need to be followed in order to completely set up a working integration. -For integrations that support multiple input types, be sure to add steps for all inputs. -*/}} +After the configuration is complete, follow these steps to verify data is flowing correctly from Backstage to the Elastic Stack: -### Validation -{{/* How can the user test whether the integration is working? Including example commands or test files if applicable */}} +1. Trigger an audit event in Backstage — for example, browse the Software Catalog to trigger a `catalog.entity-fetch` event. +2. In Kibana, navigate to **Analytics → Discover**. +3. Select the `logs-*` data view. +4. In the search bar, enter the filter: `data_stream.dataset: "backstage.audit_logs"`. +5. Verify that events appear with a non-null `event.action` field, and that `event.provider` reflects the Backstage plugin that generated the event (for example, `catalog`). ## Troubleshooting For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). -{{/* -Add any vendor specific troubleshooting here. -Are there common issues or “gotchas” for deploying this integration? If so, how can they be resolved? -If applicable, links to the third-party software’s troubleshooting documentation. -*/}} +### Common configuration issues + +- **No events collected**: Confirm the Backstage backend is emitting audit events (not every plugin calls the `auditor` service for every operation) and that the configured **Paths** match the actual log file location. +- **Multi-line JSON not parsed**: If your Backstage deployment pretty-prints JSON log lines across multiple lines, enable the `multiline` parser under **Parsers**. Otherwise, partial lines are dropped by the pipeline's `isAuditEvent` check. +- **Events silently dropped**: The ingest pipeline drops any line that doesn't contain an `isAuditEvent` key, so non-audit application logs mixed into the same file are expected to be filtered out. ## Scaling For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. -{{/* Add any vendor specific scaling information here */}} ## Reference -{{/* Repeat for each data stream of the current type -### {Data stream name} -The `{data stream name}` data stream provides events from {source} of the following types: {list types}. +### Inputs used -For each data_stream_name, include an optional summary of the datastream, the exported fields reference table and the sample event. +The following inputs are used by this integration: -The fields template function will be replaced by a generated list of all fields from the `fields/` directory of the data stream when building the integration. +{{ inputDocs }} -#### {data stream name} fields +### Data streams -To include a generated list of fields from the `fields/` directory, uncomment and use: -{{ fields "data_stream_name" }} +#### audit_logs -The event template function will be replace by a sample event, taken from `sample_event.json`, when building this integration. +The `audit_logs` data stream provides audit events from Backstage's `auditor` service, including catalog operations, user activity, and other plugin-specific audit events. -To include a sample event from `sample_event.json`, uncomment and use: -{{ event "data_stream_name" }} +##### audit_logs fields -*/}} +{{ fields "audit_logs" }} -{{/* Export ILM Policies - This accepts a list of data stream names as arguments, and will export the ILM Policies - for each given data stream name. If no arguments are provided, all ILM Policies will be - exported. - - If there are no ILM Policies defined, this will be an empty string. -*/}} -{{ ilm }} +##### audit_logs sample event -{{/* Export Transforms - This will export the transforms used by this integration. - If there are no transforms defined, this will be an empty string. -*/}} -{{ transform }} +{{ event "audit_logs" }} -### Inputs used -{{/* All inputs used by this package will be automatically listed here. */}} -{{ inputDocs }} +### Vendor documentation links -### API usage -{{/* For integrations that use APIs to collect data, document all the APIs that are used, and link to relevent information */}} -These APIs are used with this integration: -* ... +For more information about Backstage's auditor service, refer to: +* [Backstage Auditor Service documentation](https://backstage.io/docs/backend-system/core-services/auditor) diff --git a/packages/backstage/data_stream/audit_logs/sample_event.json b/packages/backstage/data_stream/audit_logs/sample_event.json index fac7483d84e..8fd53547821 100644 --- a/packages/backstage/data_stream/audit_logs/sample_event.json +++ b/packages/backstage/data_stream/audit_logs/sample_event.json @@ -1,9 +1,9 @@ { "@timestamp": "2026-05-22T20:30:05.131Z", "agent": { - "ephemeral_id": "ea32d543-c037-4007-ae5a-8fcbbea0ae1c", - "id": "c869b402-bcb5-4114-bcad-f012010d9c01", - "name": "elastic-agent-36554", + "ephemeral_id": "20aaec8f-1ea7-46e2-bd16-4567ba227f66", + "id": "6e7f5395-041c-4454-904b-81f787524d9b", + "name": "elastic-agent-58777", "type": "filebeat", "version": "9.3.3" }, @@ -26,40 +26,50 @@ }, "data_stream": { "dataset": "backstage.audit_logs", - "namespace": "24583", + "namespace": "60730", "type": "logs" }, "ecs": { "version": "9.4.0" }, "elastic_agent": { - "id": "c869b402-bcb5-4114-bcad-f012010d9c01", + "id": "6e7f5395-041c-4454-904b-81f787524d9b", "snapshot": false, "version": "9.3.3" }, "event": { - "action": "catalog.entity-fetch:initiated", + "action": "entity-fetch:initiated", "agent_id_status": "verified", + "category": [ + "api" + ], "code": "entity-fetch", "dataset": "backstage.audit_logs", - "ingested": "2026-06-30T15:29:46Z", - "module": "catalog", + "ingested": "2026-07-06T03:20:13Z", + "kind": "event", + "module": "backstage", "original": "{\"actor\":{\"hostname\":\"localhost\",\"ip\":\"127.0.0.1\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch\",\"meta\":{\"query\":{\"fields\":[\"metadata\",\"kind\",\"spec.profile\"],\"filter\":[\"kind=group,relations.hasMember=user:default/guest\"]},\"queryType\":\"all\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\",\"timestamp\":\"2026-05-22T20:30:05.131Z\"}", - "severity": 3 + "outcome": "unknown", + "provider": "catalog", + "severity": 3, + "type": [ + "access", + "start" + ] }, "host": { "architecture": "aarch64", "containerized": false, - "hostname": "elastic-agent-36554", + "hostname": "elastic-agent-58777", "ip": [ "172.21.0.2", "172.18.0.4" ], "mac": [ - "4E-08-C5-12-BF-C2", - "AA-11-51-9A-46-F3" + "7A-3E-EF-2D-FE-34", + "7E-FE-FE-2C-6A-49" ], - "name": "elastic-agent-36554", + "name": "elastic-agent-58777", "os": { "family": "", "kernel": "6.12.76-linuxkit", @@ -79,8 +89,8 @@ }, "log": { "file": { - "device_id": "44", - "inode": "96", + "device_id": "43", + "inode": "47", "path": "/tmp/service_logs/test-audit-events.log" }, "level": "info", @@ -95,12 +105,15 @@ "127.0.0.1" ] }, + "source": { + "ip": "127.0.0.1" + }, "tags": [ "backstage", "preserve_original_event" ], "url": { - "original": "http://localhost/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", + "original": "/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", "path": "/api/catalog/entities", "query": "fields=metadata,kind,spec.profile&filter=kind=group,relations.hasMember=user:default/guest" }, diff --git a/packages/backstage/docs/README.md b/packages/backstage/docs/README.md index 498ae42de15..124b52c52d4 100644 --- a/packages/backstage/docs/README.md +++ b/packages/backstage/docs/README.md @@ -1,70 +1,89 @@ - - - # Backstage Integration for Elastic ## Overview -The Backstage integration for Elastic enables collection of ... -This integration facilitates ... +The Backstage integration for Elastic collects audit log events emitted by [Backstage](https://backstage.io)'s built-in `auditor` service, giving you visibility into user actions, plugin activity, and catalog operations across your Backstage instance. Events are parsed and normalized into the Elastic Common Schema (ECS) so you can search, alert on, and visualize user and plugin activity across your developer platform. ### Compatibility -This integration is compatible with ... +This integration is compatible with Backstage backends built on `@backstage/backend-plugin-api` version `1.2.0` or later, the release that introduced the built-in `auditor` core service (implemented by default via `@backstage/backend-defaults`). No additional Backstage plugin is required — every Backstage backend created with `createBackend()` emits audit events through this service automatically. ### How it works +Backstage's `auditor` service formats each audit event as a single JSON log line (message format `.`) through the root Winston logger, along with fields such as the acting user, HTTP request details, the originating plugin, event status, and severity level. This integration's filestream input reads those JSON lines from a log file — for example, captured container or pod output, or a file the Backstage backend writes to directly — and the ingest pipeline parses and maps them to ECS. ## What data does this integration collect? -The Backstage integration collects log messages of the following types: -* ... +The Backstage integration collects audit log events of the following types, depending on which plugins emit `auditor` events in your Backstage instance: +* Catalog operations: entity fetch and catalog query events, including the requested fields and applied filters. +* User and plugin activity: the acting user or service, the plugin and event name, HTTP method and URL, and whether the operation succeeded, failed, or is still in progress. +* Severity-classified events: each event carries a Backstage-assigned severity level (`low`, `medium`, or `high`), mapped to `event.severity`. ### Supported use cases +Integrating Backstage audit logs with the Elastic Stack lets you: +* Monitor user activity: track who accessed or modified catalog entities, and from where. +* Investigate plugin behavior: correlate events by `event.provider` (the Backstage plugin) and `event.code` (the audit event ID) to debug or audit plugin-specific actions. +* Support compliance and security monitoring: maintain a searchable, ECS-normalized record of administrative and catalog actions performed through Backstage. ## What do I need to use this integration? +To use this integration, you'll need the following vendor prerequisites: +- A Backstage backend built with `createBackend()` from `@backstage/backend-defaults` (version `1.2.0` or later), so the `auditor` service is available and emitting events automatically. +- The audit log output written to a location the Elastic Agent can read as a file — for example, captured container/pod logs mounted into the Elastic Agent's filesystem, or a file the Backstage backend logs to directly. + +You'll also need the following Elastic prerequisites: +- Elastic Stack (Elasticsearch and Kibana) version `8.19.0` or later. +- An active Elastic Agent installed and enrolled in Fleet, with filesystem access to the Backstage audit log file(s). ## How do I deploy this integration? ### Agent-based deployment -Elastic Agent must be installed. For more details, check the Elastic Agent [installation instructions](docs-content://reference/fleet/install-elastic-agents.md). You can install only one Elastic Agent per host. - -Elastic Agent is required to stream data from the syslog or log file receiver and ship the data to Elastic, where the events will then be processed via the integration's ingest pipelines. - +Elastic Agent must be installed on a host or container that has access to the Backstage audit log file(s). For more details, check the Elastic Agent [installation instructions](https://www.elastic.co/guide/en/fleet/current/elastic-agent-installation.html). You can install only one Elastic Agent per host. +Elastic Agent is required to collect the log file(s) and ship the data to Elastic, where the events are then processed by this integration's ingest pipeline. ### Onboard / configure +1. In Kibana, navigate to **Management → Integrations**. +2. Search for **Backstage** and select the integration. +3. Click **Add Backstage**. +4. Configure **Paths** to point to the location of the Backstage audit log file(s) — for example, a mounted volume or captured container log path. The default is `/var/log/*.log`. +5. (Optional) Configure **Exclude files** to skip rotated or compressed log files. The default already excludes `.gz` files. +6. (Optional) Configure **Parsers** if your Backstage audit log lines are pretty-printed across multiple lines rather than one JSON object per line. See the field's description for `ndjson` and `multiline` examples. +7. Assign the integration to an Elastic Agent policy and click **Save and continue**. ### Validation +After the configuration is complete, follow these steps to verify data is flowing correctly from Backstage to the Elastic Stack: + +1. Trigger an audit event in Backstage — for example, browse the Software Catalog to trigger a `catalog.entity-fetch` event. +2. In Kibana, navigate to **Analytics → Discover**. +3. Select the `logs-*` data view. +4. In the search bar, enter the filter: `data_stream.dataset: "backstage.audit_logs"`. +5. Verify that events appear with a non-null `event.action` field, and that `event.provider` reflects the Backstage plugin that generated the event (for example, `catalog`). ## Troubleshooting For help with Elastic ingest tools, check [Common problems](https://www.elastic.co/docs/troubleshoot/ingest/fleet/common-problems). +### Common configuration issues + +- **No events collected**: Confirm the Backstage backend is emitting audit events (not every plugin calls the `auditor` service for every operation) and that the configured **Paths** match the actual log file location. +- **Multi-line JSON not parsed**: If your Backstage deployment pretty-prints JSON log lines across multiple lines, enable the `multiline` parser under **Parsers**. Otherwise, partial lines are dropped by the pipeline's `isAuditEvent` check. +- **Events silently dropped**: The ingest pipeline drops any line that doesn't contain an `isAuditEvent` key, so non-audit application logs mixed into the same file are expected to be filtered out. ## Scaling For more information on architectures that can be used for scaling this integration, check the [Ingest Architectures](https://www.elastic.co/docs/manage-data/ingest/ingest-reference-architectures) documentation. - ## Reference - - - -### Data streams using ILM policies - - - - - ### Inputs used +The following inputs are used by this integration: + These inputs can be used with this integration:

filestream @@ -82,7 +101,202 @@ To collect logs via Filestream, select **Collect logs via Filestream** and confi
-### API usage - -These APIs are used with this integration: -* ... +### Data streams + +#### audit_logs + +The `audit_logs` data stream provides audit events from Backstage's `auditor` service, including catalog operations, user activity, and other plugin-specific audit events. + +##### audit_logs fields + +**Exported fields** + +| Field | Description | Type | +|---|---|---| +| @timestamp | Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events. | date | +| backstage.meta | Event-specific metadata emitted by the Backstage plugin. Shape varies per event type; stored as `flattened` to preserve arbitrary keys without mapping explosion. Well-known catalog-query keys are promoted to `backstage.query.\*`. | flattened | +| backstage.query.fields | The entity fields requested by the catalog query. | keyword | +| backstage.query.filter | The catalog query filter conditions as key/value pairs. | flattened | +| backstage.query.type | The type of catalog query that was issued (e.g. "all"). | keyword | +| backstage.severity_level | The severity level of the audit log registered by Backstage. | keyword | +| cloud.image.id | Image ID for the cloud instance. | keyword | +| data_stream.dataset | The field can contain anything that makes sense to signify the source of the data. Examples include `nginx.access`, `prometheus`, `endpoint` etc. For data streams that otherwise fit, but that do not have dataset set we use the value "generic" for the dataset value. `event.dataset` should have the same value as `data_stream.dataset`. Beyond the Elasticsearch data stream naming criteria noted above, the `dataset` value has additional restrictions: \* Must not contain `-` \* No longer than 100 characters | constant_keyword | +| data_stream.namespace | A user defined namespace. Namespaces are useful to allow grouping of data. Many users already organize their indices this way, and the data stream naming scheme now provides this best practice as a default. Many users will populate this field with `default`. If no value is used, it falls back to `default`. Beyond the Elasticsearch index naming criteria noted above, `namespace` value has the additional restrictions: \* Must not contain `-` \* No longer than 100 characters | constant_keyword | +| data_stream.type | An overarching type for the data stream. Currently allowed values are "logs" and "metrics". We expect to also add "traces" and "synthetics" in the near future. | constant_keyword | +| event.action | The action captured by the event. This describes the information in the event. It is more specific than `event.category`. Examples are `group-add`, `process-started`, `file-created`. The value is normally defined by the implementer. | keyword | +| event.category | This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy. `event.category` represents the "big buckets" of ECS categories. For example, filtering on `event.category:process` yields all events relating to process activity. This field is closely related to `event.type`, which is used as a subcategory. This field is an array. This will allow proper categorization of some events that fall in multiple categories. | keyword | +| event.code | Identification code for this event, if one exists. Some event sources use event codes to identify messages unambiguously, regardless of message language or wording adjustments over time. An example of this is the Windows Event ID. | keyword | +| event.dataset | Name of the dataset. If an event source publishes more than one type of log or events (e.g. access log, error log), the dataset is used to specify which one the event comes from. It's recommended but not required to start the dataset name with the module name, followed by a dot, then the dataset name. | constant_keyword | +| event.kind | This is one of four ECS Categorization Fields, and indicates the highest level in the ECS category hierarchy. `event.kind` gives high-level information about what type of information the event contains, without being specific to the contents of the event. For example, values of this field distinguish alert events from metric events. The value of this field can be used to inform how these kinds of events should be handled. They may warrant different retention, different access control, it may also help understand whether the data is coming in at a regular interval or not. | keyword | +| event.module | Name of the module this data is coming from. If your monitoring agent supports the concept of modules or plugins to process events of a given source (e.g. Apache logs), `event.module` should contain the name of this module. | constant_keyword | +| event.outcome | This is one of four ECS Categorization Fields, and indicates the lowest level in the ECS category hierarchy. `event.outcome` simply denotes whether the event represents a success or a failure from the perspective of the entity that produced the event. Note that when a single transaction is described in multiple events, each event may populate different values of `event.outcome`, according to their perspective. Also note that in the case of a compound event (a single event that contains multiple logical events), this field should be populated with the value that best captures the overall success or failure from the perspective of the event producer. Further note that not all events will have an associated outcome. For example, this field is generally not populated for metric events, events with `event.type:info`, or any events for which an outcome does not make logical sense. | keyword | +| event.provider | Source of the event. Event transports such as Syslog or the Windows Event Log typically mention the source of an event. It can be the name of the software that generated the event (e.g. Sysmon, httpd), or of a subsystem of the operating system (kernel, Microsoft-Windows-Security-Auditing). | keyword | +| event.severity | The numeric severity of the event according to your event source. What the different severity values mean can be different between sources and use cases. It's up to the implementer to make sure severities are consistent across events from the same source. The Syslog severity belongs in `log.syslog.severity.code`. `event.severity` is meant to represent the severity according to the event source (e.g. firewall, IDS). If the event source does not publish its own severity, you may optionally copy the `log.syslog.severity.code` to `event.severity`. | long | +| event.type | This is one of four ECS Categorization Fields, and indicates the third level in the ECS category hierarchy. `event.type` represents a categorization "sub-bucket" that, when used along with the `event.category` field values, enables filtering events down to a level appropriate for single visualization. This field is an array. This will allow proper categorization of some events that fall in multiple event types. | keyword | +| host.containerized | If the host is a container. | boolean | +| host.os.build | OS build information. | keyword | +| host.os.codename | OS codename, if any. | keyword | +| http.request.method | HTTP request method. The value should retain its casing from the original event. For example, `GET`, `get`, and `GeT` are all considered valid values for this field. | keyword | +| input.type | Input type. | keyword | +| log.file.device_id | Device ID of the log file this event came from. | keyword | +| log.file.inode | Inode number of the log file. | keyword | +| log.file.path | Path to the log file. | keyword | +| log.level | Original log level of the log event. If the source of the event provides a log level or textual severity, this is the one that goes in `log.level`. If your source doesn't specify one, you may put your event transport's severity here (e.g. Syslog severity). Some examples are `warn`, `err`, `i`, `informational`. | keyword | +| log.offset | Log offset. | long | +| related.hosts | All hostnames or other host identifiers seen on your event. Example identifiers include FQDNs, domain names, workstation names, or aliases. | keyword | +| related.ip | All of the IPs seen on your event. | ip | +| related.user | All the user names or other user identifiers seen on the event. | keyword | +| source.ip | IP address of the source (IPv4 or IPv6). | ip | +| url.original | Unmodified original url as seen in the event source. Note that in network monitoring, the observed URL may be a full URL, whereas in access logs, the URL is often just represented as a path. This field is meant to represent the URL as it was observed, complete or not. | wildcard | +| url.original.text | Multi-field of `url.original`. | match_only_text | +| url.path | Path of the request, such as "/search". | wildcard | +| url.query | The field contains the entire query string, excluding the leading `?` character, such as "q=elasticsearch". If a URL contains no `?`, there is no query field. If there is a `?` but no query, the query field exists with an empty string. The `exists` query can be used to differentiate between the two cases. | keyword | +| user.id | Unique identifier of the user. | keyword | +| user_agent.original | Unparsed user_agent string. | keyword | +| user_agent.original.text | Multi-field of `user_agent.original`. | match_only_text | + + +##### audit_logs sample event + +An example event for `audit` looks as following: + +```json +{ + "@timestamp": "2026-05-22T20:30:05.131Z", + "agent": { + "ephemeral_id": "20aaec8f-1ea7-46e2-bd16-4567ba227f66", + "id": "6e7f5395-041c-4454-904b-81f787524d9b", + "name": "elastic-agent-58777", + "type": "filebeat", + "version": "9.3.3" + }, + "backstage": { + "query": { + "fields": [ + "metadata", + "kind", + "spec.profile" + ], + "filter": { + "kind": "group", + "relations": { + "hasMember": "user:default/guest" + } + }, + "type": "all" + }, + "severity_level": "low" + }, + "data_stream": { + "dataset": "backstage.audit_logs", + "namespace": "60730", + "type": "logs" + }, + "ecs": { + "version": "9.4.0" + }, + "elastic_agent": { + "id": "6e7f5395-041c-4454-904b-81f787524d9b", + "snapshot": false, + "version": "9.3.3" + }, + "event": { + "action": "entity-fetch:initiated", + "agent_id_status": "verified", + "category": [ + "api" + ], + "code": "entity-fetch", + "dataset": "backstage.audit_logs", + "ingested": "2026-07-06T03:20:13Z", + "kind": "event", + "module": "backstage", + "original": "{\"actor\":{\"hostname\":\"localhost\",\"ip\":\"127.0.0.1\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36\"},\"eventId\":\"entity-fetch\",\"isAuditEvent\":true,\"level\":\"info\",\"message\":\"catalog.entity-fetch\",\"meta\":{\"query\":{\"fields\":[\"metadata\",\"kind\",\"spec.profile\"],\"filter\":[\"kind=group,relations.hasMember=user:default/guest\"]},\"queryType\":\"all\"},\"plugin\":\"catalog\",\"request\":{\"method\":\"GET\",\"url\":\"/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest\"},\"service\":\"backstage\",\"severityLevel\":\"low\",\"status\":\"initiated\",\"timestamp\":\"2026-05-22T20:30:05.131Z\"}", + "outcome": "unknown", + "provider": "catalog", + "severity": 3, + "type": [ + "access", + "start" + ] + }, + "host": { + "architecture": "aarch64", + "containerized": false, + "hostname": "elastic-agent-58777", + "ip": [ + "172.21.0.2", + "172.18.0.4" + ], + "mac": [ + "7A-3E-EF-2D-FE-34", + "7E-FE-FE-2C-6A-49" + ], + "name": "elastic-agent-58777", + "os": { + "family": "", + "kernel": "6.12.76-linuxkit", + "name": "Wolfi", + "platform": "wolfi", + "type": "linux", + "version": "20230201" + } + }, + "http": { + "request": { + "method": "GET" + } + }, + "input": { + "type": "filestream" + }, + "log": { + "file": { + "device_id": "43", + "inode": "47", + "path": "/tmp/service_logs/test-audit-events.log" + }, + "level": "info", + "offset": 0 + }, + "message": "catalog.entity-fetch", + "related": { + "hosts": [ + "localhost" + ], + "ip": [ + "127.0.0.1" + ] + }, + "source": { + "ip": "127.0.0.1" + }, + "tags": [ + "backstage", + "preserve_original_event" + ], + "url": { + "original": "/api/catalog/entities?fields=metadata,kind,spec.profile&filter=kind%3Dgroup%2Crelations.hasMember%3Duser%3Adefault%2Fguest", + "path": "/api/catalog/entities", + "query": "fields=metadata,kind,spec.profile&filter=kind=group,relations.hasMember=user:default/guest" + }, + "user_agent": { + "device": { + "name": "Mac" + }, + "name": "Chrome", + "original": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36", + "os": { + "full": "Mac OS X 10.15.7", + "name": "Mac OS X", + "version": "10.15.7" + }, + "version": "148.0.0.0" + } +} +``` + +### Vendor documentation links + +For more information about Backstage's auditor service, refer to: +* [Backstage Auditor Service documentation](https://backstage.io/docs/backend-system/core-services/auditor)