Skip to content

Commit

Permalink
forbid x-elastic-product-origin header in elasticsearch configuration (
Browse files Browse the repository at this point in the history
  • Loading branch information
pgayvallet authored Feb 25, 2021
1 parent f67fef9 commit 321b8bf
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 6 deletions.
23 changes: 23 additions & 0 deletions src/core/server/elasticsearch/default_headers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { getReservedHeaders, PRODUCT_ORIGIN_HEADER } from './default_headers';

describe('getReservedHeaders', () => {
it('returns the list of reserved headers contained in a list', () => {
expect(getReservedHeaders(['foo', 'bar', PRODUCT_ORIGIN_HEADER])).toEqual([
PRODUCT_ORIGIN_HEADER,
]);
});

it('ignores the case when identifying headers', () => {
expect(getReservedHeaders(['foo', 'bar', PRODUCT_ORIGIN_HEADER.toUpperCase()])).toEqual([
PRODUCT_ORIGIN_HEADER.toUpperCase(),
]);
});
});
19 changes: 16 additions & 3 deletions src/core/server/elasticsearch/default_headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,22 @@

import { deepFreeze } from '@kbn/std';

export const PRODUCT_ORIGIN_HEADER = 'x-elastic-product-origin';

export const RESERVED_HEADERS = deepFreeze([PRODUCT_ORIGIN_HEADER]);

export const DEFAULT_HEADERS = deepFreeze({
// Elasticsearch uses this to identify when a request is coming from Kibana, to allow Kibana to
// access system indices using the standard ES APIs without logging a warning. After migrating to
// use the new system index APIs, this header can be removed.
'x-elastic-product-origin': 'kibana',
// access system indices using the standard ES APIs.
[PRODUCT_ORIGIN_HEADER]: 'kibana',
});

export const getReservedHeaders = (headerNames: string[]): string[] => {
const reservedHeaders = [];
for (const headerName of headerNames) {
if (RESERVED_HEADERS.includes(headerName.toLowerCase())) {
reservedHeaders.push(headerName);
}
}
return reservedHeaders;
};
29 changes: 29 additions & 0 deletions src/core/server/elasticsearch/elasticsearch_config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,35 @@ test('#requestHeadersWhitelist accepts both string and array of strings', () =>
expect(configValue.requestHeadersWhitelist).toEqual(['token', 'X-Forwarded-Proto']);
});

describe('reserved headers', () => {
test('throws if customHeaders contains reserved headers', () => {
expect(() => {
config.schema.validate({
customHeaders: { foo: 'bar', 'x-elastic-product-origin': 'beats' },
});
}).toThrowErrorMatchingInlineSnapshot(
`"[customHeaders]: cannot use reserved headers: [x-elastic-product-origin]"`
);
});

test('throws if requestHeadersWhitelist contains reserved headers', () => {
expect(() => {
config.schema.validate({ requestHeadersWhitelist: ['foo', 'x-elastic-product-origin'] });
}).toThrowErrorMatchingInlineSnapshot(`
"[requestHeadersWhitelist]: types that failed validation:
- [requestHeadersWhitelist.0]: expected value of type [string] but got [Array]
- [requestHeadersWhitelist.1]: cannot use reserved headers: [x-elastic-product-origin]"
`);
expect(() => {
config.schema.validate({ requestHeadersWhitelist: 'x-elastic-product-origin' });
}).toThrowErrorMatchingInlineSnapshot(`
"[requestHeadersWhitelist]: types that failed validation:
- [requestHeadersWhitelist.0]: cannot use reserved headers: [x-elastic-product-origin]
- [requestHeadersWhitelist.1]: could not parse array value from json input"
`);
});
});

describe('reads files', () => {
beforeEach(() => {
mockReadFileSync.mockReset();
Expand Down
39 changes: 36 additions & 3 deletions src/core/server/elasticsearch/elasticsearch_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { readFileSync } from 'fs';
import { ConfigDeprecationProvider } from 'src/core/server';
import { readPkcs12Keystore, readPkcs12Truststore } from '../utils';
import { ServiceConfigDescriptor } from '../internal_types';
import { getReservedHeaders } from './default_headers';

const hostURISchema = schema.uri({ scheme: ['http', 'https'] });

Expand Down Expand Up @@ -52,10 +53,42 @@ export const configSchema = schema.object({
)
),
password: schema.maybe(schema.string()),
requestHeadersWhitelist: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], {
defaultValue: ['authorization'],
requestHeadersWhitelist: schema.oneOf(
[
schema.string({
// can't use `validate` option on union types, forced to validate each individual subtypes
// see https://github.com/elastic/kibana/issues/64906
validate: (headersWhitelist) => {
const reservedHeaders = getReservedHeaders([headersWhitelist]);
if (reservedHeaders.length) {
return `cannot use reserved headers: [${reservedHeaders.join(', ')}]`;
}
},
}),
schema.arrayOf(schema.string(), {
// can't use `validate` option on union types, forced to validate each individual subtypes
// see https://github.com/elastic/kibana/issues/64906
validate: (headersWhitelist) => {
const reservedHeaders = getReservedHeaders(headersWhitelist);
if (reservedHeaders.length) {
return `cannot use reserved headers: [${reservedHeaders.join(', ')}]`;
}
},
}),
],
{
defaultValue: ['authorization'],
}
),
customHeaders: schema.recordOf(schema.string(), schema.string(), {
defaultValue: {},
validate: (customHeaders) => {
const reservedHeaders = getReservedHeaders(Object.keys(customHeaders));
if (reservedHeaders.length) {
return `cannot use reserved headers: [${reservedHeaders.join(', ')}]`;
}
},
}),
customHeaders: schema.recordOf(schema.string(), schema.string(), { defaultValue: {} }),
shardTimeout: schema.duration({ defaultValue: '30s' }),
requestTimeout: schema.duration({ defaultValue: '30s' }),
pingTimeout: schema.duration({ defaultValue: schema.siblingRef('requestTimeout') }),
Expand Down

0 comments on commit 321b8bf

Please sign in to comment.