Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ To check for security updates, go to [Security announcements for the Elastic sta

* Add support for TLS/mTLS configuration of the OpAMP client (used for [central configuration](elastic-otel-node://reference/edot-node/configuration.md#central-configuration)). Three new environment variables can be used: `ELASTIC_OTEL_OPAMP_CERTIFICATE`, `ELASTIC_OTEL_OPAMP_CLIENT_CERTIFICATE`, and `ELASTIC_OTEL_OPAMP_CLIENT_KEY`. See [the Configure central configuration doc section](elastic-otel-node://reference/edot-node/configuration.md#configure-central-configuration) for details. [#1044](https://github.com/elastic/elastic-otel-node/issues/1044)

* Improve the "preamble" log message at startup to include some details on envvars set that are relevant to the EDOT Node.js config. This uses an allowlist of non-sensitive envvars to avoid logging sensitive details. [#1018](https://github.com/elastic/elastic-otel-node/issues/1018)

### Fixes [edot-node-next-fixes]


Expand Down
137 changes: 137 additions & 0 deletions packages/opentelemetry-node/lib/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,144 @@ function restoreEnvironment() {
});
}

/**
* Return an object with all `OTEL_` and `ELASTIC_OTEL_` envvars that are **safe
* to be logged**. I.e. this redacts the value of any envvar not on the
* allowlist of non-sensitive envvars.
*
* Compare to the equiv in EDOT Python:
* https://github.com/elastic/elastic-otel-python/blob/v1.9.0/src/elasticotel/distro/config.py#L95-L104
*/
function getSafeEdotEnv() {
// While more of a PITA to maintain, I'm opting for an allowlist of known
// envvars with presumed non-sensitive values, to bias on the side of safer.
const edotEnv = {};
for (const k of Object.keys(process.env)) {
if (k.startsWith('OTEL_') || k.startsWith('ELASTIC_OTEL_')) {
if (NON_SENSTITIVE_EDOT_ENV_NAMES.has(k)) {
edotEnv[k] = process.env[k];
} else {
edotEnv[k] = '[REDACTED]';
}
}
}
return edotEnv;
}

/**
* A set of EDOT/OTel-related envvar names whose value is not considered
* sensitive information. For example the `*_HEADERS` envvars should NOT be
* included in this set.
*
* Command to grep a repo for candidates:
* rg '\b((ELASTIC_)?OTEL_\w+)\b' -g '!test' -oIN | sort | uniq | rg -v HEADERS
* Or in the relevant repo clones:
-g '!test' -g '!semantic-conventions' -g '!CHANGELOG.md' -g '!contrib-test-utils' \

rg '\b((ELASTIC_)?OTEL_\w+)\b' -oIN \
-g '*.{ts,js,mjs,cjs}' -g '!test' -g '!semantic-conventions' -g '!contrib-test-utils' \
elastic-otel-node opentelemetry-js opentelemetry-js-contrib \
| rg -v '(HEADERS|PASSWORD)' \
| rg -v '(_SYMBOL|OTEL_SEV_NUM_FROM_|OTEL_FOO|OTEL_PATCHED_SYMBOL|OTEL_OPEN_SPANS|_$)' \
| sort | uniq
*/
const NON_SENSTITIVE_EDOT_ENV_NAMES = new Set(
`
ELASTIC_OTEL_CONTEXT_PROPAGATION_ONLY
ELASTIC_OTEL_EXPERIMENTAL_OPAMP_HEARTBEAT_INTERVAL
ELASTIC_OTEL_HOST_METRICS_DISABLED
ELASTIC_OTEL_METRICS_DISABLED
ELASTIC_OTEL_NODE_ENABLE_LOG_SENDING
ELASTIC_OTEL_OPAMP_CERTIFICATE
ELASTIC_OTEL_OPAMP_CLIENT_CERTIFICATE
ELASTIC_OTEL_OPAMP_CLIENT_KEY
ELASTIC_OTEL_OPAMP_ENDPOINT
ELASTIC_OTEL_TEST_OPAMP_CLIENT_DIAG_ENABLED
OTEL_ATTRIBUTE_COUNT_LIMIT
OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT
OTEL_BLRP_EXPORT_TIMEOUT
OTEL_BLRP_MAX_EXPORT_BATCH_SIZE
OTEL_BLRP_MAX_QUEUE_SIZE
OTEL_BLRP_SCHEDULE_DELAY
OTEL_BSP_EXPORT_TIMEOUT
OTEL_BSP_MAX_EXPORT_BATCH_SIZE
OTEL_BSP_MAX_QUEUE_SIZE
OTEL_BSP_SCHEDULE_DELAY
OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT
OTEL_EXPERIMENTAL_CONFIG_FILE
OTEL_EXPORTER_JAEGER_AGENT_HOST
OTEL_EXPORTER_JAEGER_AGENT_PORT
OTEL_EXPORTER_JAEGER_ENDPOINT
OTEL_EXPORTER_JAEGER_USER
OTEL_EXPORTER_OTLP_CERTIFICATE
OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE
OTEL_EXPORTER_OTLP_CLIENT_KEY
OTEL_EXPORTER_OTLP_COMPRESSION
OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_INSECURE
OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE
OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE
OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY
OTEL_EXPORTER_OTLP_LOGS_COMPRESSION
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
OTEL_EXPORTER_OTLP_LOGS_PROTOCOL
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT
OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE
OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE
OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY
OTEL_EXPORTER_OTLP_METRICS_COMPRESSION
OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT
OTEL_EXPORTER_OTLP_PROTOCOL
OTEL_EXPORTER_OTLP_TIMEOUT
OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE
OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE
OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY
OTEL_EXPORTER_OTLP_TRACES_COMPRESSION
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT
OTEL_EXPORTER_PROMETHEUS_HOST
OTEL_EXPORTER_PROMETHEUS_PORT
OTEL_EXPORTER_ZIPKIN_ENDPOINT
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
OTEL_INSTRUMENTATION_HTTP_KNOWN_METHODS
OTEL_LINK_ATTRIBUTE_COUNT_LIMIT
OTEL_LOG_LEVEL
OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT
OTEL_LOGRECORD_ATTRIBUTE_VALUE_LENGTH_LIMIT
OTEL_LOGS_EXPORTER
OTEL_METRIC_EXPORT_INTERVAL
OTEL_METRIC_EXPORT_TIMEOUT
OTEL_METRICS_EXEMPLAR_FILTER
OTEL_METRICS_EXPORTER
OTEL_NODE_DISABLED_INSTRUMENTATIONS
OTEL_NODE_ENABLED_INSTRUMENTATIONS
OTEL_NODE_RESOURCE_DETECTORS
OTEL_PROPAGATORS
OTEL_RESOURCE_ATTRIBUTES
OTEL_SDK_DISABLED
OTEL_SEMCONV_STABILITY_OPT_IN
OTEL_SERVICE_NAME
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT
OTEL_SPAN_ATTRIBUTE_PER_EVENT_COUNT_LIMIT
OTEL_SPAN_ATTRIBUTE_PER_LINK_COUNT_LIMIT
OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT
OTEL_SPAN_EVENT_COUNT_LIMIT
OTEL_SPAN_LINK_COUNT_LIMIT
OTEL_TRACES_EXPORTER
OTEL_TRACES_SAMPLER
OTEL_TRACES_SAMPLER_ARG
`
.trim()
.split(/\s+/)
);

module.exports = {
setupEnvironment,
restoreEnvironment,
getSafeEdotEnv,
};
51 changes: 31 additions & 20 deletions packages/opentelemetry-node/lib/sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
const {log, registerOTelDiagLogger} = require('./logging');
const luggite = require('./luggite');
const {resolveDetectors} = require('./detectors');
const {setupEnvironment, restoreEnvironment} = require('./environment');
const {
setupEnvironment,
restoreEnvironment,
getSafeEdotEnv,
} = require('./environment');
const {getInstrumentations} = require('./instrumentations');
const {setupCentralConfig} = require('./central-config');
const {
Expand Down Expand Up @@ -69,7 +73,7 @@
try {
await shutdownFn();
} catch (err) {
console.warn('warning: error shutting down OTel SDK', err);

Check warning on line 76 in packages/opentelemetry-node/lib/sdk.js

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
}
process.exit(128 + os.constants.signals.SIGTERM);
});
Expand All @@ -79,7 +83,7 @@
try {
await shutdownFn();
} catch (err) {
console.warn('warning: error shutting down OTel SDK', err);

Check warning on line 86 in packages/opentelemetry-node/lib/sdk.js

View workflow job for this annotation

GitHub Actions / lint

Unexpected console statement
}
});
}
Expand All @@ -97,6 +101,10 @@
function startNodeSDK(cfg = {}) {
log.trace('startNodeSDK cfg:', cfg);

// Gather relevant envvars for the preamble, before this distro possibly
// mucks with some.
const edotEnv = getSafeEdotEnv();

// TODO: test behaviour with OTEL_SDK_DISABLED.
// Do we still log preamble? See NodeSDK _disabled handling.
// Do we still attempt to enableHostMetrics()?
Expand Down Expand Up @@ -277,25 +285,6 @@

setupEnvironment();
const sdk = new NodeSDK(config);

// TODO perhaps include some choice resource attrs in this log (sync ones): service.name, deployment.environment.name
log.info(
{
preamble: true,
distroVersion: DISTRO_VERSION,
env: {
// For darwin: https://en.wikipedia.org/wiki/Darwin_%28operating_system%29#Release_history
os: `${os.platform()} ${os.release()}`,
arch: os.arch(),
runtime: `Node.js ${process.version}`,
},
// The "config" object structure is not stable.
config: {
logLevel: luggite.nameFromLevel[log.level()] ?? log.level(),
},
},
'start EDOT Node.js'
);
sdk.start(); // .start() *does* use `process.env` though I think it should not.
restoreEnvironment();

Expand Down Expand Up @@ -338,6 +327,28 @@
contextPropagationOnly,
});

