Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core.logging] Add RewriteAppender for filtering LogMeta. #91492

Merged
merged 29 commits into from
Feb 24, 2021

Conversation

lukeelmers
Copy link
Member

@lukeelmers lukeelmers commented Feb 16, 2021

Closes #57547

Summary

Implements a RewriteAppender for the purposes of filtering / manipulating a LogRecord, as described in the original issue. The approach here is based on log4j2's RewriteAppender.

Each rewrite appender uses a designated policy which describes what part of the LogRecord to manipulate. This PR starts with a single meta policy for modifying LogMeta. The policy has a mode which can either be remove or update, and takes in a list of paths in the LogMeta object which are to be modified.

For the time being we are intentionally keeping this undocumented in the public docs in case we choose to make future changes, as the legacy filtering behavior was also undocumented.

Usage

Here's a full example configuration which logs both to the console and a log file, censoring the cookie request header from the http response logs:

logging:
  appenders:
    console:
      type: console
      layout:
        type: pattern
        highlight: true
        pattern: "[%date][%level][%logger] %message %meta"
    file:
      type: file
      fileName: ./kibana.log
      layout:
        type: json
    censor:
      type: rewrite
      appenders: [console, file]
      policy:
        type: meta
        mode: update
        properties:
          - path: "http.request.headers.cookie"
            value: "[REDACTED]"
  loggers:
    - name: http.server.response
      appenders: [censor]
      level: debug

The remove mode does not take a value as it simply deletes the provided path:

logging:
  appenders:
    censor:
      type: rewrite
      appenders: [console]
      policy:
        type: meta
        mode: remove
        properties:
          - path: "some.path.to.delete"

@lukeelmers lukeelmers self-assigned this Feb 16, 2021
@lukeelmers lukeelmers added v8.0.0 Feature:Logging release_note:skip Skip the PR/issue when compiling release notes labels Feb 17, 2021
@lukeelmers lukeelmers marked this pull request as ready for review February 17, 2021 05:14
@lukeelmers lukeelmers requested a review from a team as a code owner February 17, 2021 05:14
@lukeelmers lukeelmers added the Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc label Feb 17, 2021
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-core (Team:Core)

packages/kbn-logging/src/appenders.ts Outdated Show resolved Hide resolved
Comment on lines 170 to 182
// By default, we transparently rewrite `LogMeta` sent to the
// console appender to remove potentially sensitive keys.
// This can be overridden in a logger's configuration.
'console',
{
type: 'rewrite',
appenders: ['stdout'],
policy: {
type: 'meta',
mode: 'update',
properties: SENSITIVE_META_PATHS.map((path) => ({ path, value: REDACTED_META_TEXT })),
},
} as AppenderConfigType,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently all sensitive headers are filtered in the http response logging, and the filters cannot be disabled.

In this PR, I removed the filtering code from http, and here have "wrapped" our default console appender in a rewrite appender that performs the same logic.

However, there is one main caveat: While this ensures nothing sensitive is logged with a default configuration, the protection is gone as soon as you customize your config to use something other than the console appender. I tried to think of another way to enforce this more widely while also making it configurable, and I didn't come up with any great solutions that didn't involve creating another standalone config option. Open to suggestions though!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this ensures nothing sensitive is logged with a default configuration, the protection is gone as soon as you customize your config to use something other than the console appender. I tried to think of another way to enforce this more widely while also making it configurable, and I didn't come up with any great solutions that didn't involve creating another standalone config option.

That might be a reason not to use RewriteAppender internally to filter out sensitive data (but provide it as public API). We can either implement a custom filtering logic always applicable to http.request / http.response contexts. Or filter out sensitive properties when creating meta object https://github.com/elastic/kibana/blob/master/src/core/server/http/logging/get_response_log.ts

How it works now in the Legacy platform? Does it always censor the sensitive content?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or filter out sensitive properties when creating meta object

@restrry Yeah, that's the approach we take now, the only issue is that it can't be disabled, and IIRC this was one of the reasons we wanted a rewrite appender to begin with, for the case where we have a reason to allow sensitive headers through for support/debugging purposes.

