From a97f82584e9561941639a95590864c034fe0975d Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Wed, 27 Aug 2025 17:36:24 +0200 Subject: [PATCH 1/2] lambda: add the possibility for different kind of events in the proxy --- utils/_context/_scenarios/aws_lambda.py | 10 ++++ utils/_context/containers.py | 5 ++ utils/build/docker/lambda_proxy/main.py | 51 +++++++++++++++++-- .../python_lambda/apigw-http.Dockerfile | 21 ++++++++ .../python_lambda/apigw-rest.Dockerfile | 3 ++ .../docker/python_lambda/function/handler.py | 25 ++++++--- 6 files changed, 103 insertions(+), 12 deletions(-) create mode 100644 utils/build/docker/python_lambda/apigw-http.Dockerfile diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 0a1bc7da298..5c5916ebd7c 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -62,6 +62,16 @@ def __init__( def configure(self, config: pytest.Config): super().configure(config) + allowed_event_types = "apigateway-rest", "apigateway-http" + event_type = self.lambda_weblog.image.labels.get("lambda-proxy.event-type") + if event_type not in allowed_event_types: + pytest.exit( + "In lambda scenarios, the weblog image must contain the variable `LAMBDA_EVENT_TYPE`" + f" with a value in {allowed_event_types}", + ) + + self.lambda_proxy_container.environment.update({"LAMBDA_EVENT_TYPE": event_type}) + interfaces.agent.configure(self.host_log_folder, replay=self.replay) interfaces.library.configure(self.host_log_folder, replay=self.replay) interfaces.backend.configure(self.host_log_folder, replay=self.replay) diff --git a/utils/_context/containers.py b/utils/_context/containers.py index 3d7af7981fc..b7d0ce6f0fa 100644 --- a/utils/_context/containers.py +++ b/utils/_context/containers.py @@ -635,6 +635,11 @@ def __init__( }, ) + def post_start(self): + super().post_start() + + logger.stdout(f"Proxied event type: {self.environment.get("LAMBDA_EVENT_TYPE")}") + class AgentContainer(TestedContainer): apm_receiver_port: int = 8127 diff --git a/utils/build/docker/lambda_proxy/main.py b/utils/build/docker/lambda_proxy/main.py index 3351df3ba59..16241c7779d 100644 --- a/utils/build/docker/lambda_proxy/main.py +++ b/utils/build/docker/lambda_proxy/main.py @@ -1,24 +1,31 @@ +import logging import os from flask import Flask, request from requests import post -from samcli.local.apigw.event_constructor import construct_v1_event +from samcli.local.apigw.event_constructor import construct_v1_event, construct_v2_event_http from samcli.local.apigw.local_apigw_service import LocalApigwService +from samcli.local.apigw.local_apigw_service import PathConverter + +logger = logging.getLogger() PORT = 7777 +BINARY_TYPES = ["application/octet-stream"] RIE_HOST = os.environ.get("RIE_HOST", "lambda-weblog") RIE_PORT = os.environ.get("RIE_PORT", "8080") FUNCTION_NAME = os.environ.get("FUNCTION_NAME", "function") RIE_URL = f"http://{RIE_HOST}:{RIE_PORT}/2015-03-31/functions/{FUNCTION_NAME}/invocations" +LAMBDA_EVENT_TYPE: str | None = os.environ.get("LAMBDA_EVENT_TYPE") + app = Flask(__name__) app.config["PROVIDE_AUTOMATIC_OPTIONS"] = False -def invoke_lambda_function(): +def invoke_lambda_function_api_gateway_rest(): """ This function is used to invoke the Lambda function with the provided event. It constructs a v1 event from the Flask request and sends it to the RIE URL. @@ -26,7 +33,7 @@ def invoke_lambda_function(): converted_event = construct_v1_event( request, PORT, - binary_types=["application/octet-stream"], + binary_types=BINARY_TYPES, stage_name="Prod", ) @@ -38,7 +45,7 @@ def invoke_lambda_function(): (status_code, headers, body) = LocalApigwService._parse_v1_payload_format_lambda_output( response.content.decode("utf-8"), - binary_types=[], + binary_types=BINARY_TYPES, flask_request=request, event_type="Api", ) @@ -46,6 +53,40 @@ def invoke_lambda_function(): return app.response_class(response=body, status=status_code, headers=headers) +def invoke_lambda_function_api_gateway_http(): + """ + This function is used to invoke the Lambda function with the provided event. + It constructs a v2 event http from the Flask request and sends it to the RIE URL. + """ + + path = PathConverter.convert_path_to_api_gateway(request.path) + route_key = LocalApigwService._v2_route_key(request.method, path, is_default_route=False) + converted_event = construct_v2_event_http(request, PORT, binary_types=BINARY_TYPES, route_key=route_key) + + response = post( + RIE_URL, + json=converted_event, + headers={"Content-Type": "application/json"}, + ) + + (status_code, headers, body) = LocalApigwService._parse_v2_payload_format_lambda_output( + response.content.decode("utf-8"), binary_types=BINARY_TYPES, flask_request=request + ) + + return app.response_class(response=body, status=status_code, headers=headers) + + +match LAMBDA_EVENT_TYPE: + case "apigateway-rest": + lambda_invoker = invoke_lambda_function_api_gateway_rest + case "apigateway-http": + lambda_invoker = invoke_lambda_function_api_gateway_http + case _: + logger.error( + f"Unsupported Lambda event type: {LAMBDA_EVENT_TYPE}", + ) + exit(1) + ROUTES = [ ("/", ["GET", "POST", "OPTIONS"]), ("/finger_print", ["GET"]), @@ -66,6 +107,6 @@ def invoke_lambda_function(): app.add_url_rule( endpoint, endpoint, - lambda **kwargs: invoke_lambda_function(), + lambda **kwargs: lambda_invoker(), methods=methods, ) diff --git a/utils/build/docker/python_lambda/apigw-http.Dockerfile b/utils/build/docker/python_lambda/apigw-http.Dockerfile new file mode 100644 index 00000000000..49c50f485d4 --- /dev/null +++ b/utils/build/docker/python_lambda/apigw-http.Dockerfile @@ -0,0 +1,21 @@ +FROM public.ecr.aws/lambda/python:3.13 + +RUN dnf install -y unzip findutils socat + +# Add the Datadog Extension +RUN mkdir -p /opt/extensions +COPY --from=public.ecr.aws/datadog/lambda-extension:latest /opt/. /opt/ + +COPY utils/build/docker/python_lambda/install_datadog_lambda.sh binaries* /binaries/ +RUN /binaries/install_datadog_lambda.sh + +# Setup the aws_lambda handler +COPY utils/build/docker/python_lambda/function/. ${LAMBDA_TASK_ROOT} +RUN pip install -r ${LAMBDA_TASK_ROOT}/requirements.txt + +ENV DD_LAMBDA_HANDLER=handler.lambda_handler +ENV LAMBDA_EVENT_TYPE=apigateway-http + +LABEL lambda-proxy.event-type=apigateway-http + +ENTRYPOINT ["/bin/sh"] diff --git a/utils/build/docker/python_lambda/apigw-rest.Dockerfile b/utils/build/docker/python_lambda/apigw-rest.Dockerfile index a06e37ac51e..b7422214d0f 100644 --- a/utils/build/docker/python_lambda/apigw-rest.Dockerfile +++ b/utils/build/docker/python_lambda/apigw-rest.Dockerfile @@ -14,5 +14,8 @@ COPY utils/build/docker/python_lambda/function/. ${LAMBDA_TASK_ROOT} RUN pip install -r ${LAMBDA_TASK_ROOT}/requirements.txt ENV DD_LAMBDA_HANDLER=handler.lambda_handler +ENV LAMBDA_EVENT_TYPE=apigateway-rest + +LABEL lambda-proxy.event-type=apigateway-rest ENTRYPOINT ["/bin/sh"] diff --git a/utils/build/docker/python_lambda/function/handler.py b/utils/build/docker/python_lambda/function/handler.py index b6774f6a0fd..ffbaeaa9ddd 100644 --- a/utils/build/docker/python_lambda/function/handler.py +++ b/utils/build/docker/python_lambda/function/handler.py @@ -1,24 +1,33 @@ import logging +import os import urllib import urllib.parse from typing import Any -from aws_lambda_powertools.event_handler import APIGatewayRestResolver +from aws_lambda_powertools.event_handler import APIGatewayRestResolver, APIGatewayHttpResolver from aws_lambda_powertools.utilities.typing.lambda_context import LambdaContext from aws_lambda_powertools.shared.cookies import Cookie from aws_lambda_powertools.event_handler import Response import datadog_lambda from ddtrace.appsec import trace_utils as appsec_trace_utils -from ddtrace.contrib.trace_utils import set_user +from ddtrace.contrib.internal.trace_utils_base import set_user from ddtrace.trace import tracer logger = logging.getLogger(__name__) -app = APIGatewayRestResolver() +LAMBDA_EVENT_TYPE = os.environ.get("LAMBDA_EVENT_TYPE", "apigateway-rest") +if LAMBDA_EVENT_TYPE == "apigateway-rest": + app = APIGatewayRestResolver() +elif LAMBDA_EVENT_TYPE == "apigateway-http": + app = APIGatewayHttpResolver() +else: + logger.error( + f"Unsupported Lambda event type: {LAMBDA_EVENT_TYPE}", + ) _TRACK_CUSTOM_APPSEC_EVENT_NAME = "system_tests_appsec_event" @@ -57,6 +66,8 @@ def healthcheck_route(): @app.get("/params/") @app.post("/params/") @app.route("/params/", method="OPTIONS") +@app.get("/waf") +@app.post("/waf") @app.get("/waf/") @app.post("/waf/") @app.get("/waf/") @@ -86,7 +97,7 @@ def session_new(): @app.get("/tag_value//") @app.route("/tag_value//", method="OPTIONS") def tag_value(tag_value: str, status_code: int): - appsec_trace_utils.track_custom_event( + appsec_trace_utils.track_custom_event( # pyright: ignore[reportPrivateImportUsage] tracer, event_name=_TRACK_CUSTOM_APPSEC_EVENT_NAME, metadata={"value": tag_value} ) return Response( @@ -108,7 +119,7 @@ def tag_value(tag_value: str, status_code: int): @app.get("/user_login_success_event") def track_user_login_success_event(): - appsec_trace_utils.track_user_login_success_event( + appsec_trace_utils.track_user_login_success_event( # pyright: ignore[reportPrivateImportUsage] tracer, user_id=_TRACK_USER, login=_TRACK_USER, metadata=_TRACK_METADATA ) return Response( @@ -120,7 +131,7 @@ def track_user_login_success_event(): @app.post("/tag_value//") def tag_value_post(tag_value: str, status_code: int): - appsec_trace_utils.track_custom_event( + appsec_trace_utils.track_custom_event( # pyright: ignore[reportPrivateImportUsage] tracer, event_name=_TRACK_CUSTOM_APPSEC_EVENT_NAME, metadata={"value": tag_value} ) if tag_value.startswith("payload_in_response_body"): @@ -149,7 +160,7 @@ def tag_value_post(tag_value: str, status_code: int): @app.get("/users") def users(): - user = app.current_event.query_string_parameters.get("user") + user = app.current_event.query_string_parameters.get("user", "") set_user( tracer, user_id=user, From fd8e21741258758ef5a3f6a147b7d565ccd916a4 Mon Sep 17 00:00:00 2001 From: Florentin Labelle Date: Fri, 29 Aug 2025 16:16:23 +0200 Subject: [PATCH 2/2] prefix env and label with distinctive system-tests marker --- utils/_context/_scenarios/aws_lambda.py | 2 +- utils/build/docker/python_lambda/apigw-http.Dockerfile | 4 ++-- utils/build/docker/python_lambda/apigw-rest.Dockerfile | 4 ++-- utils/build/docker/python_lambda/function/handler.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/utils/_context/_scenarios/aws_lambda.py b/utils/_context/_scenarios/aws_lambda.py index 5c5916ebd7c..4d6a70f369e 100644 --- a/utils/_context/_scenarios/aws_lambda.py +++ b/utils/_context/_scenarios/aws_lambda.py @@ -63,7 +63,7 @@ def configure(self, config: pytest.Config): super().configure(config) allowed_event_types = "apigateway-rest", "apigateway-http" - event_type = self.lambda_weblog.image.labels.get("lambda-proxy.event-type") + event_type = self.lambda_weblog.image.labels.get("system-tests.lambda-proxy.event-type") if event_type not in allowed_event_types: pytest.exit( "In lambda scenarios, the weblog image must contain the variable `LAMBDA_EVENT_TYPE`" diff --git a/utils/build/docker/python_lambda/apigw-http.Dockerfile b/utils/build/docker/python_lambda/apigw-http.Dockerfile index 49c50f485d4..1126ee6e7e0 100644 --- a/utils/build/docker/python_lambda/apigw-http.Dockerfile +++ b/utils/build/docker/python_lambda/apigw-http.Dockerfile @@ -14,8 +14,8 @@ COPY utils/build/docker/python_lambda/function/. ${LAMBDA_TASK_ROOT} RUN pip install -r ${LAMBDA_TASK_ROOT}/requirements.txt ENV DD_LAMBDA_HANDLER=handler.lambda_handler -ENV LAMBDA_EVENT_TYPE=apigateway-http +ENV SYSTEM_TEST_WEBLOG_LAMBDA_EVENT_TYPE=apigateway-http -LABEL lambda-proxy.event-type=apigateway-http +LABEL system-tests.lambda-proxy.event-type=apigateway-http ENTRYPOINT ["/bin/sh"] diff --git a/utils/build/docker/python_lambda/apigw-rest.Dockerfile b/utils/build/docker/python_lambda/apigw-rest.Dockerfile index b7422214d0f..fc0ea27dc92 100644 --- a/utils/build/docker/python_lambda/apigw-rest.Dockerfile +++ b/utils/build/docker/python_lambda/apigw-rest.Dockerfile @@ -14,8 +14,8 @@ COPY utils/build/docker/python_lambda/function/. ${LAMBDA_TASK_ROOT} RUN pip install -r ${LAMBDA_TASK_ROOT}/requirements.txt ENV DD_LAMBDA_HANDLER=handler.lambda_handler -ENV LAMBDA_EVENT_TYPE=apigateway-rest +ENV SYSTEM_TEST_WEBLOG_LAMBDA_EVENT_TYPE=apigateway-rest -LABEL lambda-proxy.event-type=apigateway-rest +LABEL system-tests.lambda-proxy.event-type=apigateway-rest ENTRYPOINT ["/bin/sh"] diff --git a/utils/build/docker/python_lambda/function/handler.py b/utils/build/docker/python_lambda/function/handler.py index ffbaeaa9ddd..48aac5338ca 100644 --- a/utils/build/docker/python_lambda/function/handler.py +++ b/utils/build/docker/python_lambda/function/handler.py @@ -19,7 +19,7 @@ logger = logging.getLogger(__name__) -LAMBDA_EVENT_TYPE = os.environ.get("LAMBDA_EVENT_TYPE", "apigateway-rest") +LAMBDA_EVENT_TYPE = os.environ.get("SYSTEM_TEST_WEBLOG_LAMBDA_EVENT_TYPE", "apigateway-rest") if LAMBDA_EVENT_TYPE == "apigateway-rest": app = APIGatewayRestResolver() elif LAMBDA_EVENT_TYPE == "apigateway-http":