mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 11:05:39 -04:00
[Elasticsearch] Redact logs from known APIs (#153049)
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e6d03fe356
commit
5142d73243
12 changed files with 535 additions and 38 deletions
|
@ -10,7 +10,11 @@ export { ScopedClusterClient } from './src/scoped_cluster_client';
|
||||||
export { ClusterClient } from './src/cluster_client';
|
export { ClusterClient } from './src/cluster_client';
|
||||||
export { configureClient } from './src/configure_client';
|
export { configureClient } from './src/configure_client';
|
||||||
export { type AgentStatsProvider, AgentManager, type NetworkAgent } from './src/agent_manager';
|
export { type AgentStatsProvider, AgentManager, type NetworkAgent } from './src/agent_manager';
|
||||||
export { getRequestDebugMeta, getErrorMessage } from './src/log_query_and_deprecation';
|
export {
|
||||||
|
type RequestDebugMeta,
|
||||||
|
getRequestDebugMeta,
|
||||||
|
getErrorMessage,
|
||||||
|
} from './src/log_query_and_deprecation';
|
||||||
export {
|
export {
|
||||||
PRODUCT_RESPONSE_HEADER,
|
PRODUCT_RESPONSE_HEADER,
|
||||||
DEFAULT_HEADERS,
|
DEFAULT_HEADERS,
|
||||||
|
|
|
@ -212,6 +212,7 @@ describe('configureClient', () => {
|
||||||
logger,
|
logger,
|
||||||
client,
|
client,
|
||||||
type: 'test',
|
type: 'test',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,7 +45,8 @@ export const configureClient = (
|
||||||
ConnectionPool: ClusterConnectionPool,
|
ConnectionPool: ClusterConnectionPool,
|
||||||
});
|
});
|
||||||
|
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type });
|
const { apisToRedactInLogs = [] } = config;
|
||||||
|
instrumentEsQueryAndDeprecationLogger({ logger, client, type, apisToRedactInLogs });
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
|
@ -96,13 +96,23 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
it('creates a query logger context based on the `type` parameter', () => {
|
it('creates a query logger context based on the `type` parameter', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test123' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test123',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
expect(logger.get).toHaveBeenCalledWith('query', 'test123');
|
expect(logger.get).toHaveBeenCalledWith('query', 'test123');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('logs each query', () => {
|
describe('logs each query', () => {
|
||||||
it('when request body is an object', () => {
|
it('when request body is an object', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createResponseWithBody({
|
const response = createResponseWithBody({
|
||||||
seq_no_primary_term: true,
|
seq_no_primary_term: true,
|
||||||
|
@ -120,7 +130,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when request body is a string', () => {
|
it('when request body is a string', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createResponseWithBody(
|
const response = createResponseWithBody(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
@ -140,7 +155,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when request body is a buffer', () => {
|
it('when request body is a buffer', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createResponseWithBody(
|
const response = createResponseWithBody(
|
||||||
Buffer.from(
|
Buffer.from(
|
||||||
|
@ -162,7 +182,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when request body is a readable stream', () => {
|
it('when request body is a readable stream', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createResponseWithBody(
|
const response = createResponseWithBody(
|
||||||
Readable.from(
|
Readable.from(
|
||||||
|
@ -184,7 +209,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when request body is not defined', () => {
|
it('when request body is not defined', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createResponseWithBody();
|
const response = createResponseWithBody();
|
||||||
|
|
||||||
|
@ -196,7 +226,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('properly encode queries', () => {
|
it('properly encode queries', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
body: {},
|
body: {},
|
||||||
|
@ -217,7 +252,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs queries even in case of errors', () => {
|
it('logs queries even in case of errors', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
|
@ -248,7 +288,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs debug when the client emits an @elastic/elasticsearch error', () => {
|
it('logs debug when the client emits an @elastic/elasticsearch error', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({ body: {} });
|
const response = createApiResponse({ body: {} });
|
||||||
client.diagnostic.emit('response', new errors.TimeoutError('message', response), response);
|
client.diagnostic.emit('response', new errors.TimeoutError('message', response), response);
|
||||||
|
@ -259,7 +304,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs debug when the client emits an ResponseError returned by elasticsearch', () => {
|
it('logs debug when the client emits an ResponseError returned by elasticsearch', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
|
@ -285,7 +335,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs default error info when the error response body is empty', () => {
|
it('logs default error info when the error response body is empty', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
let response: DiagnosticResult<any, any> = createApiResponse({
|
let response: DiagnosticResult<any, any> = createApiResponse({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
|
@ -325,7 +380,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds meta information to logs', () => {
|
it('adds meta information to logs', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
let response = createApiResponse({
|
let response = createApiResponse({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
|
@ -407,7 +467,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs response size', () => {
|
it('logs response size', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createResponseWithBody(
|
const response = createResponseWithBody(
|
||||||
{
|
{
|
||||||
|
@ -432,7 +497,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
|
|
||||||
describe('deprecation warnings from response headers', () => {
|
describe('deprecation warnings from response headers', () => {
|
||||||
it('does not log when no deprecation warning header is returned', () => {
|
it('does not log when no deprecation warning header is returned', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
@ -458,7 +528,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not log when warning header comes from a warn-agent that is not elasticsearch', () => {
|
it('does not log when warning header comes from a warn-agent that is not elasticsearch', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
@ -487,7 +562,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs error when the client receives an Elasticsearch error response for a deprecated request originating from a user', () => {
|
it('logs error when the client receives an Elasticsearch error response for a deprecated request originating from a user', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
|
@ -519,7 +599,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs warning when the client receives an Elasticsearch error response for a deprecated request originating from kibana', () => {
|
it('logs warning when the client receives an Elasticsearch error response for a deprecated request originating from kibana', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
|
@ -552,7 +637,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs error when the client receives an Elasticsearch success response for a deprecated request originating from a user', () => {
|
it('logs error when the client receives an Elasticsearch success response for a deprecated request originating from a user', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
@ -584,7 +674,12 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('logs warning when the client receives an Elasticsearch success response for a deprecated request originating from kibana', () => {
|
it('logs warning when the client receives an Elasticsearch success response for a deprecated request originating from kibana', () => {
|
||||||
instrumentEsQueryAndDeprecationLogger({ logger, client, type: 'test type' });
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
|
||||||
const response = createApiResponse({
|
const response = createApiResponse({
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
@ -616,5 +711,287 @@ describe('instrumentQueryAndDeprecationLogger', () => {
|
||||||
/Query:\n.*200\n.*GET \/_path\?hello\=dolly/
|
/Query:\n.*200\n.*GET \/_path\?hello\=dolly/
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Request body redaction on some APIs', () => {
|
||||||
|
it('redacts for an API in the extended list (path only)', () => {
|
||||||
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [{ path: '/foo' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = createApiResponse({
|
||||||
|
body: {},
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {},
|
||||||
|
params: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/foo',
|
||||||
|
querystring: { hello: 'dolly' },
|
||||||
|
body: {
|
||||||
|
seq_no_primary_term: true,
|
||||||
|
query: {
|
||||||
|
term: { user: 'kimchy' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
GET /foo?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('redacts for an API that is contained by the declared path (path only)', () => {
|
||||||
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [{ path: '/foo' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = createApiResponse({
|
||||||
|
body: {},
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {},
|
||||||
|
params: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/foo/something/something-else',
|
||||||
|
querystring: { hello: 'dolly' },
|
||||||
|
body: {
|
||||||
|
seq_no_primary_term: true,
|
||||||
|
query: {
|
||||||
|
term: { user: 'kimchy' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
GET /foo/something/something-else?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('redacts for an API in the extended list (method and path)', () => {
|
||||||
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [{ method: 'GET', path: '/foo' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = createApiResponse({
|
||||||
|
body: {},
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {},
|
||||||
|
params: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/foo',
|
||||||
|
querystring: { hello: 'dolly' },
|
||||||
|
body: {
|
||||||
|
seq_no_primary_term: true,
|
||||||
|
query: {
|
||||||
|
term: { user: 'kimchy' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
GET /foo?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not redact for an API in the extended list when method does not match', () => {
|
||||||
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [{ method: 'PUT', path: '/foo' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = createApiResponse({
|
||||||
|
body: {},
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {},
|
||||||
|
params: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/foo',
|
||||||
|
querystring: { hello: 'dolly' },
|
||||||
|
body: {
|
||||||
|
seq_no_primary_term: true,
|
||||||
|
query: {
|
||||||
|
term: { user: 'kimchy' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
GET /foo?hello=dolly
|
||||||
|
{\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not redact for an API in the extended list when path does not match', () => {
|
||||||
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [{ path: '/foo' }],
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = createApiResponse({
|
||||||
|
body: {},
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {},
|
||||||
|
params: {
|
||||||
|
method: 'GET',
|
||||||
|
path: '/bar',
|
||||||
|
querystring: { hello: 'dolly' },
|
||||||
|
body: {
|
||||||
|
seq_no_primary_term: true,
|
||||||
|
query: {
|
||||||
|
term: { user: 'kimchy' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
GET /bar?hello=dolly
|
||||||
|
{\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Known list', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
instrumentEsQueryAndDeprecationLogger({
|
||||||
|
logger,
|
||||||
|
client,
|
||||||
|
type: 'test type',
|
||||||
|
apisToRedactInLogs: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createResponseWithPath(path: string, method: string = '*') {
|
||||||
|
return createApiResponse({
|
||||||
|
body: {},
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {},
|
||||||
|
params: {
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
querystring: { hello: 'dolly' },
|
||||||
|
body: { super_secret: 'stuff' },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('[*] /_security/', () => {
|
||||||
|
const response = createResponseWithPath('/_security/something');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
* /_security/something?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[*] /_xpack/security/', () => {
|
||||||
|
const response = createResponseWithPath('/_xpack/security/something');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
* /_xpack/security/something?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[POST] /_reindex', () => {
|
||||||
|
const response = createResponseWithPath('/_reindex', 'POST');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
POST /_reindex?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[PUT] /_watcher/watch', () => {
|
||||||
|
const response = createResponseWithPath('/_watcher/watch', 'PUT');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
PUT /_watcher/watch?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[PUT] /_xpack/watcher/watch', () => {
|
||||||
|
const response = createResponseWithPath('/_xpack/watcher/watch', 'PUT');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
PUT /_xpack/watcher/watch?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[PUT] /_snapshot/something', () => {
|
||||||
|
const response = createResponseWithPath('/_snapshot/something', 'PUT');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
PUT /_snapshot/something?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[PUT] /_logstash/pipeline/something', () => {
|
||||||
|
const response = createResponseWithPath('/_logstash/pipeline/something', 'PUT');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
PUT /_logstash/pipeline/something?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[POST] /_nodes/reload_secure_settings', () => {
|
||||||
|
const response = createResponseWithPath('/_nodes/reload_secure_settings', 'POST');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
POST /_nodes/reload_secure_settings?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('[POST] /_nodes/*/reload_secure_settings', () => {
|
||||||
|
const response = createResponseWithPath('/_nodes/node-id/reload_secure_settings', 'POST');
|
||||||
|
client.diagnostic.emit('response', null, response);
|
||||||
|
expect(loggingSystemMock.collect(logger).debug[0][0]).toMatchInlineSnapshot(`
|
||||||
|
"200
|
||||||
|
POST /_nodes/node-id/reload_secure_settings?hello=dolly
|
||||||
|
[redacted]"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,8 +13,62 @@ import { errors, DiagnosticResult, RequestBody, Client } from '@elastic/elastics
|
||||||
import numeral from '@elastic/numeral';
|
import numeral from '@elastic/numeral';
|
||||||
import type { Logger } from '@kbn/logging';
|
import type { Logger } from '@kbn/logging';
|
||||||
import type { ElasticsearchErrorDetails } from '@kbn/es-errors';
|
import type { ElasticsearchErrorDetails } from '@kbn/es-errors';
|
||||||
|
import type { ElasticsearchApiToRedactInLogs } from '@kbn/core-elasticsearch-server';
|
||||||
import { getEcsResponseLog } from './get_ecs_response_log';
|
import { getEcsResponseLog } from './get_ecs_response_log';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The logger-relevant request meta of an ES request
|
||||||
|
*/
|
||||||
|
export interface RequestDebugMeta {
|
||||||
|
/**
|
||||||
|
* The requested method
|
||||||
|
*/
|
||||||
|
method: string;
|
||||||
|
/**
|
||||||
|
* The requested endpoint + querystring
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
/**
|
||||||
|
* The request body (it may be redacted)
|
||||||
|
*/
|
||||||
|
body: string;
|
||||||
|
/**
|
||||||
|
* The status code of the response
|
||||||
|
*/
|
||||||
|
statusCode: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Known list of APIs that should redact the request body in the logs
|
||||||
|
*/
|
||||||
|
const APIS_TO_REDACT_IN_LOGS: ElasticsearchApiToRedactInLogs[] = [
|
||||||
|
{ path: '/_security/' },
|
||||||
|
{ path: '/_xpack/security/' },
|
||||||
|
{ method: 'POST', path: '/_reindex' },
|
||||||
|
{ method: 'PUT', path: '/_watcher/watch' },
|
||||||
|
{ method: 'PUT', path: '/_xpack/watcher/watch' },
|
||||||
|
{ method: 'PUT', path: '/_snapshot/' },
|
||||||
|
{ method: 'PUT', path: '/_logstash/pipeline/' },
|
||||||
|
{ method: 'POST', path: '/_nodes/reload_secure_settings' },
|
||||||
|
{ method: 'POST', path: /\/_nodes\/.+\/reload_secure_settings/ },
|
||||||
|
];
|
||||||
|
|
||||||
|
function shouldRedactBodyInLogs(
|
||||||
|
requestDebugMeta: RequestDebugMeta,
|
||||||
|
extendedList: ElasticsearchApiToRedactInLogs[] = []
|
||||||
|
) {
|
||||||
|
return [...APIS_TO_REDACT_IN_LOGS, ...extendedList].some(({ path, method }) => {
|
||||||
|
if (!method || method === requestDebugMeta.method) {
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
return requestDebugMeta.url.includes(path);
|
||||||
|
} else {
|
||||||
|
return path.test(requestDebugMeta.url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const convertQueryString = (qs: string | Record<string, any> | undefined): string => {
|
const convertQueryString = (qs: string | Record<string, any> | undefined): string => {
|
||||||
if (qs === undefined || typeof qs === 'string') {
|
if (qs === undefined || typeof qs === 'string') {
|
||||||
return qs ?? '';
|
return qs ?? '';
|
||||||
|
@ -58,35 +112,43 @@ function getContentLength(headers?: IncomingHttpHeaders): number | undefined {
|
||||||
*
|
*
|
||||||
* so it could be copy-pasted into the Dev console
|
* so it could be copy-pasted into the Dev console
|
||||||
*/
|
*/
|
||||||
function getResponseMessage(event: DiagnosticResult, bytesMsg: string): string {
|
function getResponseMessage(
|
||||||
const errorMeta = getRequestDebugMeta(event);
|
event: DiagnosticResult,
|
||||||
const body = errorMeta.body ? `\n${errorMeta.body}` : '';
|
bytesMsg: string,
|
||||||
return `${errorMeta.statusCode}${bytesMsg}\n${errorMeta.method} ${errorMeta.url}${body}`;
|
apisToRedactInLogs: ElasticsearchApiToRedactInLogs[]
|
||||||
|
): string {
|
||||||
|
const debugMeta = getRequestDebugMeta(event, apisToRedactInLogs);
|
||||||
|
const body = debugMeta.body ? `\n${debugMeta.body}` : '';
|
||||||
|
return `${debugMeta.statusCode}${bytesMsg}\n${debugMeta.method} ${debugMeta.url}${body}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns stringified debug information from an Elasticsearch request event
|
* Returns stringified debug information from an Elasticsearch request event
|
||||||
* useful for logging in case of an unexpected failure.
|
* useful for logging in case of an unexpected failure.
|
||||||
*/
|
*/
|
||||||
export function getRequestDebugMeta(event: DiagnosticResult): {
|
export function getRequestDebugMeta(
|
||||||
url: string;
|
event: DiagnosticResult,
|
||||||
body: string;
|
apisToRedactInLogs?: ElasticsearchApiToRedactInLogs[]
|
||||||
statusCode: number | null;
|
): RequestDebugMeta {
|
||||||
method: string;
|
|
||||||
} {
|
|
||||||
const params = event.meta.request.params;
|
const params = event.meta.request.params;
|
||||||
// definition is wrong, `params.querystring` can be either a string or an object
|
// definition is wrong, `params.querystring` can be either a string or an object
|
||||||
const querystring = convertQueryString(params.querystring);
|
const querystring = convertQueryString(params.querystring);
|
||||||
return {
|
|
||||||
|
const debugMeta: RequestDebugMeta = {
|
||||||
url: `${params.path}${querystring ? `?${querystring}` : ''}`,
|
url: `${params.path}${querystring ? `?${querystring}` : ''}`,
|
||||||
body: params.body ? `${ensureString(params.body)}` : '',
|
body: params.body ? `${ensureString(params.body)}` : '',
|
||||||
method: params.method,
|
method: params.method,
|
||||||
statusCode: event.statusCode!,
|
statusCode: event.statusCode!,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Some known APIs may contain sensitive information in the request body that we don't want to expose to the logs.
|
||||||
|
return shouldRedactBodyInLogs(debugMeta, apisToRedactInLogs)
|
||||||
|
? { ...debugMeta, body: '[redacted]' }
|
||||||
|
: debugMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** HTTP Warning headers have the following syntax:
|
/** HTTP Warning headers have the following syntax:
|
||||||
* <warn-code> <warn-agent> <warn-text> (where warn-code is a three digit number)
|
* <warn-code> <warn-agent> <warn-text> (where warn-code is a three-digit number)
|
||||||
* This function tests if a warning comes from an Elasticsearch warn-agent
|
* This function tests if a warning comes from an Elasticsearch warn-agent
|
||||||
* */
|
* */
|
||||||
const isEsWarning = (warning: string) => /\d\d\d Elasticsearch-/.test(warning);
|
const isEsWarning = (warning: string) => /\d\d\d Elasticsearch-/.test(warning);
|
||||||
|
@ -95,10 +157,12 @@ export const instrumentEsQueryAndDeprecationLogger = ({
|
||||||
logger,
|
logger,
|
||||||
client,
|
client,
|
||||||
type,
|
type,
|
||||||
|
apisToRedactInLogs,
|
||||||
}: {
|
}: {
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
client: Client;
|
client: Client;
|
||||||
type: string;
|
type: string;
|
||||||
|
apisToRedactInLogs: ElasticsearchApiToRedactInLogs[];
|
||||||
}) => {
|
}) => {
|
||||||
const queryLogger = logger.get('query', type);
|
const queryLogger = logger.get('query', type);
|
||||||
const deprecationLogger = logger.get('deprecation');
|
const deprecationLogger = logger.get('deprecation');
|
||||||
|
@ -111,12 +175,14 @@ export const instrumentEsQueryAndDeprecationLogger = ({
|
||||||
let queryMsg = '';
|
let queryMsg = '';
|
||||||
if (error) {
|
if (error) {
|
||||||
if (error instanceof errors.ResponseError) {
|
if (error instanceof errors.ResponseError) {
|
||||||
queryMsg = `${getResponseMessage(event, bytesMsg)} ${getErrorMessage(error)}`;
|
queryMsg = `${getResponseMessage(event, bytesMsg, apisToRedactInLogs)} ${getErrorMessage(
|
||||||
|
error
|
||||||
|
)}`;
|
||||||
} else {
|
} else {
|
||||||
queryMsg = getErrorMessage(error);
|
queryMsg = getErrorMessage(error);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
queryMsg = getResponseMessage(event, bytesMsg);
|
queryMsg = getResponseMessage(event, bytesMsg, apisToRedactInLogs);
|
||||||
}
|
}
|
||||||
|
|
||||||
queryLogger.debug(queryMsg, meta);
|
queryLogger.debug(queryMsg, meta);
|
||||||
|
@ -137,7 +203,7 @@ export const instrumentEsQueryAndDeprecationLogger = ({
|
||||||
? 'kibana'
|
? 'kibana'
|
||||||
: 'user';
|
: 'user';
|
||||||
|
|
||||||
// Strip the first 5 stack trace lines as these are irrelavent to finding the call site
|
// Strip the first 5 stack trace lines as these are irrelevant to finding the call site
|
||||||
const stackTrace = new Error().stack?.split('\n').slice(5).join('\n');
|
const stackTrace = new Error().stack?.split('\n').slice(5).join('\n');
|
||||||
|
|
||||||
deprecationLogger.debug(
|
deprecationLogger.debug(
|
||||||
|
|
|
@ -30,6 +30,7 @@ test('set correct defaults', () => {
|
||||||
expect(configValue).toMatchInlineSnapshot(`
|
expect(configValue).toMatchInlineSnapshot(`
|
||||||
ElasticsearchConfig {
|
ElasticsearchConfig {
|
||||||
"apiVersion": "master",
|
"apiVersion": "master",
|
||||||
|
"apisToRedactInLogs": Array [],
|
||||||
"compression": false,
|
"compression": false,
|
||||||
"customHeaders": Object {},
|
"customHeaders": Object {},
|
||||||
"healthCheckDelay": "PT2.5S",
|
"healthCheckDelay": "PT2.5S",
|
||||||
|
|
|
@ -13,7 +13,11 @@ import { Duration } from 'moment';
|
||||||
import { readFileSync } from 'fs';
|
import { readFileSync } from 'fs';
|
||||||
import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal';
|
import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal';
|
||||||
import type { ConfigDeprecationProvider } from '@kbn/config';
|
import type { ConfigDeprecationProvider } from '@kbn/config';
|
||||||
import type { IElasticsearchConfig, ElasticsearchSslConfig } from '@kbn/core-elasticsearch-server';
|
import type {
|
||||||
|
IElasticsearchConfig,
|
||||||
|
ElasticsearchSslConfig,
|
||||||
|
ElasticsearchApiToRedactInLogs,
|
||||||
|
} from '@kbn/core-elasticsearch-server';
|
||||||
import { getReservedHeaders } from './default_headers';
|
import { getReservedHeaders } from './default_headers';
|
||||||
|
|
||||||
const hostURISchema = schema.uri({ scheme: ['http', 'https'] });
|
const hostURISchema = schema.uri({ scheme: ['http', 'https'] });
|
||||||
|
@ -169,6 +173,13 @@ export const configSchema = schema.object({
|
||||||
}),
|
}),
|
||||||
schema.boolean({ defaultValue: false })
|
schema.boolean({ defaultValue: false })
|
||||||
),
|
),
|
||||||
|
apisToRedactInLogs: schema.arrayOf(
|
||||||
|
schema.object({
|
||||||
|
path: schema.string(),
|
||||||
|
method: schema.maybe(schema.string()),
|
||||||
|
}),
|
||||||
|
{ defaultValue: [] }
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const deprecations: ConfigDeprecationProvider = () => [
|
const deprecations: ConfigDeprecationProvider = () => [
|
||||||
|
@ -402,6 +413,11 @@ export class ElasticsearchConfig implements IElasticsearchConfig {
|
||||||
*/
|
*/
|
||||||
public readonly customHeaders: ElasticsearchConfigType['customHeaders'];
|
public readonly customHeaders: ElasticsearchConfigType['customHeaders'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends the list of APIs that should be redacted in logs.
|
||||||
|
*/
|
||||||
|
public readonly apisToRedactInLogs: ElasticsearchApiToRedactInLogs[];
|
||||||
|
|
||||||
constructor(rawConfig: ElasticsearchConfigType) {
|
constructor(rawConfig: ElasticsearchConfigType) {
|
||||||
this.ignoreVersionMismatch = rawConfig.ignoreVersionMismatch;
|
this.ignoreVersionMismatch = rawConfig.ignoreVersionMismatch;
|
||||||
this.apiVersion = rawConfig.apiVersion;
|
this.apiVersion = rawConfig.apiVersion;
|
||||||
|
@ -425,6 +441,7 @@ export class ElasticsearchConfig implements IElasticsearchConfig {
|
||||||
this.idleSocketTimeout = rawConfig.idleSocketTimeout;
|
this.idleSocketTimeout = rawConfig.idleSocketTimeout;
|
||||||
this.compression = rawConfig.compression;
|
this.compression = rawConfig.compression;
|
||||||
this.skipStartupConnectionCheck = rawConfig.skipStartupConnectionCheck;
|
this.skipStartupConnectionCheck = rawConfig.skipStartupConnectionCheck;
|
||||||
|
this.apisToRedactInLogs = rawConfig.apisToRedactInLogs;
|
||||||
|
|
||||||
const { alwaysPresentCertificate, verificationMode } = rawConfig.ssl;
|
const { alwaysPresentCertificate, verificationMode } = rawConfig.ssl;
|
||||||
const { key, keyPassphrase, certificate, certificateAuthorities } = readKeyAndCerts(rawConfig);
|
const { key, keyPassphrase, certificate, certificateAuthorities } = readKeyAndCerts(rawConfig);
|
||||||
|
|
|
@ -22,6 +22,7 @@ export type {
|
||||||
FakeRequest,
|
FakeRequest,
|
||||||
ElasticsearchClientSslConfig,
|
ElasticsearchClientSslConfig,
|
||||||
ElasticsearchClientConfig,
|
ElasticsearchClientConfig,
|
||||||
|
ElasticsearchApiToRedactInLogs,
|
||||||
} from './src/client';
|
} from './src/client';
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
|
|
|
@ -8,6 +8,23 @@
|
||||||
|
|
||||||
import type { Duration } from 'moment';
|
import type { Duration } from 'moment';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definition of an API that should redact the requested body in the logs
|
||||||
|
*/
|
||||||
|
export interface ElasticsearchApiToRedactInLogs {
|
||||||
|
/**
|
||||||
|
* The ES path.
|
||||||
|
* - If specified as a string, it'll be checked as `contains`.
|
||||||
|
* - If specified as a RegExp, it'll be tested against the path.
|
||||||
|
*/
|
||||||
|
path: string | RegExp;
|
||||||
|
/**
|
||||||
|
* HTTP method.
|
||||||
|
* If not provided, the path will be checked for all methods.
|
||||||
|
*/
|
||||||
|
method?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration options to be used to create a {@link IClusterClient | cluster client}
|
* Configuration options to be used to create a {@link IClusterClient | cluster client}
|
||||||
*
|
*
|
||||||
|
@ -32,6 +49,7 @@ export interface ElasticsearchClientConfig {
|
||||||
requestTimeout?: Duration | number;
|
requestTimeout?: Duration | number;
|
||||||
caFingerprint?: string;
|
caFingerprint?: string;
|
||||||
ssl?: ElasticsearchClientSslConfig;
|
ssl?: ElasticsearchClientSslConfig;
|
||||||
|
apisToRedactInLogs?: ElasticsearchApiToRedactInLogs[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,4 +19,8 @@ export type {
|
||||||
UnauthorizedErrorHandlerRetryResult,
|
UnauthorizedErrorHandlerRetryResult,
|
||||||
UnauthorizedErrorHandlerNotHandledResult,
|
UnauthorizedErrorHandlerNotHandledResult,
|
||||||
} from './unauthorized_error_handler';
|
} from './unauthorized_error_handler';
|
||||||
export type { ElasticsearchClientConfig, ElasticsearchClientSslConfig } from './client_config';
|
export type {
|
||||||
|
ElasticsearchClientConfig,
|
||||||
|
ElasticsearchClientSslConfig,
|
||||||
|
ElasticsearchApiToRedactInLogs,
|
||||||
|
} from './client_config';
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Duration } from 'moment';
|
import type { Duration } from 'moment';
|
||||||
|
import type { ElasticsearchApiToRedactInLogs } from './client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
|
@ -139,6 +140,11 @@ export interface IElasticsearchConfig {
|
||||||
* either `certificate` or `full`.
|
* either `certificate` or `full`.
|
||||||
*/
|
*/
|
||||||
readonly ssl: ElasticsearchSslConfig;
|
readonly ssl: ElasticsearchSslConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends the list of APIs that should be redacted in logs.
|
||||||
|
*/
|
||||||
|
readonly apisToRedactInLogs: ElasticsearchApiToRedactInLogs[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -55,6 +55,7 @@ describe('config schema', () => {
|
||||||
"debug_mode": false,
|
"debug_mode": false,
|
||||||
"elasticsearch": Object {
|
"elasticsearch": Object {
|
||||||
"apiVersion": "master",
|
"apiVersion": "master",
|
||||||
|
"apisToRedactInLogs": Array [],
|
||||||
"compression": false,
|
"compression": false,
|
||||||
"customHeaders": Object {},
|
"customHeaders": Object {},
|
||||||
"healthCheck": Object {
|
"healthCheck": Object {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue