diff --git a/docs/advanced-config.asciidoc b/docs/advanced-config.asciidoc index 638aeada4..167061dce 100644 --- a/docs/advanced-config.asciidoc +++ b/docs/advanced-config.asciidoc @@ -91,6 +91,94 @@ const client = new Client({ }) ---- +[discrete] +==== Redaction of potentially sensitive data + +When the client raises an `Error` that originated at the HTTP layer, like a `ConnectionError` or `TimeoutError`, a `meta` object is often attached to the error object that includes metadata useful for debugging, like request and response information. Because this can include potentially sensitive data, like authentication secrets in an `Authorization` header, the client takes measures to redact common sources of sensitive data when this metadata is attached and serialized. + +If your configuration requires extra headers or other configurations that may include sensitive data, you may want to adjust these settings to account for that. + +By default, the `redaction` option is set to `{ type: 'replace' }`, which recursively searches for sensitive key names, case insensitive, and replaces their values with the string `[redacted]`. + +[source,js] +---- +const { Client } = require('@elastic/elasticsearch') + +const client = new Client({ + cloud: { id: '' }, + auth: { apiKey: 'base64EncodedKey' }, +}) + +try { + await client.indices.create({ index: 'my_index' }) +} catch (err) { + console.log(err.meta.meta.request.options.headers.authorization) // prints "[redacted]" +} +---- + +If you would like to redact additional properties, you can include additional key names to search and replace: + +[source,js] +---- +const { Client } = require('@elastic/elasticsearch') + +const client = new Client({ + cloud: { id: '' }, + auth: { apiKey: 'base64EncodedKey' }, + headers: { 'X-My-Secret-Password': 'shhh it's a secret!' }, + redaction: { + type: "replace", + additionalKeys: ["x-my-secret-password"] + } +}) + +try { + await client.indices.create({ index: 'my_index' }) +} catch (err) { + console.log(err.meta.meta.request.options.headers['X-My-Secret-Password']) // prints "[redacted]" +} +---- + +Alternatively, if you know you're not going to use the metadata at all, setting the redaction type to `remove` will remove all optional sources of potentially sensitive data entirely, or replacing them with `null` for required properties. + +[source,js] +---- +const { Client } = require('@elastic/elasticsearch') + +const client = new Client({ + cloud: { id: '' }, + auth: { apiKey: 'base64EncodedKey' }, + redaction: { type: "remove" } +}) + +try { + await client.indices.create({ index: 'my_index' }) +} catch (err) { + console.log(err.meta.meta.request.options.headers) // undefined +} +---- + +Finally, if you prefer to turn off redaction altogether, perhaps while debugging on a local developer environment, you can set the redaction type to `off`. This will revert the client to pre-8.11.0 behavior, where basic redaction is only performed during common serialization methods like `console.log` and `JSON.stringify`. + +WARNING: Setting `redaction.type` to `off` is not recommended in production environments. + +[source,js] +---- +const { Client } = require('@elastic/elasticsearch') + +const client = new Client({ + cloud: { id: '' }, + auth: { apiKey: 'base64EncodedKey' }, + redaction: { type: "off" } +}) + +try { + await client.indices.create({ index: 'my_index' }) +} catch (err) { + console.log(err.meta.meta.request.options.headers.authorization) // the actual header value will be logged +} +---- + [discrete] ==== Migrate to v8 diff --git a/package.json b/package.json index b45d17952..73ed89911 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "zx": "^7.2.2" }, "dependencies": { - "@elastic/transport": "^8.3.4", + "@elastic/transport": "^8.4.0", "tslib": "^2.4.0" }, "tap": { diff --git a/src/client.ts b/src/client.ts index 09118d58c..50ba4942f 100644 --- a/src/client.ts +++ b/src/client.ts @@ -43,6 +43,7 @@ import { BearerAuth, Context } from '@elastic/transport/lib/types' +import { RedactionOptions } from '@elastic/transport/lib/Transport' import BaseConnection, { prepareHeaders } from '@elastic/transport/lib/connection/BaseConnection' import SniffingTransport from './sniffingTransport' import Helpers from './helpers' @@ -113,6 +114,7 @@ export interface ClientOptions { caFingerprint?: string maxResponseSize?: number maxCompressedResponseSize?: number + redaction?: RedactionOptions } export default class Client extends API { @@ -186,7 +188,11 @@ export default class Client extends API { proxy: null, enableMetaHeader: true, maxResponseSize: null, - maxCompressedResponseSize: null + maxCompressedResponseSize: null, + redaction: { + type: 'replace', + additionalKeys: [] + } }, opts) if (options.caFingerprint != null && isHttpConnection(opts.node ?? opts.nodes)) { @@ -259,7 +265,8 @@ export default class Client extends API { jsonContentType: 'application/vnd.elasticsearch+json; compatible-with=8', ndjsonContentType: 'application/vnd.elasticsearch+x-ndjson; compatible-with=8', accept: 'application/vnd.elasticsearch+json; compatible-with=8,text/plain' - } + }, + redaction: options.redaction }) this.helpers = new Helpers({ diff --git a/src/helpers.ts b/src/helpers.ts index 0bd1b1c5c..efad8b49b 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -196,8 +196,11 @@ export default class Helpers { await sleep(wait) } assert(response !== undefined, 'The response is undefined, please file a bug report') + + const { redaction = { type: 'replace' } } = options + const errorOptions = { redaction } if (response.statusCode === 429) { - throw new ResponseError(response) + throw new ResponseError(response, errorOptions) } let scroll_id = response.body._scroll_id @@ -237,7 +240,7 @@ export default class Helpers { await sleep(wait) } if (response.statusCode === 429) { - throw new ResponseError(response) + throw new ResponseError(response, errorOptions) } } @@ -289,6 +292,9 @@ export default class Helpers { } = options reqOptions.meta = true + const { redaction = { type: 'replace' } } = reqOptions + const errorOptions = { redaction } + let stopReading = false let stopError: Error | null = null let timeoutRef = null @@ -502,7 +508,7 @@ export default class Helpers { // @ts-expect-error addDocumentsGetter(result) if (response.status != null && response.status >= 400) { - callbacks[i](new ResponseError(result), result) + callbacks[i](new ResponseError(result, errorOptions), result) } else { callbacks[i](null, result) }