forbid x-elastic-product-origin header in elasticsearch configuration (#92359)
This commit is contained in:
parent
f67fef9e50
commit
321b8bf052
23
src/core/server/elasticsearch/default_headers.test.ts
Normal file
23
src/core/server/elasticsearch/default_headers.test.ts
Normal 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(),
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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') }),
|
||||
|
|
Loading…
Reference in a new issue