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';
|
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({
|
export const DEFAULT_HEADERS = deepFreeze({
|
||||||
// Elasticsearch uses this to identify when a request is coming from Kibana, to allow Kibana to
|
// 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
|
// access system indices using the standard ES APIs.
|
||||||
// use the new system index APIs, this header can be removed.
|
[PRODUCT_ORIGIN_HEADER]: 'kibana',
|
||||||
'x-elastic-product-origin': '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']);
|
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', () => {
|
describe('reads files', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockReadFileSync.mockReset();
|
mockReadFileSync.mockReset();
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { readFileSync } from 'fs';
|
||||||
import { ConfigDeprecationProvider } from 'src/core/server';
|
import { ConfigDeprecationProvider } from 'src/core/server';
|
||||||
import { readPkcs12Keystore, readPkcs12Truststore } from '../utils';
|
import { readPkcs12Keystore, readPkcs12Truststore } from '../utils';
|
||||||
import { ServiceConfigDescriptor } from '../internal_types';
|
import { ServiceConfigDescriptor } from '../internal_types';
|
||||||
|
import { getReservedHeaders } from './default_headers';
|
||||||
|
|
||||||
const hostURISchema = schema.uri({ scheme: ['http', 'https'] });
|
const hostURISchema = schema.uri({ scheme: ['http', 'https'] });
|
||||||
|
|
||||||
|
@ -52,10 +53,42 @@ export const configSchema = schema.object({
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
password: schema.maybe(schema.string()),
|
password: schema.maybe(schema.string()),
|
||||||
requestHeadersWhitelist: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], {
|
requestHeadersWhitelist: schema.oneOf(
|
||||||
defaultValue: ['authorization'],
|
[
|
||||||
|
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' }),
|
shardTimeout: schema.duration({ defaultValue: '30s' }),
|
||||||
requestTimeout: schema.duration({ defaultValue: '30s' }),
|
requestTimeout: schema.duration({ defaultValue: '30s' }),
|
||||||
pingTimeout: schema.duration({ defaultValue: schema.siblingRef('requestTimeout') }),
|
pingTimeout: schema.duration({ defaultValue: schema.siblingRef('requestTimeout') }),
|
||||||
|
|
Loading…
Reference in a new issue