Skip to content

Commit

Permalink
Merge branch 'master' into fix/92712
Browse files Browse the repository at this point in the history
  • Loading branch information
kibanamachine authored Feb 24, 2021
2 parents f520f50 + 0280d5a commit b1206b6
Show file tree
Hide file tree
Showing 90 changed files with 2,906 additions and 546 deletions.
496 changes: 496 additions & 0 deletions dev_docs/tutorials/data/search.mdx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
<b>Signature:</b>

```typescript
export declare type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig | RollingFileAppenderConfig;
export declare type AppenderConfigType = ConsoleAppenderConfig | FileAppenderConfig | LegacyAppenderConfig | RewriteAppenderConfig | RollingFileAppenderConfig;
```
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,11 @@
"**/cross-fetch/node-fetch": "^2.6.1",
"**/deepmerge": "^4.2.2",
"**/fast-deep-equal": "^3.1.1",
"**/graphql-toolkit/lodash": "^4.17.15",
"**/graphql-toolkit/lodash": "^4.17.21",
"**/hoist-non-react-statics": "^3.3.2",
"**/isomorphic-fetch/node-fetch": "^2.6.1",
"**/istanbul-instrumenter-loader/schema-utils": "1.0.0",
"**/load-grunt-config/lodash": "^4.17.20",
"**/load-grunt-config/lodash": "^4.17.21",
"**/minimist": "^1.2.5",
"**/node-jose/node-forge": "^0.10.0",
"**/prismjs": "1.22.0",
Expand Down Expand Up @@ -233,7 +233,7 @@
"json-stringify-safe": "5.0.1",
"jsonwebtoken": "^8.5.1",
"load-json-file": "^6.2.0",
"lodash": "^4.17.20",
"lodash": "^4.17.21",
"lru-cache": "^4.1.5",
"markdown-it": "^10.0.0",
"md5": "^2.1.0",
Expand Down Expand Up @@ -390,7 +390,6 @@
"@storybook/addon-essentials": "^6.0.26",
"@storybook/addon-knobs": "^6.0.26",
"@storybook/addon-storyshots": "^6.0.26",
"@storybook/addons": "^6.0.16",
"@storybook/components": "^6.0.26",
"@storybook/core": "^6.0.26",
"@storybook/core-events": "^6.0.26",
Expand Down
18 changes: 18 additions & 0 deletions packages/kbn-logging/src/appenders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,24 @@ import { LogRecord } from './log_record';
*/
export interface Appender {
append(record: LogRecord): void;
/**
* Appenders can be "attached" to one another so that they are able to act
* as a sort of middleware by calling `append` on a different appender.
*
* As appenders cannot be attached to each other until they are configured,
* the `addAppender` method can be used to pass in a newly configured appender
* to attach.
*/
addAppender?(appenderRef: string, appender: Appender): void;
/**
* For appenders which implement `addAppender`, they should declare a list of
* `appenderRefs`, which specify the names of the appenders that their configuration
* depends on.
*
* Note that these are the appender key names that the user specifies in their
* config, _not_ the names of the appender types themselves.
*/
appenderRefs?: string[];
}

