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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions utils/_context/_scenarios/aws_lambda.py
Original file line number Diff line number Diff line change
Expand Up @@ -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("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`"
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)
Expand Down
5 changes: 5 additions & 0 deletions utils/_context/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 46 additions & 5 deletions utils/build/docker/lambda_proxy/main.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
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.
"""
converted_event = construct_v1_event(
request,
PORT,
binary_types=["application/octet-stream"],
binary_types=BINARY_TYPES,
stage_name="Prod",
)

Expand All @@ -38,14 +45,48 @@ 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",
)

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"]),
Expand All @@ -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,
)
21 changes: 21 additions & 0 deletions utils/build/docker/python_lambda/apigw-http.Dockerfile
Original file line number Diff line number Diff line change
@@ -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 SYSTEM_TEST_WEBLOG_LAMBDA_EVENT_TYPE=apigateway-http

LABEL system-tests.lambda-proxy.event-type=apigateway-http

ENTRYPOINT ["/bin/sh"]
3 changes: 3 additions & 0 deletions utils/build/docker/python_lambda/apigw-rest.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 SYSTEM_TEST_WEBLOG_LAMBDA_EVENT_TYPE=apigateway-rest

LABEL system-tests.lambda-proxy.event-type=apigateway-rest

ENTRYPOINT ["/bin/sh"]
25 changes: 18 additions & 7 deletions utils/build/docker/python_lambda/function/handler.py
Original file line number Diff line number Diff line change
@@ -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("SYSTEM_TEST_WEBLOG_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"

Expand Down Expand Up @@ -57,6 +66,8 @@ def healthcheck_route():
@app.get("/params/<path>")
@app.post("/params/<path>")
@app.route("/params/<path>", method="OPTIONS")
@app.get("/waf")
@app.post("/waf")
@app.get("/waf/")
@app.post("/waf/")
@app.get("/waf/<path>")
Expand Down Expand Up @@ -86,7 +97,7 @@ def session_new():
@app.get("/tag_value/<tag_value>/<status_code>")
@app.route("/tag_value/<tag_value>/<status_code>", 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(
Expand All @@ -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(
Expand All @@ -120,7 +131,7 @@ def track_user_login_success_event():

@app.post("/tag_value/<tag_value>/<status_code>")
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"):
Expand Down Expand Up @@ -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,
Expand Down