// Log a preamble by default. The primary goal is for getting clarity on
// what is running and configure how for *support*.
// TODO perhaps include some choice resource attrs in this log (sync ones): service.name, deployment.environment.name
log.info(
{
preamble: true,
distroVersion: DISTRO_VERSION,
system: {
// For darwin: https://en.wikipedia.org/wiki/Darwin_%28operating_system%29#Release_history
os: `${os.platform()} ${os.release()}`,
arch: os.arch(),
runtime: `Node.js ${process.version}`,
},
edotEnv,
// The "configInfo" object structure is not stable.
configInfo: {
logLevel: luggite.nameFromLevel[log.level()] ?? log.level(),
},
},
'start EDOT Node.js'
);

// Shutdown handling.
const shutdownFn = () => {
const promises = [sdk.shutdown()];
Expand Down
51 changes: 51 additions & 0 deletions packages/opentelemetry-node/test/preamble.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and contributors
* SPDX-License-Identifier: Apache-2.0
*/

// EDOT Node.js logs a preamble message at startup. This test file ensures
// that preamble includes the intended data.

const {test} = require('tape');
const {runTestFixtures, findObjInArray} = require('./testutils');

/** @type {import('./testutils').TestFixture[]} */
const testFixtures = [
{
name: 'premable basic',
args: ['./fixtures/use-express.js'],
cwd: __dirname,
env: {
NODE_OPTIONS: '--require=@elastic/opentelemetry-node',
OTEL_METRICS_EXPORTER: 'none',
OTEL_EXPORTER_OTLP_HEADERS: 'Foo=bar',
ELASTIC_OTEL_NODE_ENABLE_LOG_SENDING: 'true',
},
verbose: true,
checkTelemetry: (t, col, stdout) => {
const logs = stdout
.split(/\r?\n/g)
.filter((ln) => ln.startsWith('{'))
.map((ln) => JSON.parse(ln));
const preamble = findObjInArray(logs, 'preamble', true);
t.ok(preamble);
t.ok(preamble.system.os);
t.ok(preamble.system.runtime);
t.strictEqual(preamble.level, 30, 'premable logged at INFO level');
t.strictEqual(preamble.edotEnv.OTEL_METRICS_EXPORTER, 'none');
t.strictEqual(
preamble.edotEnv.ELASTIC_OTEL_NODE_ENABLE_LOG_SENDING,
'true'
);
t.strictEqual(
preamble.edotEnv.OTEL_EXPORTER_OTLP_HEADERS,
'[REDACTED]'
);
},
},
];

test('preamble log.info', (suite) => {
runTestFixtures(suite, testFixtures);
suite.end();
});
9 changes: 9 additions & 0 deletions packages/opentelemetry-node/types/environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,12 @@ export function setupEnvironment(): void;
* Restores any `process.env` stashed in `setupEnvironment()`.
*/
export function restoreEnvironment(): void;
/**
* Return an object with all `OTEL_` and `ELASTIC_OTEL_` envvars that are **safe
* to be logged**. I.e. this redacts the value of any envvar not on the
* allowlist of non-sensitive envvars.
*
* Compare to the equiv in EDOT Python:
* https://github.com/elastic/elastic-otel-python/blob/v1.9.0/src/elasticotel/distro/config.py#L95-L104
*/
export function getSafeEdotEnv(): {};
Loading