diff --git a/.gitlab/scripts/build_layer.sh b/.gitlab/scripts/build_layer.sh index 36a2d24..6c80cd4 100755 --- a/.gitlab/scripts/build_layer.sh +++ b/.gitlab/scripts/build_layer.sh @@ -40,9 +40,12 @@ function docker_build_zip { # Install datadog ruby in a docker container to avoid the mess from switching # between different ruby runtimes. + # + # NOTE: using the Lambda base image so native extensions (FFI, libddwaf) + # compile against the same libffi available at runtime on Lambda. temp_dir=$(mktemp -d) docker buildx build -t datadog-lambda-ruby-${arch}:$1 . --no-cache \ - --build-arg "image=ruby:${1}" \ + --build-arg "image=public.ecr.aws/lambda/ruby:${1}" \ --build-arg "runtime=${1}.0" \ --platform linux/${arch} \ --progress=plain \ diff --git a/.rubocop.yml b/.rubocop.yml index 2d9f108..4f079a0 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,8 @@ AllCops: TargetRubyVersion: 3.2 + Exclude: + - 'test/**/*' + - 'vendor/**/*' Metrics/MethodLength: Max: 20 diff --git a/Dockerfile b/Dockerfile index d6e2867..52d2bd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,15 +4,28 @@ ARG runtime # Install dev dependencies COPY . /var/task/datadog-lambda-rb WORKDIR /var/task/datadog-lambda-rb -RUN apt-get update -RUN apt-get install -y gcc zip binutils + +# NOTE: AL2 (Ruby 3.2) uses yum, AL2023 (Ruby 3.3+) uses dnf +RUN PKG=$(command -v dnf || command -v yum) && \ + $PKG install -y gcc gcc-c++ make zip binutils libffi-devel # Install this gem RUN gem build datadog-lambda # Install ddtrace gem -RUN gem install datadog-lambda --install-dir "/opt/ruby/gems/$runtime" -RUN gem install datadog -v 2.12 --install-dir "/opt/ruby/gems/$runtime" +RUN MAKEFLAGS="-j$(nproc)" \ + gem install datadog-lambda --install-dir "/opt/ruby/gems/$runtime" --no-document +RUN MAKEFLAGS="-j$(nproc)" \ + gem install datadog -v 2.33 --install-dir "/opt/ruby/gems/$runtime" --no-document + +# Recompile FFI from source — precompiled binaries have glibc mismatch with Lambda AL2 +# +# NOTE: runs after datadog gem as a defensive measure — force-replaces whatever +# transitive FFI variant was pulled, regardless of version resolution. +RUN MAKEFLAGS="-j$(nproc)" \ + gem install ffi -v 1.17.4 --platform ruby --force --install-dir "/opt/ruby/gems/$runtime" --no-document +RUN rm -rf /opt/ruby/gems/$runtime/gems/ffi-*-*-linux-* \ + /opt/ruby/gems/$runtime/specifications/ffi-*-*-linux-*.gemspec WORKDIR /opt # Remove native extension debase-ruby_core_source (25MB) runtimes below Ruby 2.6 @@ -22,7 +35,7 @@ RUN rm -rf ./ruby/gems/$runtime/gems/aws*/ # Remove binaries not needed in AWS Lambda RUN find . -name '*linux-musl*' -prune -exec rm -rf {} + -# Cache files zipped gem files, that aren't used by during runtime, only during +# Cache files zipped gem files, that aren't used by during runtime, only during # installation, so they are safe to delete RUN rm -rf "/opt/ruby/gems/${runtime}/cache" RUN cd /opt diff --git a/integration_tests/appsec_request.rb b/integration_tests/appsec_request.rb new file mode 100644 index 0000000..9b51090 --- /dev/null +++ b/integration_tests/appsec_request.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'datadog/lambda' + +Datadog::Lambda.configure_apm do |c| + c.appsec.enabled = true +end + +def handle(event:, context:) + Datadog::Lambda.wrap(event, context) do + Datadog::Lambda.metric('serverless.integration_test.execution', 1, function: 'appsec-request') + + { + 'statusCode' => 200, + 'message' => 'hello, dog!', + 'eventType' => 'APIGateway' + } + end +end diff --git a/integration_tests/input_events/api-gateway-appsec-blocking.json b/integration_tests/input_events/api-gateway-appsec-blocking.json new file mode 100644 index 0000000..b6f9490 --- /dev/null +++ b/integration_tests/input_events/api-gateway-appsec-blocking.json @@ -0,0 +1,31 @@ +{ + "path": "/test/hello", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com", + "X-Forwarded-For": "192.168.100.1, 192.168.1.1", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "pathParameters": { + "proxy": "hello" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "us4z18", + "stage": "test", + "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9", + "identity": { + "sourceIp": "192.168.100.1" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "GET", + "apiId": "wt6mne2s9k" + }, + "resource": "/{proxy+}", + "httpMethod": "GET", + "queryStringParameters": { + "q": "1' OR '1'='1" + }, + "stageVariables": null +} diff --git a/integration_tests/input_events/api-gateway-appsec.json b/integration_tests/input_events/api-gateway-appsec.json new file mode 100644 index 0000000..f369c56 --- /dev/null +++ b/integration_tests/input_events/api-gateway-appsec.json @@ -0,0 +1,31 @@ +{ + "path": "/test/hello", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Host": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com", + "User-Agent": "Arachni/v1.0", + "X-Forwarded-For": "192.168.100.1, 192.168.1.1", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "pathParameters": { + "proxy": "hello" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "us4z18", + "stage": "test", + "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9", + "identity": { + "sourceIp": "192.168.100.1", + "userAgent": "Arachni/v1.0" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "GET", + "apiId": "wt6mne2s9k" + }, + "resource": "/{proxy+}", + "httpMethod": "GET", + "queryStringParameters": null, + "stageVariables": null +} diff --git a/integration_tests/serverless.yml b/integration_tests/serverless.yml index ec15e1a..e87bf86 100644 --- a/integration_tests/serverless.yml +++ b/integration_tests/serverless.yml @@ -60,3 +60,15 @@ functions: - { Ref: RubyLambdaLayer } environment: DD_FLUSH_TO_LOG: true + + # appsec-request + appsec-request_ruby: + name: integration-tests-rb-${sls:stage}-appsec-request_${env:RUNTIME} + handler: appsec_request.handle + runtime: ${env:SERVERLESS_RUNTIME} + memorySize: 1024 + layers: + - { Ref: RubyLambdaLayer } + environment: + DD_FLUSH_TO_LOG: true + DD_APPSEC_ENABLED: true diff --git a/integration_tests/snapshots/logs/appsec-request_ruby32.log b/integration_tests/snapshots/logs/appsec-request_ruby32.log new file mode 100644 index 0000000..9716a17 --- /dev/null +++ b/integration_tests/snapshots/logs/appsec-request_ruby32.log @@ -0,0 +1,29 @@ + +END Duration: XXXX ms (init: XXXX ms) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.32.0","lang":"ruby","lang_version":"3.2.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.2.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - TRACING - {"enabled":true,"agent_url":null,"analytics_enabled":false,"sample_rate":null,"sampling_rules":null,"integrations_loaded":"aws@","partial_flushing_enabled":false} +START +START +START +START +START +W, [XXXX] WARN XXXX[datadog] Unable to patch Datadog::Tracing::Contrib::Aws::Integration (Available?: false, Loaded? false, Compatible? false, Patchable? false) +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby32","functionname:integration-tests-rb-XXXX-appsec-request_ruby32","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.2.X","resource:integration-tests-rb-XXXX-appsec-request_ruby32","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby32","functionname:integration-tests-rb-XXXX-appsec-request_ruby32","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.2.X","resource:integration-tests-rb-XXXX-appsec-request_ruby32","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby32","functionname:integration-tests-rb-XXXX-appsec-request_ruby32","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.2.X","resource:integration-tests-rb-XXXX-appsec-request_ruby32","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby32","functionname:integration-tests-rb-XXXX-appsec-request_ruby32","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.2.X","resource:integration-tests-rb-XXXX-appsec-request_ruby32","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby32","functionname:integration-tests-rb-XXXX-appsec-request_ruby32","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.2.X","resource:integration-tests-rb-XXXX-appsec-request_ruby32","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby32","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby32","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby32","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby32","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby32","function:appsec-request"],"v":1} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},\"on_match\":[]},\"rule_matches\":[{\"operator\":\"is_sqli\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"server.request.query\",\"key_path\":[\"q\"],\"value\":\"1' OR '1'='1\",\"highlight\":[\"s&sos\"]}]}]}]}"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},\"on_match\":[]},\"rule_matches\":[{\"operator\":\"match_regex\",\"operator_value\":\"^Arachni\\\\/v\",\"parameters\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"],\"value\":\"Arachni/v1.0\",\"highlight\":[\"Arachni/v\"]}]}]}]}"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} diff --git a/integration_tests/snapshots/logs/appsec-request_ruby33.log b/integration_tests/snapshots/logs/appsec-request_ruby33.log new file mode 100644 index 0000000..cbacf26 --- /dev/null +++ b/integration_tests/snapshots/logs/appsec-request_ruby33.log @@ -0,0 +1,29 @@ + +END Duration: XXXX ms (init: XXXX ms) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.32.0","lang":"ruby","lang_version":"3.3.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.3.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - TRACING - {"enabled":true,"agent_url":null,"analytics_enabled":false,"sample_rate":null,"sampling_rules":null,"integrations_loaded":"aws@","partial_flushing_enabled":false} +START +START +START +START +START +W, [XXXX] WARN XXXX[datadog] Unable to patch Datadog::Tracing::Contrib::Aws::Integration (Available?: false, Loaded? false, Compatible? false, Patchable? false) +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby33","functionname:integration-tests-rb-XXXX-appsec-request_ruby33","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.3.X","resource:integration-tests-rb-XXXX-appsec-request_ruby33","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby33","functionname:integration-tests-rb-XXXX-appsec-request_ruby33","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.3.X","resource:integration-tests-rb-XXXX-appsec-request_ruby33","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby33","functionname:integration-tests-rb-XXXX-appsec-request_ruby33","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.3.X","resource:integration-tests-rb-XXXX-appsec-request_ruby33","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby33","functionname:integration-tests-rb-XXXX-appsec-request_ruby33","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.3.X","resource:integration-tests-rb-XXXX-appsec-request_ruby33","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby33","functionname:integration-tests-rb-XXXX-appsec-request_ruby33","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.3.X","resource:integration-tests-rb-XXXX-appsec-request_ruby33","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby33","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby33","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby33","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby33","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby33","function:appsec-request"],"v":1} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},\"on_match\":[]},\"rule_matches\":[{\"operator\":\"is_sqli\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"server.request.query\",\"key_path\":[\"q\"],\"value\":\"1' OR '1'='1\",\"highlight\":[\"s&sos\"]}]}]}]}"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},\"on_match\":[]},\"rule_matches\":[{\"operator\":\"match_regex\",\"operator_value\":\"^Arachni\\\\/v\",\"parameters\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"],\"value\":\"Arachni/v1.0\",\"highlight\":[\"Arachni/v\"]}]}]}]}"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} diff --git a/integration_tests/snapshots/logs/appsec-request_ruby34.log b/integration_tests/snapshots/logs/appsec-request_ruby34.log new file mode 100644 index 0000000..64534ce --- /dev/null +++ b/integration_tests/snapshots/logs/appsec-request_ruby34.log @@ -0,0 +1,29 @@ + +END Duration: XXXX ms (init: XXXX ms) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.32.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - TRACING - {"enabled":true,"agent_url":null,"analytics_enabled":false,"sample_rate":null,"sampling_rules":null,"integrations_loaded":"aws@","partial_flushing_enabled":false} +START +START +START +START +START +W, [XXXX] WARN XXXX[datadog] Unable to patch Datadog::Tracing::Contrib::Aws::Integration (Available?: false, Loaded? false, Compatible? false, Patchable? false) +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-appsec-request_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-appsec-request_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-appsec-request_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-appsec-request_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-appsec-request_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-appsec-request_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-appsec-request_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-appsec-request_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-appsec-request_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-appsec-request_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:appsec-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:appsec-request"],"v":1} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},\"on_match\":[]},\"rule_matches\":[{\"operator\":\"is_sqli\",\"operator_value\":\"\",\"parameters\":[{\"address\":\"server.request.query\",\"key_path\":[\"q\"],\"value\":\"1' OR '1'='1\",\"highlight\":[\"s&sos\"]}]}]}]}"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},\"on_match\":[]},\"rule_matches\":[{\"operator\":\"match_regex\",\"operator_value\":\"^Arachni\\\\/v\",\"parameters\":[{\"address\":\"server.request.headers.no_cookies\",\"key_path\":[\"user-agent\"],\"value\":\"Arachni/v1.0\",\"highlight\":[\"Arachni/v\"]}]}]}]}"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} diff --git a/integration_tests/snapshots/logs/async-metrics_ruby34.log b/integration_tests/snapshots/logs/async-metrics_ruby34.log index 4eaabe5..5d93eb1 100644 --- a/integration_tests/snapshots/logs/async-metrics_ruby34.log +++ b/integration_tests/snapshots/logs/async-metrics_ruby34.log @@ -1,25 +1,37 @@ -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-async-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-async-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-async-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-async-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-async-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-async-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:APIGateway"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SNS"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SNS"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} END Duration: XXXX ms (init: XXXX ms) Memory Used: XXXX MB END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB -I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.12.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.32.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - TRACING - {"enabled":true,"agent_url":null,"analytics_enabled":false,"sample_rate":null,"sampling_rules":null,"integrations_loaded":"aws@","partial_flushing_enabled":false} Processed APIGateway request +Processed APIGateway request +Processed APIGateway request Processed SNS request Processed SQS request START START START +START +START W, [XXXX] WARN XXXX[datadog] Unable to patch Datadog::Tracing::Contrib::Aws::Integration (Available?: false, Loaded? false, Compatible? false, Patchable? false) +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-async-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-async-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-async-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-async-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-async-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-async-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-async-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-async-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-async-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-async-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:APIGateway"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:APIGateway"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:APIGateway"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SNS"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SNS"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} diff --git a/integration_tests/snapshots/logs/http-requests_ruby34.log b/integration_tests/snapshots/logs/http-requests_ruby34.log index b0be88d..5258c71 100644 --- a/integration_tests/snapshots/logs/http-requests_ruby34.log +++ b/integration_tests/snapshots/logs/http-requests_ruby34.log @@ -1,22 +1,34 @@ -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-http-requests_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-http-requests_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-http-requests_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-http-requests_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-http-requests_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-http-requests_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} END Duration: XXXX ms (init: XXXX ms) Memory Used: XXXX MB END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB -I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.12.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.32.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - TRACING - {"enabled":true,"agent_url":null,"analytics_enabled":false,"sample_rate":null,"sampling_rules":null,"integrations_loaded":"aws@","partial_flushing_enabled":false} -Snapshot test http requests successfully made to URLs: ["ip-ranges.datadoghq.com", "ip-ranges.datadoghq.eu"] -Snapshot test http requests successfully made to URLs: ["ip-ranges.datadoghq.com", "ip-ranges.datadoghq.eu"] -Snapshot test http requests successfully made to URLs: ["ip-ranges.datadoghq.com", "ip-ranges.datadoghq.eu"] START START START +START +START +Snapshot test http requests successfully made to URLs: ["ip-ranges.datadoghq.com", "ip-ranges.datadoghq.eu"] +Snapshot test http requests successfully made to URLs: ["ip-ranges.datadoghq.com", "ip-ranges.datadoghq.eu"] +Snapshot test http requests successfully made to URLs: ["ip-ranges.datadoghq.com", "ip-ranges.datadoghq.eu"] +Snapshot test http requests successfully made to URLs: ["ip-ranges.datadoghq.com", "ip-ranges.datadoghq.eu"] +Snapshot test http requests successfully made to URLs: ["ip-ranges.datadoghq.com", "ip-ranges.datadoghq.eu"] W, [XXXX] WARN XXXX[datadog] Unable to patch Datadog::Tracing::Contrib::Aws::Integration (Available?: false, Loaded? false, Compatible? false, Patchable? false) +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-http-requests_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-http-requests_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-http-requests_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-http-requests_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-http-requests_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-http-requests_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-http-requests_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-http-requests_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-http-requests_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-http-requests_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} diff --git a/integration_tests/snapshots/logs/process-input-traced_ruby34.log b/integration_tests/snapshots/logs/process-input-traced_ruby34.log index 58e3c3f..3ba48d3 100644 --- a/integration_tests/snapshots/logs/process-input-traced_ruby34.log +++ b/integration_tests/snapshots/logs/process-input-traced_ruby34.log @@ -1,19 +1,29 @@ -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-process-input-traced_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-process-input-traced_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-process-input-traced_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-process-input-traced_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-process-input-traced_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-process-input-traced_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"get_record_ids","parent_id":"XXXX","resource":"get_record_ids","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"get_record_ids","parent_id":"XXXX","resource":"get_record_ids","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"get_record_ids","parent_id":"XXXX","resource":"get_record_ids","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"get_api_gateway_request_id","parent_id":"XXXX","resource":"get_api_gateway_request_id","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} END Duration: XXXX ms (init: XXXX ms) Memory Used: XXXX MB END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB -I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.12.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.32.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - TRACING - {"enabled":true,"agent_url":null,"analytics_enabled":false,"sample_rate":null,"sampling_rules":null,"integrations_loaded":"aws@","partial_flushing_enabled":false} START START START +START +START W, [XXXX] WARN XXXX[datadog] Unable to patch Datadog::Tracing::Contrib::Aws::Integration (Available?: false, Loaded? false, Compatible? false, Patchable? false) +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-process-input-traced_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-process-input-traced_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-process-input-traced_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-process-input-traced_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-process-input-traced_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-process-input-traced_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-process-input-traced_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-process-input-traced_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-process-input-traced_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-process-input-traced_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","function:http-request"],"v":1} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"get_record_ids","parent_id":"XXXX","resource":"get_record_ids","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"get_record_ids","parent_id":"XXXX","resource":"get_record_ids","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"get_record_ids","parent_id":"XXXX","resource":"get_record_ids","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"get_api_gateway_request_id","parent_id":"XXXX","resource":"get_api_gateway_request_id","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"get_record_ids","parent_id":"XXXX","resource":"get_record_ids","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"get_api_gateway_request_id","parent_id":"XXXX","resource":"get_api_gateway_request_id","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"get_record_ids","parent_id":"XXXX","resource":"get_record_ids","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"get_api_gateway_request_id","parent_id":"XXXX","resource":"get_api_gateway_request_id","service":"index","span_id":"XXXX","trace_id":"XXXX","type":null,"span_links":[],"start":XXXX,"duration":XXXX},{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} diff --git a/integration_tests/snapshots/logs/sync-metrics_ruby34.log b/integration_tests/snapshots/logs/sync-metrics_ruby34.log index 1dfc212..bd32213 100644 --- a/integration_tests/snapshots/logs/sync-metrics_ruby34.log +++ b/integration_tests/snapshots/logs/sync-metrics_ruby34.log @@ -1,25 +1,37 @@ -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-sync-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-sync-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-sync-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-sync-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-sync-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-sync-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:APIGateway"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SNS"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SNS"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} -{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} -{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} END Duration: XXXX ms (init: XXXX ms) Memory Used: XXXX MB END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB -I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.12.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +END Duration: XXXX ms (init: XXXX) Memory Used: XXXX MB +I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - CORE - {"date":"XXXX","os_name":"XXXX","version":"2.32.0","lang":"ruby","lang_version":"3.4.X","env":null,"service":"index","dd_version":null,"debug":false,"tags":"_dd.origin:lambda","runtime_metrics_enabled":false,"vm":"ruby-3.4.X","health_metrics_enabled":false,"profiling_enabled":false,"dynamic_instrumentation_enabled":false} I, [XXXX] INFO XXXX[datadog] DATADOG CONFIGURATION - TRACING - {"enabled":true,"agent_url":null,"analytics_enabled":false,"sample_rate":null,"sampling_rules":null,"integrations_loaded":"aws@","partial_flushing_enabled":false} Processed APIGateway request +Processed APIGateway request +Processed APIGateway request Processed SNS request Processed SQS request START START START +START +START W, [XXXX] WARN XXXX[datadog] Unable to patch Datadog::Tracing::Contrib::Aws::Integration (Available?: false, Loaded? false, Compatible? false, Patchable? false) +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-sync-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-sync-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-sync-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-sync-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-sync-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-sync-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-sync-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:false","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-sync-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"aws.lambda.enhanced.invocations","t":["dd_lambda_layer:datadog-ruby34","functionname:integration-tests-rb-XXXX-sync-metrics_ruby34","region:eu-west-1","account_id:XXXX","memorysize:1024","cold_start:true","runtime:Ruby 3.4.X","resource:integration-tests-rb-XXXX-sync-metrics_ruby34","datadog_lambda:X.X.X","dd_trace:2.XX.X"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:APIGateway"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:APIGateway"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:APIGateway"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SNS"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.execution","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SNS"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} +{"e":XXXX,"m":"serverless.integration_test.records_processed","t":["dd_lambda_layer:datadog-ruby34","tagkey:tagvalue","eventsource:SQS"],"v":1} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} +{"traces":[[{"error":0,"meta":{"XXXX": "XXXX"},"metrics":{"XXXX": "XXXX"},"meta_struct":{},"name":"aws.lambda","parent_id":"XXXX","resource":"dd-tracer-serverless-span","service":"aws.lambda","span_id":"XXXX","trace_id":"XXXX","type":"serverless","span_links":[],"start":XXXX,"duration":XXXX}]]} diff --git a/integration_tests/snapshots/return_values/appsec-request_api-gateway-appsec-blocking.json b/integration_tests/snapshots/return_values/appsec-request_api-gateway-appsec-blocking.json new file mode 100644 index 0000000..eb8ee03 --- /dev/null +++ b/integration_tests/snapshots/return_values/appsec-request_api-gateway-appsec-blocking.json @@ -0,0 +1,5 @@ +{ + "statusCode": 200, + "message": "hello, dog!", + "eventType": "APIGateway" +} diff --git a/integration_tests/snapshots/return_values/appsec-request_api-gateway-appsec.json b/integration_tests/snapshots/return_values/appsec-request_api-gateway-appsec.json new file mode 100644 index 0000000..eb8ee03 --- /dev/null +++ b/integration_tests/snapshots/return_values/appsec-request_api-gateway-appsec.json @@ -0,0 +1,5 @@ +{ + "statusCode": 200, + "message": "hello, dog!", + "eventType": "APIGateway" +} diff --git a/integration_tests/snapshots/return_values/appsec-request_api-gateway-get.json b/integration_tests/snapshots/return_values/appsec-request_api-gateway-get.json new file mode 100644 index 0000000..eb8ee03 --- /dev/null +++ b/integration_tests/snapshots/return_values/appsec-request_api-gateway-get.json @@ -0,0 +1,5 @@ +{ + "statusCode": 200, + "message": "hello, dog!", + "eventType": "APIGateway" +} diff --git a/integration_tests/snapshots/return_values/appsec-request_sns.json b/integration_tests/snapshots/return_values/appsec-request_sns.json new file mode 100644 index 0000000..eb8ee03 --- /dev/null +++ b/integration_tests/snapshots/return_values/appsec-request_sns.json @@ -0,0 +1,5 @@ +{ + "statusCode": 200, + "message": "hello, dog!", + "eventType": "APIGateway" +} diff --git a/integration_tests/snapshots/return_values/appsec-request_sqs.json b/integration_tests/snapshots/return_values/appsec-request_sqs.json new file mode 100644 index 0000000..eb8ee03 --- /dev/null +++ b/integration_tests/snapshots/return_values/appsec-request_sqs.json @@ -0,0 +1,5 @@ +{ + "statusCode": 200, + "message": "hello, dog!", + "eventType": "APIGateway" +} diff --git a/integration_tests/snapshots/return_values/async-metrics_api-gateway-appsec-blocking.json b/integration_tests/snapshots/return_values/async-metrics_api-gateway-appsec-blocking.json new file mode 100644 index 0000000..6f453e9 --- /dev/null +++ b/integration_tests/snapshots/return_values/async-metrics_api-gateway-appsec-blocking.json @@ -0,0 +1,5 @@ +{ + "message": "hello, dog!", + "eventType": "APIGateway", + "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9" +} diff --git a/integration_tests/snapshots/return_values/async-metrics_api-gateway-appsec.json b/integration_tests/snapshots/return_values/async-metrics_api-gateway-appsec.json new file mode 100644 index 0000000..6f453e9 --- /dev/null +++ b/integration_tests/snapshots/return_values/async-metrics_api-gateway-appsec.json @@ -0,0 +1,5 @@ +{ + "message": "hello, dog!", + "eventType": "APIGateway", + "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9" +} diff --git a/integration_tests/snapshots/return_values/http-requests_api-gateway-appsec-blocking.json b/integration_tests/snapshots/return_values/http-requests_api-gateway-appsec-blocking.json new file mode 100644 index 0000000..eb1e9d3 --- /dev/null +++ b/integration_tests/snapshots/return_values/http-requests_api-gateway-appsec-blocking.json @@ -0,0 +1,3 @@ +{ + "message": "hello, dog!" +} diff --git a/integration_tests/snapshots/return_values/http-requests_api-gateway-appsec.json b/integration_tests/snapshots/return_values/http-requests_api-gateway-appsec.json new file mode 100644 index 0000000..eb1e9d3 --- /dev/null +++ b/integration_tests/snapshots/return_values/http-requests_api-gateway-appsec.json @@ -0,0 +1,3 @@ +{ + "message": "hello, dog!" +} diff --git a/integration_tests/snapshots/return_values/process-input-traced_api-gateway-appsec-blocking.json b/integration_tests/snapshots/return_values/process-input-traced_api-gateway-appsec-blocking.json new file mode 100644 index 0000000..12d7e1a --- /dev/null +++ b/integration_tests/snapshots/return_values/process-input-traced_api-gateway-appsec-blocking.json @@ -0,0 +1,5 @@ +{ + "message": "hello, dog!", + "recordIds": [], + "eventType": "APIGateway" +} diff --git a/integration_tests/snapshots/return_values/process-input-traced_api-gateway-appsec.json b/integration_tests/snapshots/return_values/process-input-traced_api-gateway-appsec.json new file mode 100644 index 0000000..12d7e1a --- /dev/null +++ b/integration_tests/snapshots/return_values/process-input-traced_api-gateway-appsec.json @@ -0,0 +1,5 @@ +{ + "message": "hello, dog!", + "recordIds": [], + "eventType": "APIGateway" +} diff --git a/integration_tests/snapshots/return_values/sync-metrics_api-gateway-appsec-blocking.json b/integration_tests/snapshots/return_values/sync-metrics_api-gateway-appsec-blocking.json new file mode 100644 index 0000000..6f453e9 --- /dev/null +++ b/integration_tests/snapshots/return_values/sync-metrics_api-gateway-appsec-blocking.json @@ -0,0 +1,5 @@ +{ + "message": "hello, dog!", + "eventType": "APIGateway", + "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9" +} diff --git a/integration_tests/snapshots/return_values/sync-metrics_api-gateway-appsec.json b/integration_tests/snapshots/return_values/sync-metrics_api-gateway-appsec.json new file mode 100644 index 0000000..6f453e9 --- /dev/null +++ b/integration_tests/snapshots/return_values/sync-metrics_api-gateway-appsec.json @@ -0,0 +1,5 @@ +{ + "message": "hello, dog!", + "eventType": "APIGateway", + "requestId": "41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9" +} diff --git a/lib/datadog/lambda.rb b/lib/datadog/lambda.rb index 4eee6d4..604c385 100644 --- a/lib/datadog/lambda.rb +++ b/lib/datadog/lambda.rb @@ -29,6 +29,8 @@ module Lambda # Configures Datadog's APM tracer with lambda specific defaults. # Same options can be given as Datadog.configure in tracer # See https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#quickstart-for-ruby-applications + # + # rubocop:disable Metrics/AbcSize def self.configure_apm require 'datadog/tracing' require 'datadog/tracing/transport/io' @@ -48,30 +50,36 @@ def self.configure_apm c.tracing.instrument :aws if trace_managed_services? yield(c) if block_given? + + c.appsec.instrument(:aws_lambda) end end + # rubocop:enable Metrics/AbcSize # Wrap the body of a lambda invocation # @param event [Object] event sent to lambda # @param context [Object] lambda context # @param block [Proc] implementation of the handler function. + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def self.wrap(event, context, &block) @listener ||= initialize_listener record_enhanced('invocations', context) begin cold = @is_cold_start @listener&.on_start(event:, request_context: context, cold_start: cold) - @response = block.call + @response = @listener&.response_override || block.call rescue StandardError => e record_enhanced('errors', context) raise e ensure @listener&.on_end(response: @response, request_context: context) + @response = @listener&.response_override || @response @is_cold_start = false @metrics_client.close end @response end + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity # Gets the current tracing context def self.trace_context diff --git a/lib/datadog/lambda/appsec.rb b/lib/datadog/lambda/appsec.rb new file mode 100644 index 0000000..a27831a --- /dev/null +++ b/lib/datadog/lambda/appsec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require 'json' +require_relative 'appsec/request' +require_relative 'appsec/event_normalizer' + +module Datadog + module Lambda + # AppSec integration for AWS Lambda invocations. + module AppSec + class << self + # rubocop:disable Metrics/AbcSize + def on_start(event, trace:, span:, cold_start: false) + @request = nil + return unless enabled? + + context = create_context(trace, span) + return unless Datadog::AppSec::Context.active + + tag_and_keep(context, cold_start: cold_start) + + event = EventNormalizer.normalize(event) + @request = Request.from_normalized(event) + + payload = Datadog::AppSec::Instrumentation::Gateway::DataContainer.new( + event, context: context + ) + + interrupt_params = catch(Datadog::AppSec::Ext::INTERRUPT) do + Datadog::AppSec::Instrumentation.gateway.push('aws_lambda.request.start', payload) + nil + end + + return unless interrupt_params + + context.mark_as_interrupted! + response_override(interrupt_params, headers: @request.headers) + rescue StandardError => e + Datadog::Utils.logger.debug("failed to start AppSec: #{e}") + end + # rubocop:enable Metrics/AbcSize + + def on_finish(response) + return unless enabled? + + context = Datadog::AppSec::Context.active + return unless context + + payload = Datadog::AppSec::Instrumentation::Gateway::DataContainer.new( + response, context: context + ) + + interrupt_params = catch(Datadog::AppSec::Ext::INTERRUPT) do + Datadog::AppSec::Instrumentation.gateway.push('aws_lambda.response.start', payload) + nil + end + + context.mark_as_interrupted! if interrupt_params + + Datadog::AppSec::Event.record(context, request: @request) + context.export_metrics + context.export_request_telemetry + + response_override(interrupt_params, headers: @request.headers) if interrupt_params + rescue StandardError => e + Datadog::Utils.logger.debug "failed to finish AppSec: #{e}" + ensure + Datadog::AppSec::Context.deactivate if context + end + + private + + def enabled? + defined?(Datadog::AppSec) && + Datadog::AppSec.respond_to?(:enabled?) && + Datadog::AppSec.enabled? + end + + def create_context(trace, span) + return if trace.nil? || span.nil? + + security_engine = Datadog::AppSec.security_engine + return unless security_engine + + context = Datadog::AppSec::Context.new(trace, span, security_engine.new_runner) + Datadog::AppSec::Context.activate(context) + + context + end + + def tag_and_keep(context, cold_start:) + span = context.span + trace = context.trace + + return unless trace && span + + span.set_metric(Datadog::AppSec::Ext::TAG_APPSEC_ENABLED, 1) + span.set_tag('_dd.runtime_family', 'ruby') + span.set_tag('_dd.appsec.waf.version', Datadog::AppSec::WAF::VERSION::BASE_STRING) + + ruleset_version = context.waf_runner_ruleset_version + return unless ruleset_version + + span.set_tag('_dd.appsec.event_rules.version', ruleset_version) + + return unless cold_start + + span.set_tag( + '_dd.appsec.event_rules.addresses', JSON.dump(context.waf_runner_known_addresses) + ) + + trace.keep! + trace.set_tag( + Datadog::Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, + Datadog::Tracing::Sampling::Ext::Decision::ASM + ) + end + + def response_override(interrupt_params, headers:) + response = Datadog::AppSec::Response.from_interrupt_params( + interrupt_params, headers['accept'] + ) + + { + 'statusCode' => response.status, + 'headers' => response.headers, + 'body' => response.body.join + } + end + end + end + end +end diff --git a/lib/datadog/lambda/appsec/event_normalizer.rb b/lib/datadog/lambda/appsec/event_normalizer.rb new file mode 100644 index 0000000..e05eeb1 --- /dev/null +++ b/lib/datadog/lambda/appsec/event_normalizer.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +module Datadog + module Lambda + module AppSec + # Normalizes API Gateway v1/v2 event payloads into a standard key set. + module EventNormalizer + module_function + + def normalize(event) + event.key?('httpMethod') ? normalize_v1(event) : normalize_v2(event) + end + + def normalize_v1(event) + data = { + 'method' => event['httpMethod'], + 'path' => event['path'], + 'headers' => event['headers'], + 'query' => event['multiValueQueryStringParameters'] || event['queryStringParameters'], + 'source_ip' => event.dig('requestContext', 'identity', 'sourceIp'), + 'body' => event['body'], + 'base64_encoded' => event['isBase64Encoded'], + 'path_params' => event['pathParameters'] + } + data.compact! + data + end + + def normalize_v2(event) + data = { + 'method' => event.dig('requestContext', 'http', 'method'), + 'path' => event['rawPath'], + 'headers' => event['headers'], + 'cookies' => event['cookies'], + 'query' => event['queryStringParameters'], + 'query_string' => event['rawQueryString'], + 'source_ip' => event.dig('requestContext', 'http', 'sourceIp'), + 'body' => event['body'], + 'base64_encoded' => event['isBase64Encoded'], + 'path_params' => event['pathParameters'] + } + data.compact! + data + end + end + end + end +end diff --git a/lib/datadog/lambda/appsec/request.rb b/lib/datadog/lambda/appsec/request.rb new file mode 100644 index 0000000..42926f2 --- /dev/null +++ b/lib/datadog/lambda/appsec/request.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module Datadog + module Lambda + module AppSec + # Minimal request object for AppSec event recording. + # + # WARNING: It's a minimal data for interface compliance + # + # @see Datadog::AppSec::Event.record + # @see Datadog::AppSec::Contrib::Rack::Gateway::Request + class Request + attr_reader :host, :user_agent, :remote_addr, :headers + + class << self + def from_normalized(event) + headers = lowercase_headers(event) + + new( + host: headers['host'], + user_agent: headers['user-agent'], + remote_addr: event['source_ip'], + headers: headers + ) + end + + private + + def lowercase_headers(event) + (event['headers'] || {}).each_with_object({}) do |(key, value), hash| + hash[key.downcase] = value + end + end + end + + def initialize(host:, user_agent:, remote_addr:, headers:) + @host = host + @user_agent = user_agent + @remote_addr = remote_addr + @headers = headers + end + end + end + end +end diff --git a/lib/datadog/lambda/trace/listener.rb b/lib/datadog/lambda/trace/listener.rb index e4ac564..078e3de 100644 --- a/lib/datadog/lambda/trace/listener.rb +++ b/lib/datadog/lambda/trace/listener.rb @@ -11,11 +11,16 @@ require 'datadog/lambda/trace/context' require 'datadog/lambda/trace/patch_http' require 'datadog/lambda/trace/ddtrace' +require 'datadog/lambda/appsec' module Datadog module Trace # TraceListener tracks tracing context information class Listener + # AppSec blocking response that replaces the handler result. + # Set during either on_start or on_end when WAF decides to block. + attr_reader :response_override + @trace = nil def initialize(handler_name:, function_name:, patch_http:, merge_xray_traces:) @@ -50,10 +55,14 @@ def on_start(event:, request_context:, cold_start:) @trace = Datadog::Tracing.trace('aws.lambda', **options) Datadog::Trace.apply_datadog_trace_context(Datadog::Trace.trace_context) + @response_override = Datadog::Lambda::AppSec.on_start( + event, trace: Datadog::Tracing.active_trace, span: @trace, cold_start: cold_start + ) end # rubocop:enable Metrics/AbcSize def on_end(response:, request_context:) + @response_override = Datadog::Lambda::AppSec.on_finish(response) Datadog::Utils.send_end_invocation_request(response:, span_id: @trace.id, request_context:) @trace&.finish end diff --git a/scripts/build_layers.sh b/scripts/build_layers.sh index 304b334..3d67092 100755 --- a/scripts/build_layers.sh +++ b/scripts/build_layers.sh @@ -36,9 +36,12 @@ function docker_build_zip { # Install datadog ruby in a docker container to avoid the mess from switching # between different ruby runtimes. + # + # NOTE: using the Lambda base image so native extensions (FFI, libddwaf) + # compile against the same libffi available at runtime on Lambda. temp_dir=$(mktemp -d) docker buildx build -t datadog-lambda-ruby-${arch}:$1 . --no-cache \ - --build-arg "image=ruby:${1}" \ + --build-arg "image=public.ecr.aws/lambda/ruby:${1}" \ --build-arg "runtime=${1}.0" \ --platform linux/${arch} \ --progress=plain \ diff --git a/scripts/run_integration_tests.sh b/scripts/run_integration_tests.sh index 52bdfb7..b094ba2 100755 --- a/scripts/run_integration_tests.sh +++ b/scripts/run_integration_tests.sh @@ -10,7 +10,7 @@ set -e # These values need to be in sync with serverless.yml, where there needs to be a function # defined for every handler_runtime combination -LAMBDA_HANDLERS=("async-metrics" "sync-metrics" "http-requests" "process-input-traced") +LAMBDA_HANDLERS=("async-metrics" "sync-metrics" "http-requests" "process-input-traced" "appsec-request") RUNTIMES=("ruby32" "ruby33") LOGS_WAIT_SECONDS=45 diff --git a/test/datadog/lambda/appsec.spec.rb b/test/datadog/lambda/appsec.spec.rb new file mode 100644 index 0000000..c1d3eea --- /dev/null +++ b/test/datadog/lambda/appsec.spec.rb @@ -0,0 +1,397 @@ +# frozen_string_literal: true + +require 'datadog/lambda' +require 'datadog/lambda/appsec' + +RSpec.describe Datadog::Lambda::AppSec do + before do + stub_const('Datadog::AppSec::WAF::VERSION::BASE_STRING', '1.30.0') + allow(Datadog::AppSec::Instrumentation).to receive(:gateway).and_return(gateway) + allow(gateway).to receive(:push) + end + + let(:gateway) { instance_double(Datadog::AppSec::Instrumentation::Gateway) } + let(:context_span) { instance_double(Datadog::Tracing::SpanOperation, set_tag: nil, set_metric: nil) } + let(:appsec_context) do + instance_double( + Datadog::AppSec::Context, + span: context_span, + state: {}, + export_metrics: nil, + export_request_telemetry: nil + ) + end + + describe '.on_start' do + subject(:on_start) do + described_class.on_start({'httpMethod' => 'GET', 'path' => '/'}, trace: trace, span: span) + end + + let(:trace) { instance_double(Datadog::Tracing::TraceOperation) } + let(:span) { instance_double(Datadog::Tracing::SpanOperation, set_metric: nil, set_tag: nil) } + + context 'when appsec is disabled' do + before { allow(Datadog::AppSec).to receive(:enabled?).and_return(false) } + + it 'does not push to gateway' do + on_start + + expect(gateway).not_to have_received(:push) + end + end + + context 'when appsec is enabled' do + before do + allow(Datadog::AppSec).to receive_messages(enabled?: true, security_engine: security_engine) + allow(Datadog::AppSec::Context).to receive_messages(activate: nil, active: appsec_context) + end + + let(:security_engine) { instance_double(Datadog::AppSec::SecurityEngine::Engine, new_runner: waf_runner) } + let(:waf_runner) { instance_double(Datadog::AppSec::SecurityEngine::Runner, ruleset_version: nil) } + + it 'marks span as appsec-enabled' do + on_start + + expect(span).to have_received(:set_metric).with(Datadog::AppSec::Ext::TAG_APPSEC_ENABLED, 1) + end + + it 'pushes event to gateway' do + on_start + + expect(gateway).to have_received(:push).with( + 'aws_lambda.request.start', kind_of(Datadog::AppSec::Instrumentation::Gateway::DataContainer) + ) + end + + context 'when security_engine is nil' do + before do + allow(Datadog::AppSec).to receive(:security_engine).and_return(nil) + allow(Datadog::AppSec::Context).to receive(:active).and_return(nil) + end + + it 'skips context activation and gateway push' do + on_start + + aggregate_failures('skipped activation') do + expect(Datadog::AppSec::Context).not_to have_received(:activate) + expect(gateway).not_to have_received(:push) + end + end + end + + context 'when trace is nil' do + subject(:on_start) do + described_class.on_start({'httpMethod' => 'GET', 'path' => '/'}, trace: nil, span: span) + end + + before { allow(Datadog::AppSec::Context).to receive(:active).and_return(nil) } + + it 'skips context activation and gateway push' do + on_start + + aggregate_failures('skipped activation') do + expect(Datadog::AppSec::Context).not_to have_received(:activate) + expect(gateway).not_to have_received(:push) + end + end + end + + context 'when span is nil' do + subject(:on_start) do + described_class.on_start({'httpMethod' => 'GET', 'path' => '/'}, trace: trace, span: nil) + end + + before { allow(Datadog::AppSec::Context).to receive(:active).and_return(nil) } + + it 'skips context activation and gateway push' do + on_start + + aggregate_failures('skipped activation') do + expect(Datadog::AppSec::Context).not_to have_received(:activate) + expect(gateway).not_to have_received(:push) + end + end + end + + context 'when gateway push triggers a blocking interrupt' do + before do + allow(Datadog::AppSec::Context).to receive(:new).and_return(appsec_context) + allow(appsec_context).to receive_messages( + trace: trace, + waf_runner_ruleset_version: nil, + mark_as_interrupted!: nil + ) + + allow(gateway).to receive(:push).and_invoke(lambda { |_name, _payload| + throw(Datadog::AppSec::Ext::INTERRUPT, {'status_code' => 403, 'type' => 'auto'}) + }) + end + + it 'returns a Lambda-shaped blocking response' do + expect(on_start).to include( + 'statusCode' => 403, + 'headers' => {'Content-Type' => 'application/json'}, + 'body' => include('blocked') + ) + end + end + + context 'when an error occurs' do + before { allow(Datadog::AppSec::Context).to receive(:new).and_raise(StandardError, 'boom') } + + it { expect { on_start }.not_to raise_error } + end + + context 'when waf ruleset is loaded' do + before do + allow(Datadog::AppSec::Context).to receive(:new).and_return(appsec_context) + allow(appsec_context).to receive_messages( + waf_runner_ruleset_version: '1.12.0', + waf_runner_known_addresses: ['server.request.headers.no_cookies'] + ) + + allow(trace).to receive_messages(keep!: nil, set_tag: nil) + allow(span).to receive(:set_tag) + end + + let(:appsec_context) do + instance_double( + Datadog::AppSec::Context, + span: span, + trace: trace, + state: {}, + export_metrics: nil, + export_request_telemetry: nil + ) + end + + it 'sets runtime family and WAF version on the span' do + on_start + + aggregate_failures('appsec span tags') do + expect(span).to have_received(:set_tag).with('_dd.runtime_family', 'ruby') + expect(span).to have_received(:set_tag).with('_dd.appsec.waf.version', '1.30.0') + end + end + + it 'sets event rules version from the WAF runner' do + on_start + + expect(span).to have_received(:set_tag).with('_dd.appsec.event_rules.version', '1.12.0') + end + + context 'when cold_start is true' do + subject(:on_start) do + described_class.on_start( + {'httpMethod' => 'GET', 'path' => '/'}, + trace: trace, span: span, cold_start: true + ) + end + + it 'sets known addresses and keeps trace' do + on_start + + aggregate_failures('cold start tags') do + expect(span).to have_received(:set_tag).with( + '_dd.appsec.event_rules.addresses', + '["server.request.headers.no_cookies"]' + ) + expect(trace).to have_received(:keep!) + expect(trace).to have_received(:set_tag).with( + Datadog::Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER, + Datadog::Tracing::Sampling::Ext::Decision::ASM + ) + end + end + end + + context 'when cold_start is false' do + it 'does not send cold start tags' do + on_start + + aggregate_failures('no cold start tags') do + expect(span).not_to have_received(:set_tag).with('_dd.appsec.event_rules.addresses', anything) + expect(trace).not_to have_received(:keep!) + end + end + end + + context 'when ruleset version is not set' do + before { allow(appsec_context).to receive(:waf_runner_ruleset_version).and_return(nil) } + + it 'skips event rules tags' do + on_start + + aggregate_failures('skipped rules tags') do + expect(span).not_to have_received(:set_tag).with('_dd.appsec.event_rules.version', anything) + expect(span).not_to have_received(:set_tag).with('_dd.appsec.event_rules.addresses', anything) + end + end + end + + context 'when span is not set' do + subject(:on_start) do + described_class.on_start({'httpMethod' => 'GET', 'path' => '/'}, trace: trace, span: nil) + end + + before { allow(Datadog::AppSec::Context).to receive(:active).and_return(nil) } + + it 'does not set any appsec tags' do + on_start + + expect(span).not_to have_received(:set_tag) + end + end + + context 'when context trace is not set' do + before { allow(appsec_context).to receive(:trace).and_return(nil) } + + it 'does not set any tags' do + on_start + + aggregate_failures('no tags when trace nil') do + expect(span).not_to have_received(:set_tag) + expect(span).not_to have_received(:set_metric).with(Datadog::AppSec::Ext::TAG_APPSEC_ENABLED, anything) + end + end + end + end + end + end + + describe '.on_finish' do + subject(:on_finish) { described_class.on_finish({ 'statusCode' => 200 }) } + + context 'when appsec is disabled' do + before do + allow(Datadog::AppSec).to receive(:enabled?).and_return(false) + allow(Datadog::AppSec::Context).to receive(:active).and_return(appsec_context) + end + + it 'does not push to gateway' do + on_finish + + expect(gateway).not_to have_received(:push) + end + end + + context 'when no active context exists' do + before do + allow(Datadog::AppSec).to receive(:enabled?).and_return(true) + allow(Datadog::AppSec::Context).to receive(:active).and_return(nil) + end + + it 'does not push to gateway' do + on_finish + + expect(gateway).not_to have_received(:push) + end + end + + context 'when active context exists' do + before do + allow(Datadog::AppSec).to receive(:enabled?).and_return(true) + allow(Datadog::AppSec::Context).to receive_messages(active: appsec_context, deactivate: nil) + allow(Datadog::AppSec::Event).to receive(:record) + end + + it 'pushes response and records events' do + on_finish + + aggregate_failures('response processing') do + expect(gateway).to have_received(:push).with( + 'aws_lambda.response.start', kind_of(Datadog::AppSec::Instrumentation::Gateway::DataContainer) + ) + expect(Datadog::AppSec::Event).to have_received(:record).with(appsec_context, request: anything) + end + end + + it 'exports telemetry and deactivates' do + on_finish + + aggregate_failures('AppSec deactivation') do + expect(appsec_context).to have_received(:export_metrics) + expect(appsec_context).to have_received(:export_request_telemetry) + expect(Datadog::AppSec::Context).to have_received(:deactivate) + end + end + + context 'when a security event occurs' do + before do + allow(Datadog::AppSec).to receive(:security_engine).and_return(security_engine) + allow(Datadog::AppSec::Context).to receive(:activate) + + described_class.on_start( + { + 'httpMethod' => 'GET', + 'headers' => {'Host' => 'example.com', 'User-Agent' => 'TestBot'}, + 'requestContext' => {'identity' => {'sourceIp' => '1.2.3.4'}} + }, + trace: trace, span: span + ) + end + + let(:trace) { instance_double(Datadog::Tracing::TraceOperation) } + let(:span) { instance_double(Datadog::Tracing::SpanOperation, set_metric: nil, set_tag: nil) } + let(:security_engine) { instance_double(Datadog::AppSec::SecurityEngine::Engine, new_runner: waf_runner) } + let(:waf_runner) { instance_double(Datadog::AppSec::SecurityEngine::Runner, ruleset_version: nil) } + + it 'records security event with request' do + on_finish + + expect(Datadog::AppSec::Event).to have_received(:record).with( + appsec_context, request: kind_of(Datadog::Lambda::AppSec::Request) + ) + end + end + + context 'when gateway push triggers a blocking interrupt' do + before do + allow(Datadog::AppSec).to receive(:security_engine).and_return(security_engine) + allow(Datadog::AppSec::Context).to receive(:activate) + allow(appsec_context).to receive(:mark_as_interrupted!) + allow(gateway).to receive(:push).and_invoke(lambda { |_name, _payload| + throw(Datadog::AppSec::Ext::INTERRUPT, {'status_code' => 403, 'type' => 'auto'}) + }) + + described_class.on_start( + {'httpMethod' => 'GET', 'headers' => {'Accept' => 'application/json'}}, + trace: trace, span: span + ) + end + + let(:trace) { instance_double(Datadog::Tracing::TraceOperation) } + let(:span) { instance_double(Datadog::Tracing::SpanOperation, set_metric: nil, set_tag: nil) } + let(:security_engine) { instance_double(Datadog::AppSec::SecurityEngine::Engine, new_runner: waf_runner) } + let(:waf_runner) { instance_double(Datadog::AppSec::SecurityEngine::Runner, ruleset_version: nil) } + + it 'returns a Lambda-shaped blocking response' do + expect(on_finish).to include( + 'statusCode' => 403, + 'headers' => {'Content-Type' => 'application/json'} + ) + end + + it 'still records events and deactivates' do + on_finish + + aggregate_failures('cleanup after interrupt') do + expect(Datadog::AppSec::Event).to have_received(:record) + expect(appsec_context).to have_received(:export_metrics) + expect(Datadog::AppSec::Context).to have_received(:deactivate) + end + end + end + + context 'when an error occurs' do + before { allow(gateway).to receive(:push).and_raise(StandardError, 'boom') } + + it 'still deactivates the context' do + on_finish + + expect(Datadog::AppSec::Context).to have_received(:deactivate) + end + end + end + end +end diff --git a/test/datadog/lambda/appsec/event_normalizer.spec.rb b/test/datadog/lambda/appsec/event_normalizer.spec.rb new file mode 100644 index 0000000..2a421b0 --- /dev/null +++ b/test/datadog/lambda/appsec/event_normalizer.spec.rb @@ -0,0 +1,120 @@ +# frozen_string_literal: true + +require 'datadog/lambda/appsec/event_normalizer' + +RSpec.describe Datadog::Lambda::AppSec::EventNormalizer do + describe '.normalize' do + subject(:result) { described_class.normalize(event) } + + context 'when event is API Gateway v1' do + let(:event) do + { + 'httpMethod' => 'POST', + 'path' => '/users/123', + 'headers' => {'Host' => 'example.com', 'Cookie' => 'session=abc'}, + 'queryStringParameters' => {'page' => '1'}, + 'multiValueQueryStringParameters' => {'page' => ['1']}, + 'pathParameters' => {'id' => '123'}, + 'body' => '{"name":"john"}', + 'isBase64Encoded' => false, + 'requestContext' => {'identity' => {'sourceIp' => '10.0.0.1'}}, + } + end + + it { expect(result['method']).to eq('POST') } + it { expect(result['path']).to eq('/users/123') } + it { expect(result['headers']).to eq('Host' => 'example.com', 'Cookie' => 'session=abc') } + it { expect(result['query']).to eq('page' => ['1']) } + it { expect(result['source_ip']).to eq('10.0.0.1') } + it { expect(result['body']).to eq('{"name":"john"}') } + it { expect(result['base64_encoded']).to eq(false) } + it { expect(result['path_params']).to eq('id' => '123') } + it { expect(result).not_to have_key('cookies') } + it { expect(result).not_to have_key('query_string') } + end + + context 'when event is API Gateway v1 without multiValueQueryStringParameters' do + let(:event) do + { + 'httpMethod' => 'GET', + 'path' => '/health', + 'headers' => {}, + 'queryStringParameters' => {'page' => '1'}, + 'requestContext' => {'identity' => {}}, + } + end + + it { expect(result['query']).to eq('page' => '1') } + end + + context 'when event is API Gateway v2' do + let(:event) do + { + 'rawPath' => '/users/123', + 'rawQueryString' => 'page=1&sort=asc', + 'queryStringParameters' => {'page' => '1', 'sort' => 'asc'}, + 'headers' => {'host' => 'example.com'}, + 'cookies' => ['session=abc', 'theme=dark'], + 'pathParameters' => {'id' => '123'}, + 'body' => 'hello', + 'isBase64Encoded' => false, + 'requestContext' => {'http' => {'method' => 'GET', 'sourceIp' => '10.0.0.2'}}, + } + end + + it { expect(result['method']).to eq('GET') } + it { expect(result['path']).to eq('/users/123') } + it { expect(result['headers']).to eq('host' => 'example.com') } + it { expect(result['cookies']).to eq(['session=abc', 'theme=dark']) } + it { expect(result['query']).to eq('page' => '1', 'sort' => 'asc') } + it { expect(result['query_string']).to eq('page=1&sort=asc') } + it { expect(result['source_ip']).to eq('10.0.0.2') } + it { expect(result['body']).to eq('hello') } + it { expect(result['base64_encoded']).to eq(false) } + it { expect(result['path_params']).to eq('id' => '123') } + end + + context 'when v1 event has nil fields' do + let(:event) do + { + 'httpMethod' => 'GET', + 'path' => '/health', + 'headers' => nil, + 'queryStringParameters' => nil, + 'multiValueQueryStringParameters' => nil, + 'pathParameters' => nil, + 'body' => nil, + 'isBase64Encoded' => false, + 'requestContext' => {'identity' => {'sourceIp' => '127.0.0.1'}}, + } + end + + it { expect(result['method']).to eq('GET') } + it { expect(result['path']).to eq('/health') } + it { expect(result['source_ip']).to eq('127.0.0.1') } + it { expect(result).not_to have_key('headers') } + it { expect(result).not_to have_key('query') } + it { expect(result).not_to have_key('path_params') } + it { expect(result).not_to have_key('body') } + end + + context 'when v2 event has minimal fields' do + let(:event) do + { + 'rawPath' => '/health', + 'headers' => {}, + 'requestContext' => {'http' => {'method' => 'GET', 'sourceIp' => '127.0.0.1'}}, + } + end + + it { expect(result['method']).to eq('GET') } + it { expect(result['path']).to eq('/health') } + it { expect(result['source_ip']).to eq('127.0.0.1') } + it { expect(result).not_to have_key('cookies') } + it { expect(result).not_to have_key('query') } + it { expect(result).not_to have_key('query_string') } + it { expect(result).not_to have_key('body') } + it { expect(result).not_to have_key('path_params') } + end + end +end diff --git a/test/datadog/lambda/appsec/request.spec.rb b/test/datadog/lambda/appsec/request.spec.rb new file mode 100644 index 0000000..265f6e2 --- /dev/null +++ b/test/datadog/lambda/appsec/request.spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'datadog/lambda/appsec/request' + +RSpec.describe Datadog::Lambda::AppSec::Request do + subject(:request) { described_class.from_normalized(event) } + + let(:event) do + { + 'headers' => {'Host' => 'example.com', 'User-Agent' => 'TestBot/1.0', 'Accept' => 'text/html'}, + 'source_ip' => '10.0.0.1', + } + end + + describe '#headers' do + it 'lowercases header keys' do + expect(request.headers).to eq( + 'host' => 'example.com', + 'user-agent' => 'TestBot/1.0', + 'accept' => 'text/html' + ) + end + + context 'when event has no headers' do + let(:event) { {'source_ip' => '10.0.0.1'} } + + it { expect(request.headers).to eq({}) } + end + end + + describe '#host' do + it { expect(request.host).to eq('example.com') } + end + + describe '#user_agent' do + it { expect(request.user_agent).to eq('TestBot/1.0') } + end + + describe '#remote_addr' do + it { expect(request.remote_addr).to eq('10.0.0.1') } + + context 'when source_ip is absent' do + let(:event) { {'headers' => {}} } + + it { expect(request.remote_addr).to be_nil } + end + end +end