That's why the only alternative that comes to mind is a new config option to disable filters, which would then be used inside the http response logger getResponseLog. But in the spirit of having One Way Of Doing Things (TM), it'd be nice to use the existing config if we can.

Actually, as I think about it, this is an issue that would possibly be solved by our discussion around having logger-specific default appender configurations.

How it works now in the Legacy platform? Does it always censor the sensitive content?

It filters them out by default (just authorization and cookie), but you can disable that using the (undocumented) logging.filter settings - e.g. logging.filter.cookie=none to remove filter from that header in hapi/good.

// I'm adding the default here because if you add another filter
// using the commandline it will remove authorization. I want users
// to have to explicitly set --logging.filter.authorization=none or
// --logging.filter.cookie=none to have it show up in the logs.
filter: _.defaults(config.filter, {
authorization: 'remove',
cookie: 'remove',
}),

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's the approach we take now, the only issue is that it can't be disabled, and IIRC this was one of the reasons we wanted a rewrite appender to begin with, for the case where we have a reason to allow sensitive headers through for support/debugging purposes.

I'd say it's nice to have. It is better to not add such a feature than to add it but with the possibility of sensitive data leakage due to incorrect configuration:

 While this ensures nothing sensitive is logged with a default configuration,
the protection is gone as soon as you customize your config to use something other than the console appender. 

Actually, as I think about it, this is an issue that would possibly be solved by our discussion around having logger-specific default appender configurations.

Are you implying that we can hide the Meta object from the logs? The pattern does not apply to json layout.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say it's nice to have. It is better to not add such a feature than to add it but with the possibility of sensitive data leakage due to incorrect configuration

Fair point, so do you think we should:

  1. Keep the hardcoded filtering of sensitive header fields, meaning we can't turn those off for debugging purposes
  2. Go with option 1 but also introduce a new config flag to disable this for debugging purposes (e.g. logging.filterSensitiveHttpHeaders or whatever). The flag would default to true but could be disabled if someone wanted to use a custom rewrite appender.
  3. Some other option I'm not thinking of?

Ideally I'd like to not introduce another config flag, but I'm also unsure of how we could otherwise make it possible to disable the filtering.

Are you implying that we can hide the Meta object from the logs?

No, sorry that was a bit vague. I meant if we went the route of having predefined default appender configurations on a per-logger basis (similar to the ES config file), then this would be another use case for those and would solve this problem. But if we go the simpler route of removing the meta from the pattern by default, that won't help us in this case.

Copy link
Contributor

@mshustov mshustov Feb 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the hardcoded filtering of sensitive header fields, meaning we can't turn those off for debugging purposes

As for me, it's an acceptable trade-off. Any other opinions? @joshdover @pgayvallet @TinaHeiligers

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the more I think about it, the hardcoded approach feels like the safest option for the time being. And then if we were to consider an enhancement in 8.x to support logger-specific appender defaults as discussed in #90457, it would allow us to move these settings there anyway.

src/core/server/logging/logging_config.ts Outdated Show resolved Hide resolved
@TinaHeiligers
Copy link
Contributor

TinaHeiligers commented Feb 17, 2021

@lukeelmers

For the time being we are intentionally keeping this undocumented in case we choose to make future changes, as the legacy filtering behavior was also undocumented.

While I understand that the initial implementation is pretty much a MVP, IMHO, I must disagree with not documenting the RewriteAppender in at least the dev docs. Being able to censor/filter sensitive data is a powerful feature, even at an MVP stage and I envisage it leading to some head scratching down the line when folks are using logs for debugging.

The fact the filtering in the LP isn't documented shouldn't hold us back from doing the right thing, at least at the dev level.

@lukeelmers
Copy link
Member Author

@TinaHeiligers I'm perfectly comfortable documenting this for devs -- I think the only reason we previously decided to make this internal was because the legacy filtering is also undocumented and we wanted to reserve the right to make changes in the future (or decide whether we want to support this functionality long-term at all).

Since we don't yet have proper docs on the new logging system, I will just add a mention to the README for now and clarify that it's experimental/internal.