/**
Expand Down
136 changes: 134 additions & 2 deletions src/core/server/http/integration_tests/logging.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ describe('request logging', () => {
expect(JSON.parse(meta).http.response.headers.bar).toBe('world');
});

it('filters sensitive request headers', async () => {
it('filters sensitive request headers by default', async () => {
const { http } = await root.setup();

http.createRouter('/').post(
Expand Down Expand Up @@ -283,7 +283,139 @@ describe('request logging', () => {
expect(JSON.parse(meta).http.request.headers.authorization).toBe('[REDACTED]');
});

it('filters sensitive response headers', async () => {
it('filters sensitive request headers when RewriteAppender is configured', async () => {
root = kbnTestServer.createRoot({
logging: {
silent: true,
appenders: {
'test-console': {
type: 'console',
layout: {
type: 'pattern',
pattern: '%level|%logger|%message|%meta',
},
},
rewrite: {
type: 'rewrite',
appenders: ['test-console'],
policy: {
type: 'meta',
mode: 'update',
properties: [
{ path: 'http.request.headers.authorization', value: '[REDACTED]' },
],
},
},
},
loggers: [
{
name: 'http.server.response',
appenders: ['rewrite'],
level: 'debug',
},
],
},
plugins: {
initialize: false,
},
});
const { http } = await root.setup();

http.createRouter('/').post(
{
path: '/ping',
validate: {
body: schema.object({ message: schema.string() }),
},
options: {
authRequired: 'optional',
body: {
accepts: ['application/json'],
},
timeout: { payload: 100 },
},
},
(context, req, res) => res.ok({ body: { message: req.body.message } })
);
await root.start();

await kbnTestServer.request
.post(root, '/ping')
.set('content-type', 'application/json')
.set('authorization', 'abc')
.send({ message: 'hi' })
.expect(200);
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
const [, , , meta] = mockConsoleLog.mock.calls[0][0].split('|');
expect(JSON.parse(meta).http.request.headers.authorization).toBe('[REDACTED]');
});

it('filters sensitive response headers by defaut', async () => {
const { http } = await root.setup();

http.createRouter('/').post(
{
path: '/ping',
validate: {
body: schema.object({ message: schema.string() }),
},
options: {
authRequired: 'optional',
body: {
accepts: ['application/json'],
},
timeout: { payload: 100 },
},
},
(context, req, res) =>
res.ok({ headers: { 'set-cookie': ['123'] }, body: { message: req.body.message } })
);
await root.start();

await kbnTestServer.request
.post(root, '/ping')
.set('Content-Type', 'application/json')
.send({ message: 'hi' })
.expect(200);
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
const [, , , meta] = mockConsoleLog.mock.calls[0][0].split('|');
expect(JSON.parse(meta).http.response.headers['set-cookie']).toBe('[REDACTED]');
});

it('filters sensitive response headers when RewriteAppender is configured', async () => {
root = kbnTestServer.createRoot({
logging: {
silent: true,
appenders: {
'test-console': {
type: 'console',
layout: {
type: 'pattern',
pattern: '%level|%logger|%message|%meta',
},
},
rewrite: {
type: 'rewrite',
appenders: ['test-console'],
policy: {
type: 'meta',
mode: 'update',
properties: [{ path: 'http.response.headers.set-cookie', value: '[REDACTED]' }],
},
},
},
loggers: [
{
name: 'http.server.response',
appenders: ['rewrite'],
level: 'debug',
},
],
},
plugins: {
initialize: false,
},
});
const { http } = await root.setup();

http.createRouter('/').post(
Expand Down
47 changes: 47 additions & 0 deletions src/core/server/http/logging/get_response_log.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,53 @@ describe('getEcsResponseLog', () => {
});

test('does not mutate original headers', () => {
const reqHeaders = { a: 'foo', b: ['hello', 'world'] };
const resHeaders = { headers: { c: 'bar' } };
const req = createMockHapiRequest({
headers: reqHeaders,
response: { headers: resHeaders },
});

const responseLog = getEcsResponseLog(req, logger);
expect(reqHeaders).toMatchInlineSnapshot(`
Object {
"a": "foo",
"b": Array [
"hello",
"world",
],
}
`);
expect(resHeaders).toMatchInlineSnapshot(`
Object {
"headers": Object {
"c": "bar",
},
}
`);

responseLog.http.request.headers.a = 'testA';
responseLog.http.request.headers.b[1] = 'testB';
responseLog.http.request.headers.c = 'testC';
expect(reqHeaders).toMatchInlineSnapshot(`
Object {
"a": "foo",
"b": Array [
"hello",
"world",
],
}
`);
expect(resHeaders).toMatchInlineSnapshot(`
Object {
"headers": Object {
"c": "bar",
},
}
`);
});

test('does not mutate original headers when redacting sensitive data', () => {
const reqHeaders = { authorization: 'a', cookie: 'b', 'user-agent': 'hi' };
const resHeaders = { headers: { 'content-length': 123, 'set-cookie': 'c' } };
const req = createMockHapiRequest({
Expand Down
28 changes: 20 additions & 8 deletions src/core/server/http/logging/get_response_log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,22 @@ const ECS_VERSION = '1.7.0';
const FORBIDDEN_HEADERS = ['authorization', 'cookie', 'set-cookie'];
const REDACTED_HEADER_TEXT = '[REDACTED]';

type HapiHeaders = Record<string, string | string[]>;

// We are excluding sensitive headers by default, until we have a log filtering mechanism.
function redactSensitiveHeaders(
headers?: Record<string, string | string[]>
): Record<string, string | string[]> {
const result = {} as Record<string, string | string[]>;
function redactSensitiveHeaders(key: string, value: string | string[]): string | string[] {
return FORBIDDEN_HEADERS.includes(key) ? REDACTED_HEADER_TEXT : value;
}

// Shallow clone the headers so they are not mutated if filtered by a RewriteAppender.
function cloneAndFilterHeaders(headers?: HapiHeaders) {
const result = {} as HapiHeaders;
if (headers) {
for (const key of Object.keys(headers)) {
result[key] = FORBIDDEN_HEADERS.includes(key) ? REDACTED_HEADER_TEXT : headers[key];
result[key] = redactSensitiveHeaders(
key,
Array.isArray(headers[key]) ? [...headers[key]] : headers[key]
);
}
}
return result;
Expand All @@ -45,7 +53,11 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta {

// eslint-disable-next-line @typescript-eslint/naming-convention
const status_code = isBoom(response) ? response.output.statusCode : response.statusCode;
const responseHeaders = isBoom(response) ? response.output.headers : response.headers;

const requestHeaders = cloneAndFilterHeaders(request.headers);
const responseHeaders = cloneAndFilterHeaders(
isBoom(response) ? (response.output.headers as HapiHeaders) : response.headers
);

// borrowed from the hapi/good implementation
const responseTime = (request.info.completed || request.info.responded) - request.info.received;
Expand All @@ -66,15 +78,15 @@ export function getEcsResponseLog(request: Request, log: Logger): LogMeta {
mime_type: request.mime,
referrer: request.info.referrer,
// @ts-expect-error Headers are not yet part of ECS: https://github.com/elastic/ecs/issues/232.
headers: redactSensitiveHeaders(request.headers),
headers: requestHeaders,
},
response: {
body: {
bytes,
},
status_code,
// @ts-expect-error Headers are not yet part of ECS: https://github.com/elastic/ecs/issues/232.
headers: redactSensitiveHeaders(responseHeaders),
headers: responseHeaders,
// responseTime is a custom non-ECS field
responseTime: !isNaN(responseTime) ? responseTime : undefined,
},
Expand Down
Loading

0 comments on commit b1206b6

Please sign in to comment.