From 0fa00313893cc6a0b1c20d4960426dbad47f707f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Serkan=20=C3=96ZAL?= Date: Fri, 24 Feb 2023 13:43:33 +0300 Subject: [PATCH] Add masked key support while reporting collected traces, spans, metrics and logs (#396) * Add masked key support while reporting collected traces, spans, metrics and logs * Catch and log unexpected errors occurred while serializing masked * Update doc --- README.md | 247 +++++++++++++------------ src/Reporter.ts | 129 ++++++++++++- src/config/ConfigMetadata.ts | 7 + src/config/ConfigNames.ts | 4 + src/integrations/MySQL2Integration.ts | 17 +- src/integrations/MySQLIntegration.ts | 19 +- src/integrations/PostgreIntegration.ts | 19 +- 7 files changed, 294 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index a463e6a8..6405a27b 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,7 @@ Check out [example projects](https://github.com/thundra-io/thundra-examples-lamb - [How to build](#how-to-build) - [How to test](#how-to-test) - [Changelog](#changelog) - - - + ## Installation ```bash @@ -48,6 +46,7 @@ npm install @thundra/core --save ``` ## Configuration + You can configure Thundra using **environment variables** or **module initialization parameters**. Environment variables have **higher precedence** over initialization parameters. @@ -70,15 +69,13 @@ Check out the [configuration part](https://apm.docs.thundra.io/node.js/nodejs-co | THUNDRA_AGENT_REPORT_REST_BASEURL | string | https://collector.thundra.io/v1 | | THUNDRA_AGENT_REPORT_CLOUDWATCH_ENABLE | bool | false | - - ## Usage ### Integration Options for Containers and VMs ```shell -export THUNDRA_APIKEY= -export THUNDRA_AGENT_APPLICATION_NAME= +export THUNDRA_APIKEY= +export THUNDRA_AGENT_APPLICATION_NAME= ``` For `Dockerfile`, you just replace `export` with `ENV`. @@ -252,13 +249,12 @@ if (ackIds.length !== 0) { Integrating Thundra using AWS Lambda Layers is the recommended (and easier) way to get started with Thundra. For latest layer version(layer arn) and details of the integration see the [doc](https://apm.docs.thundra.io/node.js/nodejs-integration-options) - #### Without Layers Just require this module, pass your api key to it and wrap your handler: ```js -const thundra = require("@thundra/core")({ apiKey: "your_thundra_api_key" }); +const thundra = require("@thundra/core")({ apiKey: "" }); exports.handler = thundra((event, context,callback) => { callback(null, "Hello Thundra!"); @@ -270,7 +266,7 @@ Thundra will monitor your AWS lambda function and report automatically! `context.done`, `context.succeed` and `context.fail` are also supported: ```js -const thundra = require("@thundra/core")({ apiKey: "your_thundra_api_key" }); +const thundra = require("@thundra/core")({ apiKey: "" }); exports.handler = thundra((event, context) => { context.succeed("Hello Thundra!"); @@ -316,18 +312,16 @@ Thundra provides out-of-the-box instrumentation (tracing) for following librarie |@google-cloud/pubsub |`>=1.2` | |@google-cloud/bigquery |`>=5.0` | - - - ## Async Monitoring with Zero Overhead + By default, Thundra agent reports by making an HTTPS request. This adds an overhead to your lambda function. Instead, you can [setup async monitoring](https://apm.docs.thundra.io/performance/zero-overhead-with-asynchronous-monitoring) in **2 minutes** and monitor your lambda functions with **zero overhead**! Check out our async monitoring example at our [example projects](https://github.com/thundra-io/thundra-examples-lambda-nodejs) for a quick start. - ## Log Support + You can monitor your logs using Thundra and enjoy the three pillars of observability in one place! ```js @@ -394,7 +388,7 @@ logger.log("trace", "Hey, I am %s", "tracing."); In increasing precedence: **`trace`**, **`debug`**, **`info`**, **`warn`**, **`error`**, **`fatal`**. -You can set the log level by setting the environment variable `thundra_log_logLevel` to one of the following: +You can set the log level by setting the environment variable `THUNDRA_AGENT_LOG_LOGLEVEL` to one of the following: * `trace` * `debug` * `info` @@ -403,118 +397,139 @@ You can set the log level by setting the environment variable `thundra_log_logLe * `fatal` * `none` -For instance, if `thundra_log_logLevel` is: +For instance, if `THUNDRA_AGENT_LOG_LOGLEVEL` is: * `debug`, only `debug` and higher precedence logs will be reported. * `none`, none of the logs will be reported. +## Mask Sensitive Data +You can specify the keys to be masked in the trace by passing the key names +(separated by comma (`,`) if there are multiple) through the `THUNDRA_AGENT_REPORT_MASKED_KEYS` environment variable. +Here, key names can be string for exact match or [regexp](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp) pattern. -## Warmup Support -You can cut down cold starts easily by deploying our lambda function [`thundra-lambda-warmup`](https://github.com/thundra-io/thundra-lambda-warmup). +For example, +``` +THUNDRA_AGENT_REPORT_MASKED_KEYS=password +``` +masks all the keys/properties whose names **exactly** match to `password`. -Our agent handles warmup requests automatically so you don't need to make any code changes. +As another example, +``` +THUNDRA_AGENT_REPORT_MASKED_KEYS=/.*password.*/ +``` +masks all the keys/properties whose names **contain** (partially match) `password`. -You just need to deploy `thundra-lambda-warmup` once, then you can enable warming up for your lambda by -* setting its environment variable `thundra_agent_lambda_warmup_warmupAware` **true** OR -* adding its name to `thundra-lambda-warmup`'s environment variable `thundra_agent_lambda_warmup_function`. +If there are multiple key names or patterns you want to specify, you can separate them by comma (`,`). +``` +THUNDRA_AGENT_REPORT_MASKED_KEYS=/.*password.*/,/.*secret.*/ +``` -Check out [this part](https://thundra.readme.io/docs/how-to-warmup) in our docs for more information. +By default, masked data is replaced with `*****`. +But if you want to remove the masked data completely, you can set `THUNDRA_AGENT_REPORT_HIDE` environment variable to `true`. +## Warmup Support -## All Environment Variables +You can cut down cold starts easily by deploying our lambda function [`thundra-lambda-warmup`](https://github.com/thundra-io/thundra-lambda-warmup). + +By default, Thundra agent doesn't recognize warmup requests by default, but you can enable it +by setting `THUNDRA_AGENT_LAMBDA_WARMUP_WARMUPAWARE` environment variable to `true` -| Name | Type | Default Value | -|:--------------------------------------------------------------------|:-------:|:-------------------------------:| -| THUNDRA_APIKEY | string | - | -| THUNDRA_AGENT_DISABLE | bool | false | -| THUNDRA_AGENT_DEBUG_ENABLE | bool | false | -| THUNDRA_AGENT_TRACE_DISABLE | bool | false | -| THUNDRA_AGENT_METRIC_DISABLE | bool | true | -| THUNDRA_AGENT_LOG_DISABLE | bool | true | -| THUNDRA_AGENT_REPORT_REST_BASEURL | string | https://collector.thundra.io/v1 | -| THUNDRA_AGENT_REPORT_REST_TRUSTALLCERTIFICATES | bool | false | -| THUNDRA_AGENT_REPORT_REST_LOCAL | bool | false | -| THUNDRA_AGENT_REPORT_CLOUDWATCH_ENABLE | bool | false | -| THUNDRA_AGENT_REPORT_SIZE_MAX | number | 32 * 1024 (32 KB) | -| THUNDRA_AGENT_LAMBDA_HANDLER | string | - | -| THUNDRA_AGENT_LAMBDA_WARMUP_WARMUPAWARE | bool | false | -| THUNDRA_AGENT_LAMBDA_TIMEOUT_MARGIN | number | - | -| THUNDRA_AGENT_LAMBDA_ERROR_STACKTRACE_MASK | bool | false | -| THUNDRA_AGENT_TRACE_REQUEST_SKIP | bool | false | -| THUNDRA_AGENT_TRACE_RESPONSE_SKIP | bool | false | -| THUNDRA_AGENT_LAMBDA_TRACE_KINESIS_REQUEST_ENABLE | bool | false | -| THUNDRA_AGENT_LAMBDA_TRACE_FIREHOSE_REQUEST_ENABLE | bool | false | -| THUNDRA_AGENT_LAMBDA_TRACE_CLOUDWATCHLOG_REQUEST_ENABLE | bool | false | -| THUNDRA_AGENT_LAMBDA_AWS_STEPFUNCTIONS | bool | false | -| THUNDRA_AGENT_LAMBDA_AWS_APPSYNC | bool | false | -| THUNDRA_AGENT_APPLICATION_ID | string | - | -| THUNDRA_AGENT_APPLICATION_INSTANCEID | string | - | -| THUNDRA_AGENT_APPLICATION_REGION | string | - | -| THUNDRA_AGENT_APPLICATION_NAME | string | - | -| THUNDRA_AGENT_APPLICATION_STAGE | string | - | -| THUNDRA_AGENT_APPLICATION_DOMAINNAME | string | - | -| THUNDRA_AGENT_APPLICATION_CLASSNAME | string | - | -| THUNDRA_AGENT_APPLICATION_VERSION | string | - | -| THUNDRA_AGENT_APPLICATION_TAG | any | - | -| THUNDRA_AGENT_INVOCATION_SAMPLE_ONERROR | bool | false | -| THUNDRA_AGENT_INVOCATION_REQUEST_TAGS | string | - | -| THUNDRA_AGENT_INVOCATION_RESPONSE_TAGS | string | - | -| THUNDRA_AGENT_TRACE_INSTRUMENT_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INSTRUMENT_TRACEABLECONFIG | string | - | -| THUNDRA_AGENT_TRACE_INSTRUMENT_FILE_PREFIX | string | - | -| THUNDRA_AGENT_TRACE_SPAN_LISTENERCONFIG | string | - | -| THUNDRA_AGENT_TRACE_SPAN_COUNT_MAX | number | 200 | -| THUNDRA_AGENT_SAMPLER_TIMEAWARE_TIMEFREQ | number | 300000 | -| THUNDRA_AGENT_SAMPLER_COUNTAWARE_COUNTFREQ | number | 100 | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_INSTRUMENT_ONLOAD | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SNS_MESSAGE_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SNS_TRACEINJECTION_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SQS_MESSAGE_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SQS_TRACEINJECTION_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_LAMBDA_PAYLOAD_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_LAMBDA_TRACEINJECTION_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_DYNAMODB_STATEMENT_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_DYNAMODB_TRACEINJECTION_ENABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_ATHENA_STATEMENT_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_BODY_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_BODY_SIZE_MAX | number | 10 * 1024 (10 KB) | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_RESPONSE_BODY_MASK | bool | true | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_RESPONSE_BODY_SIZE_MAX | number | 10 * 1024 (10 KB) | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_URL_DEPTH | number | 1 | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_TRACEINJECTION_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_ERROR_ON4XX_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_ERROR_ON5XX_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_REDIS_COMMAND_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_RDB_STATEMENT_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_RDB_RESULT_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_ELASTICSEARCH_BODY_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_ELASTICSEARCH_PATH_DEPTH | number | 1 | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_MONGODB_COMMAND_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_EVENTBRIDGE_DETAIL_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SES_MAIL_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SES_MAIL_DESTINATION_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_RABBITMQ_MESSAGE_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_PUBSUB_MESSAGE_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_BIGQUERY_RESPONSE_SIZE_MAX | number | 1 * 1024 (1 KB) | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_BIGQUERY_QUERY_MASK | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_BIGQUERY_RESPONSE_MASK | bool | false | -| THUNDRA_AGENT_LOG_CONSOLE_DISABLE | bool | false | -| THUNDRA_AGENT_LOG_LOGLEVEL | string | TRACE | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_ENABLE | bool | false | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_PORT | number | 1111 | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_LOGS_ENABLE | bool | false | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_WAIT_MAX | number | 60000 | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_IO_WAIT | number | 60000 | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_BROKER_PORT | number | 444 | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_BROKER_HOST | string | debug.thundra.io | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_SESSION_NAME | string | default | -| THUNDRA_AGENT_LAMBDA_DEBUGGER_AUTH_TOKEN | string | - | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_HAPI_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_KOA_DISABLE | bool | false | -| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_PUBSUB_DISABLE | bool | false | +Check out [this part](https://apm.docs.thundra.io/performance/dealing-with-cold-starts) in our docs for more information. +## All Environment Variables +| Name | Type | Default Value | Description | +|:--------------------------------------------------------------------|:------:|:-------------------------------:|:----------------------------------------------------------------------------------| +| THUNDRA_APIKEY | string | - | | +| THUNDRA_AGENT_DISABLE | bool | false | | +| THUNDRA_AGENT_DEBUG_ENABLE | bool | false | | +| THUNDRA_AGENT_TRACE_DISABLE | bool | false | | +| THUNDRA_AGENT_METRIC_DISABLE | bool | true | | +| THUNDRA_AGENT_LOG_DISABLE | bool | true | | +| THUNDRA_AGENT_REPORT_REST_BASEURL | string | https://collector.thundra.io/v1 | | +| THUNDRA_AGENT_REPORT_REST_TRUSTALLCERTIFICATES | bool | false | | +| THUNDRA_AGENT_REPORT_REST_LOCAL | bool | false | | +| THUNDRA_AGENT_REPORT_CLOUDWATCH_ENABLE | bool | false | | +| THUNDRA_AGENT_REPORT_SIZE_MAX | number | 32 * 1024 (32 KB) | | +| THUNDRA_AGENT_REPORT_MASKED_KEYS | string | - | Comma (,) separated key names (can be string or regexp) to be masked in the trace | +| THUNDRA_AGENT_REPORT_HIDE | bool | false | Hides masked keys instead of masking them | +| THUNDRA_AGENT_LAMBDA_HANDLER | string | - | | +| THUNDRA_AGENT_LAMBDA_WARMUP_WARMUPAWARE | bool | false | | +| THUNDRA_AGENT_LAMBDA_TIMEOUT_MARGIN | number | - | | +| THUNDRA_AGENT_LAMBDA_ERROR_STACKTRACE_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_REQUEST_SKIP | bool | false | | +| THUNDRA_AGENT_TRACE_RESPONSE_SKIP | bool | false | | +| THUNDRA_AGENT_LAMBDA_TRACE_KINESIS_REQUEST_ENABLE | bool | false | | +| THUNDRA_AGENT_LAMBDA_TRACE_FIREHOSE_REQUEST_ENABLE | bool | false | | +| THUNDRA_AGENT_LAMBDA_TRACE_CLOUDWATCHLOG_REQUEST_ENABLE | bool | false | | +| THUNDRA_AGENT_LAMBDA_AWS_STEPFUNCTIONS | bool | false | | +| THUNDRA_AGENT_LAMBDA_AWS_APPSYNC | bool | false | | +| THUNDRA_AGENT_APPLICATION_ID | string | - | | +| THUNDRA_AGENT_APPLICATION_INSTANCEID | string | - | | +| THUNDRA_AGENT_APPLICATION_REGION | string | - | | +| THUNDRA_AGENT_APPLICATION_NAME | string | - | | +| THUNDRA_AGENT_APPLICATION_STAGE | string | - | | +| THUNDRA_AGENT_APPLICATION_DOMAINNAME | string | - | | +| THUNDRA_AGENT_APPLICATION_CLASSNAME | string | - | | +| THUNDRA_AGENT_APPLICATION_VERSION | string | - | | +| THUNDRA_AGENT_APPLICATION_TAG | any | - | | +| THUNDRA_AGENT_INVOCATION_SAMPLE_ONERROR | bool | false | | +| THUNDRA_AGENT_INVOCATION_REQUEST_TAGS | string | - | | +| THUNDRA_AGENT_INVOCATION_RESPONSE_TAGS | string | - | | +| THUNDRA_AGENT_TRACE_INSTRUMENT_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INSTRUMENT_TRACEABLECONFIG | string | - | | +| THUNDRA_AGENT_TRACE_INSTRUMENT_FILE_PREFIX | string | - | | +| THUNDRA_AGENT_TRACE_SPAN_LISTENERCONFIG | string | - | | +| THUNDRA_AGENT_TRACE_SPAN_COUNT_MAX | number | 200 | | +| THUNDRA_AGENT_SAMPLER_TIMEAWARE_TIMEFREQ | number | 300000 | | +| THUNDRA_AGENT_SAMPLER_COUNTAWARE_COUNTFREQ | number | 100 | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_INSTRUMENT_ONLOAD | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SNS_MESSAGE_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SNS_TRACEINJECTION_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SQS_MESSAGE_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SQS_TRACEINJECTION_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_LAMBDA_PAYLOAD_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_LAMBDA_TRACEINJECTION_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_DYNAMODB_STATEMENT_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_DYNAMODB_TRACEINJECTION_ENABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_ATHENA_STATEMENT_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_BODY_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_BODY_SIZE_MAX | number | 10 * 1024 (10 KB) | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_RESPONSE_BODY_MASK | bool | true | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_RESPONSE_BODY_SIZE_MAX | number | 10 * 1024 (10 KB) | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_URL_DEPTH | number | 1 | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_TRACEINJECTION_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_ERROR_ON4XX_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HTTP_ERROR_ON5XX_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_REDIS_COMMAND_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_RDB_STATEMENT_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_RDB_RESULT_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_ELASTICSEARCH_BODY_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_ELASTICSEARCH_PATH_DEPTH | number | 1 | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_MONGODB_COMMAND_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_EVENTBRIDGE_DETAIL_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SES_MAIL_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_AWS_SES_MAIL_DESTINATION_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_RABBITMQ_MESSAGE_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_PUBSUB_MESSAGE_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_BIGQUERY_RESPONSE_SIZE_MAX | number | 1 * 1024 (1 KB) | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_BIGQUERY_QUERY_MASK | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_BIGQUERY_RESPONSE_MASK | bool | false | | +| THUNDRA_AGENT_LOG_CONSOLE_DISABLE | bool | false | | +| THUNDRA_AGENT_LOG_LOGLEVEL | string | TRACE | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_ENABLE | bool | false | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_PORT | number | 1111 | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_LOGS_ENABLE | bool | false | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_WAIT_MAX | number | 60000 | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_IO_WAIT | number | 60000 | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_BROKER_PORT | number | 444 | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_BROKER_HOST | string | debug.thundra.io | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_SESSION_NAME | string | default | | +| THUNDRA_AGENT_LAMBDA_DEBUGGER_AUTH_TOKEN | string | - | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_HAPI_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_KOA_DISABLE | bool | false | | +| THUNDRA_AGENT_TRACE_INTEGRATIONS_GOOGLE_PUBSUB_DISABLE | bool | false | | ### Module initialization parameters @@ -524,9 +539,8 @@ Check out [this part](https://thundra.readme.io/docs/how-to-warmup) in our docs | disableThundra | bool | false | | plugins | array | [ ] | - - ## How to build + [Webpack](https://webpack.js.org/) is used as a module bundler. To build the project, @@ -536,6 +550,7 @@ To build the project, ``` ## How to test + Tests are written using [Jest](https://facebook.github.io/jest/). To run tests, diff --git a/src/Reporter.ts b/src/Reporter.ts index 2e068c4c..ad2f4d35 100644 --- a/src/Reporter.ts +++ b/src/Reporter.ts @@ -4,13 +4,13 @@ import * as https from 'https'; import * as url from 'url'; import { COMPOSITE_MONITORING_DATA_PATH, - getDefaultCollectorEndpoint, LOCAL_COLLECTOR_ENDPOINT, SPAN_TAGS_TO_TRIM_1, SPAN_TAGS_TO_TRIM_2, SPAN_TAGS_TO_TRIM_3, REPORTER_HTTP_TIMEOUT, REPORTER_DATA_SIZE_LIMIT, + getDefaultCollectorEndpoint, } from './Constants'; import Utils from './utils/Utils'; import ThundraLogger from './ThundraLogger'; @@ -30,6 +30,11 @@ const httpsAgent = new https.Agent({ keepAlive: true, }); +const REGEXP_PATTERN = /^\/(.*?)\/([gimyu]*)$/; +const MASKED_VALUE = '*****'; + +type MaskedKey = string | RegExp; + /** * Reports given telemetry data to given/configured Thundra collector endpoint */ @@ -43,6 +48,8 @@ class Reporter { private async: boolean; private trimmers: Trimmer[]; private maxReportSize: number; + private maskedKeys: MaskedKey[]; + private hide: boolean; constructor(apiKey: string, opt: any = {}) { this.url = url.parse(opt.url || Reporter.getCollectorURL()); @@ -67,6 +74,29 @@ class Reporter { ` Max report size cannot be bigger than ${REPORTER_DATA_SIZE_LIMIT} ` + `but it is set to ${this.maxReportSize}. So limiting to ${REPORTER_DATA_SIZE_LIMIT}.`); } + this.maskedKeys = opt.maskedKeys || Reporter.getMaskedKeys(); + this.hide = opt.hide || ConfigProvider.get(ConfigNames.THUNDRA_REPORT_HIDE); + } + + private static getMaskedKeys(): MaskedKey[] | undefined { + const maskedKeysConfig: string | undefined = + ConfigProvider.get(ConfigNames.THUNDRA_REPORT_MASKED_KEYS); + const maskedKeys: MaskedKey[] = []; + if (maskedKeysConfig) { + for (const maskedKey of maskedKeysConfig.split(',')) { + const regexpParts: string[] = maskedKey.match(REGEXP_PATTERN); + if (regexpParts) { + maskedKeys.push(new RegExp(regexpParts[1], regexpParts[2])); + } else { + maskedKeys.push(maskedKey); + } + } + } + if (maskedKeys && maskedKeys.length) { + return maskedKeys; + } else { + return undefined; + } } private static getCollectorURL(): string { @@ -306,7 +336,7 @@ class Reporter { // If trimming is disabled, trim if and only if data size is bigger than maximum allowed limit const maxReportDataSize: number = disableTrim ? REPORTER_DATA_SIZE_LIMIT : this.maxReportSize; - let json: string = Utils.serializeJSON(batch); + let json: string = this.serializeMasked(batch, this.maskedKeys, this.hide); if (json.length < maxReportDataSize) { return json; @@ -317,14 +347,107 @@ class Reporter { if (!trimResult.mutated) { continue; } - json = Utils.serializeJSON(batch); + json = this.serializeMasked(batch, this.maskedKeys, this.hide); if (json.length < maxReportDataSize) { return json; } } + return this.serializeMasked(batch, this.maskedKeys, this.hide); + } + + private serializeMasked(batch: any, maskedKeys: MaskedKey[], hide?: boolean): string { + if (maskedKeys && maskedKeys.length) { + try { + ThundraLogger.debug(` Serializing masked ...`); + + const maskCheckSet: WeakSet = new WeakSet(); + + for (const monitoringData of batch.data.allMonitoringData) { + if (monitoringData.tags) { + maskCheckSet.add(monitoringData.tags); + } + } + + const result: string = + JSON.stringify(batch, this.createMaskingReplacer(maskCheckSet, maskedKeys, hide)); + + ThundraLogger.debug(` Serialized masked`); + + return result; + } catch (err) { + ThundraLogger.debug(` Error occurred while serializing masked`, err); + } + } return Utils.serializeJSON(batch); } + private isMasked(key: string, maskedKeys: MaskedKey[]): boolean { + for (const maskedKey of maskedKeys) { + if (typeof maskedKey === 'string' && maskedKey === key) { + return true; + } + if (maskedKey instanceof RegExp && maskedKey.test(key)) { + return true; + } + } + return false; + } + + private createMaskingReplacer(maskCheckSet: WeakSet, maskedKeys: MaskedKey[], hide?: boolean) + : (this: any, key: string, value: any) => any { + const isObject: Function = (o: any) => o != null && typeof o === 'object'; + const isArray: Function = (o: any) => o != null && Array.isArray(o); + const isObjectOrArray: Function = (o: any) => isObject(o) || isArray(o); + const isJson = (str: any) => + typeof str === 'string' && + ( + (str.charAt(0) === '{' && str.charAt(str.length - 1) === '}') || + (str.charAt(0) === '[' && str.charAt(str.length - 1) === ']') + ); + + const seen: WeakSet = new WeakSet(); + const me = this; + + return function (key: string, value: any) { + if (isObject(value)) { + if (seen.has(value)) { + return; + } + seen.add(value); + } + + // The parent needs to be checked to check the current property + const checkForMask: boolean = maskCheckSet.has(this); + if (checkForMask) { + if (me.isMasked(key, maskedKeys)) { + if (ThundraLogger.isDebugEnabled()) { + ThundraLogger.debug(` Masking (hide=${hide}) key ${key} ...`); + } + return hide ? undefined : MASKED_VALUE; + } else { + if (isObjectOrArray(value)) { + maskCheckSet.add(value); + } else if (isJson(value)) { + try { + const jsonObj: any = JSON.parse(value); + const jsonMaskCheckSet: WeakSet = new WeakSet(); + jsonMaskCheckSet.add(jsonObj); + const maskedJson = + JSON.stringify(jsonObj, me.createMaskingReplacer(jsonMaskCheckSet, maskedKeys, hide)); + if (maskedJson) { + value = maskedJson; + } + } catch (e) { + ThundraLogger.debug( + ` Unable to mask (hide=${hide}) json with key ${key}: ${value}`, e); + } + } + } + } + return value; + }; + } + } export class TrimResult { diff --git a/src/config/ConfigMetadata.ts b/src/config/ConfigMetadata.ts index 9f3f4ae3..ec7b8199 100644 --- a/src/config/ConfigMetadata.ts +++ b/src/config/ConfigMetadata.ts @@ -78,6 +78,13 @@ export const ConfigMetadata: {[key: string]: { type: string, defaultValue?: any type: 'number', defaultValue: 32 * 1024, // 32 KB }, + [ConfigNames.THUNDRA_REPORT_MASKED_KEYS]: { + type: 'string', + }, + [ConfigNames.THUNDRA_REPORT_HIDE]: { + type: 'boolean', + defaultValue: false, + }, [ConfigNames.THUNDRA_LAMBDA_HANDLER]: { type: 'string', }, diff --git a/src/config/ConfigNames.ts b/src/config/ConfigNames.ts index 9b93c79b..649c5012 100644 --- a/src/config/ConfigNames.ts +++ b/src/config/ConfigNames.ts @@ -53,6 +53,10 @@ class ConfigNames { 'thundra.agent.report.cloudwatch.enable'; public static readonly THUNDRA_REPORT_SIZE_MAX: string = 'thundra.agent.report.size.max'; + public static readonly THUNDRA_REPORT_MASKED_KEYS: string = + 'thundra.agent.report.masked.keys'; + public static readonly THUNDRA_REPORT_HIDE: string = + 'thundra.agent.report.hide'; ///////////////////////////////////////////////////////////////////////////// diff --git a/src/integrations/MySQL2Integration.ts b/src/integrations/MySQL2Integration.ts index ad6d79a9..66c236ea 100644 --- a/src/integrations/MySQL2Integration.ts +++ b/src/integrations/MySQL2Integration.ts @@ -142,20 +142,19 @@ class MySQL2Integration implements Integration { span.setErrorTag(err); } else { try { - let {rowCount, rows} = res; + let { rowCount, rows } = res; if (!rowCount && res instanceof Array) { rowCount = res.length; rows = res; } - span.addTags({ - [DBTags.DB_RESULT_COUNT]: rowCount, - [DBTags.DB_RESULTS]: - config.maskRdbResult - ? undefined - : (rows.length > MAX_DB_RESULT_COUNT + span.setTag(DBTags.DB_RESULT_COUNT, rowCount); + if (!config.maskRdbResult && Array.isArray(rows) && rows.length) { + span.setTag( + DBTags.DB_RESULTS, + rows.length > MAX_DB_RESULT_COUNT ? rows.slice(0, MAX_DB_RESULT_COUNT) - : rows), - }); + : rows); + } } catch (e) { ThundraLogger.debug(` Unable to capture DB results`, e); } diff --git a/src/integrations/MySQLIntegration.ts b/src/integrations/MySQLIntegration.ts index 6dc11293..a62a7357 100644 --- a/src/integrations/MySQLIntegration.ts +++ b/src/integrations/MySQLIntegration.ts @@ -148,20 +148,19 @@ class MySQLIntegration implements Integration { span.setErrorTag(err); } else { try { - let {rowCount, rows} = res; + let { rowCount, rows } = res; if (!rowCount && res instanceof Array) { rowCount = res.length; rows = res; } - span.addTags({ - [DBTags.DB_RESULT_COUNT]: rowCount, - [DBTags.DB_RESULTS]: - config.maskRdbResult - ? undefined - : (rows.length > MAX_DB_RESULT_COUNT - ? rows.slice(0, MAX_DB_RESULT_COUNT) - : rows), - }); + span.setTag(DBTags.DB_RESULT_COUNT, rowCount); + if (!config.maskRdbResult && Array.isArray(rows) && rows.length) { + span.setTag( + DBTags.DB_RESULTS, + rows.length > MAX_DB_RESULT_COUNT + ? rows.slice(0, MAX_DB_RESULT_COUNT) + : rows); + } } catch (e) { ThundraLogger.debug(` Unable to capture DB results`, e); } diff --git a/src/integrations/PostgreIntegration.ts b/src/integrations/PostgreIntegration.ts index 26855749..d2a0bd2b 100644 --- a/src/integrations/PostgreIntegration.ts +++ b/src/integrations/PostgreIntegration.ts @@ -120,20 +120,19 @@ class PostgreIntegration implements Integration { span.setErrorTag(err); } else { try { - let {rowCount, rows} = res; + let { rowCount, rows } = res; if (!rowCount && res instanceof Array) { rowCount = res.length; rows = res; } - span.addTags({ - [DBTags.DB_RESULT_COUNT]: rowCount, - [DBTags.DB_RESULTS]: - config.maskRdbResult - ? undefined - : (rows.length > MAX_DB_RESULT_COUNT - ? rows.slice(0, MAX_DB_RESULT_COUNT) - : rows), - }); + span.setTag(DBTags.DB_RESULT_COUNT, rowCount); + if (!config.maskRdbResult && Array.isArray(rows) && rows.length) { + span.setTag( + DBTags.DB_RESULTS, + rows.length > MAX_DB_RESULT_COUNT + ? rows.slice(0, MAX_DB_RESULT_COUNT) + : rows); + } } catch (e) { ThundraLogger.debug(` Unable to capture DB results`, e); }