@instana/core
Advanced tools
Comparing version 1.105.1 to 1.106.0
{ | ||
"name": "@instana/core", | ||
"version": "1.105.1", | ||
"version": "1.106.0", | ||
"description": "Core library for Instana's Node.js packages", | ||
@@ -136,3 +136,3 @@ "main": "src/index.js", | ||
}, | ||
"gitHead": "5f18465c4af6fa09ce62583bc69cd3f6a2eac5f8" | ||
"gitHead": "d780ba095bd7d11df8b2e1afc05b305f958aad72" | ||
} |
@@ -27,2 +27,4 @@ 'use strict'; | ||
if (value) { | ||
// If the client has set the same header multiple times, Node.js has already normalized | ||
// this to "value 1, value 2, ..." | ||
extraHeaders[key] = value; | ||
@@ -68,3 +70,5 @@ extraHeadersFound = true; | ||
if (keys[j].low === keyToCapture) { | ||
extraHeaders[keys[j].low] = headers[keys[j].orig]; | ||
extraHeaders[keys[j].low] = Array.isArray(headers[keys[j].orig]) | ||
? headers[keys[j].orig].join(', ') | ||
: headers[keys[j].orig]; | ||
extraHeadersFound = true; | ||
@@ -148,3 +152,6 @@ } | ||
const value = getHeader(key); | ||
if (value) { | ||
if (Array.isArray(value)) { | ||
additionalHeaders[key] = value.join(', '); | ||
additionalHeadersFound = true; | ||
} else if (value) { | ||
additionalHeaders[key] = value; | ||
@@ -151,0 +158,0 @@ additionalHeadersFound = true; |
@@ -123,5 +123,28 @@ 'use strict'; | ||
host: authority, | ||
header: httpCommon.getExtraHeadersCaseInsensitive(headers, extraHttpHeadersToCapture) | ||
header: httpCommon.getExtraHeadersFromHeaders(headers, extraHttpHeadersToCapture) | ||
}; | ||
const incomingServiceName = | ||
span.data.http.header && span.data.http.header[constants.serviceNameHeaderNameLowerCase]; | ||
if (incomingServiceName != null) { | ||
span.data.service = incomingServiceName; | ||
} | ||
if (!headers['x-instana-t']) { | ||
// In cases where we have started a fresh trace (that is, there is no X-INSTANA-T in the incoming request | ||
// headers, we add the new trace ID to the incoming request so a customer's app can render it reliably into the | ||
// EUM snippet, see | ||
// eslint-disable-next-line max-len | ||
// https://www.instana.com/docs/products/website_monitoring/backendCorrelation/#retrieve-the-backend-trace-id-in-nodejs | ||
headers['x-instana-t'] = span.t; | ||
} | ||
// Support for automatic client/back end EUM correlation: We add our key-value pair to the Server-Timing header | ||
// (the key intid is short for INstana Trace ID). This abbreviation is small enough to not incur a notable | ||
// overhead while at the same time being unique enough to avoid name collisions. | ||
const serverTimingValue = `intid;desc=${span.t}`; | ||
instrumentResponseMethod(stream, 'respond', 0, serverTimingValue); | ||
instrumentResponseMethod(stream, 'respondWithFD', 1, serverTimingValue); | ||
instrumentResponseMethod(stream, 'respondWithFile', 1, serverTimingValue); | ||
stream.on('aborted', () => { | ||
@@ -167,2 +190,31 @@ finishSpan(); | ||
function instrumentResponseMethod(stream, method, headerArgumentIndex, serverTimingValue) { | ||
if (typeof stream[method] === 'function') { | ||
shimmer.wrap( | ||
stream, | ||
method, | ||
original => | ||
function() { | ||
const headers = arguments[headerArgumentIndex]; | ||
if (!headers || typeof headers !== 'object' || !headers[HTTP2_HEADER_STATUS]) { | ||
return original.apply(this, arguments); | ||
} | ||
const existingKey = Object.keys(headers).filter(key => key.toLowerCase() === 'server-timing')[0]; | ||
const existingValue = existingKey ? headers[existingKey] : null; | ||
if (existingValue == null) { | ||
headers['Server-Timing'] = serverTimingValue; | ||
} else if (Array.isArray(existingValue)) { | ||
if (!existingValue.find(kv => kv.indexOf('intid;') === 0)) { | ||
headers[existingKey] = existingValue.concat(serverTimingValue); | ||
} | ||
} else if (typeof existingValue === 'string' && existingValue.indexOf('intid;') < 0) { | ||
headers[existingKey] = `${existingValue}, ${serverTimingValue}`; | ||
} | ||
return original.apply(this, arguments); | ||
} | ||
); | ||
} | ||
} | ||
exports.updateConfig = function updateConfig(config) { | ||
@@ -169,0 +221,0 @@ extraHttpHeadersToCapture = config.tracing.http.extraHttpHeadersToCapture; |
@@ -97,2 +97,3 @@ 'use strict'; | ||
}; | ||
const incomingServiceName = | ||
@@ -104,30 +105,44 @@ span.data.http.header && span.data.http.header[constants.serviceNameHeaderNameLowerCase]; | ||
// Handle client/back end eum correlation. | ||
if (!span.p) { | ||
// We add the trace ID to the incoming request so a customer's app can render it into the EUM snippet, see | ||
if (!req.headers['x-instana-t']) { | ||
// In cases where we have started a fresh trace (that is, there is no X-INSTANA-T in the incoming request | ||
// headers, we add the new trace ID to the incoming request so a customer's app can render it reliably into the | ||
// EUM snippet, see | ||
// eslint-disable-next-line max-len | ||
// https://www.instana.com/docs/products/website_monitoring/backendCorrelation/#retrieve-the-backend-trace-id-in-nodejs | ||
req.headers['x-instana-t'] = span.t; | ||
} | ||
// support for automatic client/back end EUM correlation | ||
// intid = instana trace id | ||
// This abbreviation is small enough to not incur a notable overhead while at the same | ||
// time being unique enough to avoid name collisions. | ||
const serverTimingValue = `intid;desc=${span.t}`; | ||
res.setHeader('Server-Timing', serverTimingValue); | ||
shimmer.wrap( | ||
res, | ||
'setHeader', | ||
realSetHeader => | ||
function shimmedSetHeader(key, value) { | ||
if (key.toLowerCase() === 'server-timing') { | ||
if (Array.isArray(value)) { | ||
// Support for automatic client/back end EUM correlation: We add our key-value pair to the Server-Timing header | ||
// (the key intid is short for INstana Trace ID). This abbreviation is small enough to not incur a notable | ||
// overhead while at the same time being unique enough to avoid name collisions. | ||
const serverTimingValue = `intid;desc=${span.t}`; | ||
res.setHeader('Server-Timing', serverTimingValue); | ||
shimmer.wrap( | ||
res, | ||
'setHeader', | ||
realSetHeader => | ||
function shimmedSetHeader(key, value) { | ||
if (key.toLowerCase() === 'server-timing') { | ||
if (value == null) { | ||
return realSetHeader.call(this, key, serverTimingValue); | ||
} else if (Array.isArray(value)) { | ||
if (value.find(kv => kv.indexOf('intid;') === 0)) { | ||
// If the application code sets intid, do not append another intid value. Actually, the application | ||
// has no business setting an intid key-value pair, but it could happen theoretically for a proxy-like | ||
// Node.js app (which blindly copies headers from downstream responses) in combination with a | ||
// downstream service that is also instrumented by Instana (and adds the intid key-value pair). | ||
return realSetHeader.apply(this, arguments); | ||
} else { | ||
return realSetHeader.call(this, key, value.concat(serverTimingValue)); | ||
} | ||
} else if (typeof value === 'string' && value.indexOf('intid;') >= 0) { | ||
// Do not add another intid key-value pair, see above. | ||
return realSetHeader.apply(this, arguments); | ||
} else { | ||
return realSetHeader.call(this, key, `${value}, ${serverTimingValue}`); | ||
} | ||
return realSetHeader.apply(this, arguments); | ||
} | ||
); | ||
} | ||
return realSetHeader.apply(this, arguments); | ||
} | ||
); | ||
@@ -134,0 +149,0 @@ req.on('aborted', () => { |
@@ -173,5 +173,12 @@ /* eslint-disable */ | ||
if (!config.tracing.http.extraHttpHeadersToCapture) { | ||
let fromEnvVar = {}; | ||
if (process.env.INSTANA_EXTRA_HTTP_HEADERS) { | ||
fromEnvVar = parseHeadersEnvVar(process.env.INSTANA_EXTRA_HTTP_HEADERS); | ||
} | ||
if (!config.tracing.http.extraHttpHeadersToCapture && !fromEnvVar) { | ||
config.tracing.http.extraHttpHeadersToCapture = defaults.tracing.http.extraHttpHeadersToCapture; | ||
return; | ||
} else if (!config.tracing.http.extraHttpHeadersToCapture && fromEnvVar) { | ||
config.tracing.http.extraHttpHeadersToCapture = fromEnvVar; | ||
} | ||
@@ -193,2 +200,9 @@ if (!Array.isArray(config.tracing.http.extraHttpHeadersToCapture)) { | ||
function parseHeadersEnvVar(envVarValue) { | ||
return envVarValue | ||
.split(/[;,]/) | ||
.map(header => header.trim()) | ||
.filter(header => header !== ''); | ||
} | ||
function normalizeTracingStackTraceLength(config) { | ||
@@ -282,5 +296,10 @@ if (config.tracing.stackTraceLength == null && process.env['INSTANA_STACK_TRACE_LENGTH']) { | ||
config.secrets.matcherMode = config.secrets.matcherMode || defaults.secrets.matcherMode; | ||
config.secrets.keywords = config.secrets.keywords || defaults.secrets.keywords; | ||
let fromEnvVar = {}; | ||
if (process.env.INSTANA_SECRETS) { | ||
fromEnvVar = parseSecretsEnvVar(process.env.INSTANA_SECRETS); | ||
} | ||
config.secrets.matcherMode = config.secrets.matcherMode || fromEnvVar.matcherMode || defaults.secrets.matcherMode; | ||
config.secrets.keywords = config.secrets.keywords || fromEnvVar.keywords || defaults.secrets.keywords; | ||
if (typeof config.secrets.matcherMode !== 'string') { | ||
@@ -295,3 +314,3 @@ logger.warn( | ||
logger.warn( | ||
'The value of config.secrets.matcherMode (%s) is not a supported matcher mode. Assuming the default value %s.', | ||
'The value of config.secrets.matcherMode (or the matcher mode parsed from INSTANA_SECRETS) (%s) is not a supported matcher mode. Assuming the default value %s.', | ||
config.secrets.matcherMode, | ||
@@ -309,4 +328,33 @@ defaults.secrets.matcherMode | ||
} | ||
if (config.secrets.matcherMode === 'none') { | ||
config.secrets.keywords = []; | ||
} | ||
} | ||
function parseSecretsEnvVar(envVarValue) { | ||
let [matcherMode, keywords] = envVarValue.split(':', 2); | ||
matcherMode = matcherMode.trim().toLowerCase(); | ||
if (matcherMode === 'none') { | ||
return { | ||
matcherMode, | ||
keywords: [] | ||
}; | ||
} | ||
if (!keywords) { | ||
// a list of keywords (with at least one element) is mandatory for all matcher modes except "none" | ||
logger.warn( | ||
'The value of INSTANA_SECRETS (%s) cannot be parsed. Please use the following format: INSTANA_SECRETS=<matcher>:<secret>[,<secret>]. This setting will be ignored.', | ||
envVarValue | ||
); | ||
return {}; | ||
} | ||
keywords = keywords.split(',').map(word => word.trim()); | ||
return { | ||
matcherMode, | ||
keywords | ||
}; | ||
} | ||
function normalizeSingleValue(configValue, defaultValue, configPath, envVarKey) { | ||
@@ -313,0 +361,0 @@ const envVarVal = process.env[envVarKey]; |
@@ -26,10 +26,10 @@ 'use strict'; | ||
.split('&') | ||
.filter(param => { | ||
.map(param => { | ||
const key = param.split('=')[0]; | ||
if (key) { | ||
return !secrets.isSecret(key); | ||
if (key && secrets.isSecret(key)) { | ||
return `${key}=<redacted>`; | ||
} | ||
return true; | ||
return param; | ||
}) | ||
.join('&'); | ||
}; |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances in 1 package
378458
10248
25