forbid x-elastic-product-origin header in elasticsearch configuration (#92359)

This commit is contained in:
Pierre Gayvallet 2021-02-25 08:20:01 +01:00 committed by GitHub
parent f67fef9e50
commit 321b8bf052
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 104 additions and 6 deletions

View file

@ -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(),
]);
});
});

View file

@ -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;
};

View file

@ -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();

View file

@ -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'] });
@ -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') }),