export const createRewritePolicy = (config: RewritePolicyConfig): RewritePolicy => {
switch (config.type) {
case 'meta':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find MetaRewritePolicy in log4j http://logging.apache.org/log4j/2.x/manual/appenders.html#RewritePolicy
Do we call so because we apply a policy to Meta object only?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think MetaRewritePolicy is a combined modification of log4j's MapRewritePolicy and PropertiesRewritePolicy where we're using the type field and the properties field rather than the keyValuePair in MetaRewritePolicy and we're allowing fields to be added too ( what PropertiesRewritePolicy does). And, yes, the policy is applied to the meta property. It's hard to tell though because our implementation isn't a 1-to-1 with log4j.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we call so because we apply a policy to Meta object only?

Yeah exactly, they have a map and properties rewrite policy, so I called this meta as it is only scoped to the LogMeta. With this pattern, in the future we could have something like a message or level policy that will rewrite those items specifically.

src/core/server/logging/logging_config.ts Outdated Show resolved Hide resolved
Comment on lines 170 to 182
// By default, we transparently rewrite `LogMeta` sent to the
// console appender to remove potentially sensitive keys.
// This can be overridden in a logger's configuration.
'console',
{
type: 'rewrite',
appenders: ['stdout'],
policy: {
type: 'meta',
mode: 'update',
properties: SENSITIVE_META_PATHS.map((path) => ({ path, value: REDACTED_META_TEXT })),
},
} as AppenderConfigType,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this ensures nothing sensitive is logged with a default configuration, the protection is gone as soon as you customize your config to use something other than the console appender. I tried to think of another way to enforce this more widely while also making it configurable, and I didn't come up with any great solutions that didn't involve creating another standalone config option.

That might be a reason not to use RewriteAppender internally to filter out sensitive data (but provide it as public API). We can either implement a custom filtering logic always applicable to http.request / http.response contexts. Or filter out sensitive properties when creating meta object https://github.com/elastic/kibana/blob/master/src/core/server/http/logging/get_response_log.ts

How it works now in the Legacy platform? Does it always censor the sensitive content?

src/core/server/logging/logging_system.ts Outdated Show resolved Hide resolved
packages/kbn-logging/src/appenders.ts Outdated Show resolved Hide resolved
@lukeelmers lukeelmers added v7.13.0 auto-backport Deprecated - use backport:version if exact versions are needed labels Feb 17, 2021
Copy link
Contributor

@TinaHeiligers TinaHeiligers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went through the initial implementation and it seems to make sense. I tried to pull the code and run it locally but keep hitting a config error on start up (probably local).

I'd really like to see something in the README on the final implementation though.

src/core/server/logging/logging_config.ts Outdated Show resolved Hide resolved

export const createRewritePolicy = (config: RewritePolicyConfig): RewritePolicy => {
switch (config.type) {
case 'meta':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think MetaRewritePolicy is a combined modification of log4j's MapRewritePolicy and PropertiesRewritePolicy where we're using the type field and the properties field rather than the keyValuePair in MetaRewritePolicy and we're allowing fields to be added too ( what PropertiesRewritePolicy does). And, yes, the policy is applied to the meta property. It's hard to tell though because our implementation isn't a 1-to-1 with log4j.

@lukeelmers
Copy link
Member Author

I tried to pull the code and run it locally but keep hitting a config error on start up (probably local).

I'll update the README, but the example config I gave in the PR description should be working without an error if you want to play around with it.

@TinaHeiligers
Copy link
Contributor

... but the example config I gave in the PR description should be working without an error if you want to play around with it.

A direct copy-paste throws the following:

/Users/christianeheiligers/Projects/kibana/packages/kbn-config-schema/target/out/types/literal_type.js:17
    handleError(type, { value, valids: [expectedValue] }) {
                        ^
TypeError: Cannot destructure property 'value' of 'undefined' as it is undefined.

Changing kind to type gets rid of that but also doesn't work directly:

/Users/christianeheiligers/Projects/kibana/packages/kbn-config-schema/target/out/types/type.js:51
            throw new errors_1.ValidationError(error, namespace);
                  ^
Error: [config validation of [logging].appenders.file]: types that failed validation:
- [config validation of [logging].0.type]: expected value to equal [console]
- [config validation of [logging].1.fileName]: expected value of type [string] but got [undefined]
- [config validation of [logging].2.type]: expected value to equal [legacy-appender]
- [config validation of [logging].3.type]: expected value to equal [rewrite]
- [config validation of [logging].4.type]: expected value to equal [rolling-file]

Removing the file appender solves that. What am I missing in the config?

@lukeelmers
Copy link
Member Author

Ah sorry @TinaHeiligers, I added that example before #90764 was merged and some of the properties were renamed. I've updated now, let me know if you hit any issues.

Copy link
Contributor

@pgayvallet pgayvallet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few NITS, overall looking great

src/core/server/http/logging/get_response_log.ts Outdated Show resolved Hide resolved
src/core/server/logging/README.md Outdated Show resolved Hide resolved
src/core/server/logging/logging_system.ts Outdated Show resolved Hide resolved
result[key] = FORBIDDEN_HEADERS.includes(key) ? REDACTED_HEADER_TEXT : headers[key];
result[key] = redactSensitiveHeaders(
key,
Array.isArray(headers[key]) ? [...headers[key]] : headers[key]
Copy link
Contributor

@mshustov mshustov Feb 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: It's good we enforce immutability, but we pay the extra cost when cloning the array. IMO we can assume that no-one mutates the array.

@lukeelmers
Copy link
Member Author

@elasticmachine merge upstream

@lukeelmers
Copy link
Member Author

@elasticmachine merge upstream

Copy link
Contributor

@TinaHeiligers TinaHeiligers left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great addition to our new logging system and even though we're not using the implementation in the default config right now, it's going to be super useful when we get around to #92082.
LGTM!

@lukeelmers lukeelmers enabled auto-merge (squash) February 24, 2021 21:20
@kibanamachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

✅ unchanged

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

cc @lukeelmers

@kibanamachine
Copy link
Contributor

💚 Backport successful

7.x / #92724

Successful backport PRs will be merged automatically after passing CI.

@lukeelmers lukeelmers deleted the feat/rewrite-appender branch February 24, 2021 23:25
kibanamachine added a commit that referenced this pull request Feb 25, 2021
…92724)

Co-authored-by: Luke Elmers <luke.elmers@elastic.co>
gmmorris added a commit to gmmorris/kibana that referenced this pull request Feb 25, 2021
* master: (38 commits)
  Fixes Cypress flake by adding pipe, click, and should (elastic#92762)
  [Discover] Fix filtering selected sidebar fields (elastic#91828)
  [ML] Fixes positions of calendar arrow buttons in start datafeed modal (elastic#92625)
  [dev/build_ts_refs] check that commit in outDirs matches mergeBase (elastic#92513)
  add dep on `@kbn/config` so it is built first
  [Expressions] [Lens] Add id and copyMetaFrom arg to mapColumn fn + add configurable onError argument to math fn (elastic#90481)
  [Lens] Fix Workspace hidden when using Safari (elastic#92616)
  [Lens] Fixes vertical alignment validation messages (elastic#91878)
  forbid x-elastic-product-origin header in elasticsearch configuration (elastic#92359)
  [Security Solution][Detections] Set default indicator path to reduce friction with new filebeat modules (elastic#92081)
  [ILM][Accessibility] Added A11y test for ILM new policy form. (elastic#92570)
  [Security Solution][Exceptions] - Fixes exceptions builder UI where invalid values can cause overwrites of other values (elastic#90634)
  Automatically generated Api documentation (elastic#86232)
  Increase index pattern select limit to 1000 (elastic#92093)
  [core.logging] Add RewriteAppender for filtering LogMeta. (elastic#91492)
  [Security Solution][Detection Rules] Update prebuilt rule threats to match schema (elastic#92281)
  [Security Solutions][Detection Engine] Fixes bug with not being able to duplicate indicator matches (elastic#92565)
  [Dashboard] Export appropriate references from byValue panels (elastic#91567)
  [Upgrade Assistant] Align code between branches (elastic#91862)
  [Security Solution][Case] Fix alerts push (elastic#91638)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
auto-backport Deprecated - use backport:version if exact versions are needed Feature:Logging release_note:skip Skip the PR/issue when compiling release notes Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v7.13.0 v8.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement filtering mechanism in the Logging service
6 participants