Skip to content

Commit 5476864

Browse files
Merge branch 'main' into michael.zhao/dsm-ckpt-all-records
2 parents c4526f7 + f1a4cb4 commit 5476864

24 files changed

+1050
-1050
lines changed

Dockerfile

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ RUN rm -rf ./python/lib/$runtime/site-packages/setuptools
2323
RUN rm -rf ./python/lib/$runtime/site-packages/jsonschema/tests
2424
RUN rm -f ./python/lib/$runtime/site-packages/ddtrace/appsec/_iast/_taint_tracking/*.so
2525
RUN rm -f ./python/lib/$runtime/site-packages/ddtrace/appsec/_iast/_stacktrace*.so
26-
RUN rm -f ./python/lib/$runtime/site-packages/ddtrace/internal/datadog/profiling/libdd_wrapper*.so
27-
RUN rm -f ./python/lib/$runtime/site-packages/ddtrace/internal/datadog/profiling/ddup/_ddup.*.so
2826
# _stack_v2 may not exist for some versions of ddtrace (e.g. under python 3.13)
2927
RUN rm -f ./python/lib/$runtime/site-packages/ddtrace/internal/datadog/profiling/stack_v2/_stack_v2.*.so
3028
# remove *.dist-info directories except any entry_points.txt files

datadog_lambda/asm.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
from copy import deepcopy
21
import logging
2+
import urllib.parse
3+
from copy import deepcopy
34
from typing import Any, Dict, List, Optional, Union
45

56
from ddtrace.contrib.internal.trace_utils import _get_request_header_client_ip
67
from ddtrace.internal import core
8+
from ddtrace.internal.utils import get_blocked
9+
from ddtrace.internal.utils import http as http_utils
710
from ddtrace.trace import Span
811

912
from datadog_lambda.trigger import (
@@ -50,6 +53,7 @@ def asm_set_context(event_source: _EventSource):
5053
This allows the AppSecSpanProcessor to know information about the event
5154
at the moment the span is created and skip it when not relevant.
5255
"""
56+
5357
if event_source.event_type not in _http_event_types:
5458
core.set_item("appsec_skip_next_lambda_event", True)
5559

@@ -126,6 +130,14 @@ def asm_start_request(
126130
span.set_tag_str("http.client_ip", request_ip)
127131
span.set_tag_str("network.client.ip", request_ip)
128132

133+
# Encode the parsed query and append it to reconstruct the original raw URI expected by AppSec.
134+
if parsed_query:
135+
try:
136+
encoded_query = urllib.parse.urlencode(parsed_query, doseq=True)
137+
raw_uri += "?" + encoded_query # type: ignore
138+
except Exception:
139+
pass
140+
129141
core.dispatch(
130142
# The matching listener is registered in ddtrace.appsec._handlers
131143
"aws_lambda.start_request",
@@ -182,3 +194,47 @@ def asm_start_response(
182194
response_headers,
183195
),
184196
)
197+
198+
if isinstance(response, dict) and "statusCode" in response:
199+
body = response.get("body")
200+
else:
201+
body = response
202+
203+
core.dispatch(
204+
# The matching listener is registered in ddtrace.appsec._handlers
205+
"aws_lambda.parse_body",
206+
(body,),
207+
)
208+
209+
210+
def get_asm_blocked_response(
211+
event_source: _EventSource,
212+
) -> Optional[Dict[str, Any]]:
213+
"""Get the blocked response for the given event source."""
214+
if event_source.event_type not in _http_event_types:
215+
return None
216+
217+
blocked = get_blocked()
218+
if not blocked:
219+
return None
220+
221+
desired_type = blocked.get("type", "auto")
222+
if desired_type == "none":
223+
content_type = "text/plain; charset=utf-8"
224+
content = ""
225+
else:
226+
content_type = blocked.get("content-type", "application/json")
227+
content = http_utils._get_blocked_template(content_type)
228+
229+
response_headers = {
230+
"content-type": content_type,
231+
}
232+
if "location" in blocked:
233+
response_headers["location"] = blocked["location"]
234+
235+
return {
236+
"statusCode": blocked.get("status_code", 403),
237+
"headers": response_headers,
238+
"body": content,
239+
"isBase64Encoded": False,
240+
}

datadog_lambda/tracing.py

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Copyright 2019 Datadog, Inc.
55
import logging
66
import os
7+
import re
78
import traceback
89
import ujson as json
910
from datetime import datetime, timezone
@@ -828,15 +829,31 @@ def create_service_mapping(val):
828829
return new_service_mapping
829830

830831

831-
def determine_service_name(service_mapping, specific_key, generic_key, default_value):
832-
service_name = service_mapping.get(specific_key)
833-
if service_name is None:
834-
service_name = service_mapping.get(generic_key, default_value)
835-
return service_name
832+
def determine_service_name(
833+
service_mapping, specific_key, generic_key, extracted_key, fallback=None
834+
):
835+
# Check for mapped service (specific key first, then generic key)
836+
mapped_service = service_mapping.get(specific_key) or service_mapping.get(
837+
generic_key
838+
)
839+
if mapped_service:
840+
return mapped_service
841+
842+
# Check if AWS service representation is disabled
843+
aws_service_representation = os.environ.get(
844+
"DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", ""
845+
).lower()
846+
if aws_service_representation in ("false", "0"):
847+
return fallback
848+
849+
# Use extracted_key if it exists and is not empty, otherwise use fallback
850+
return (
851+
extracted_key.strip() if extracted_key and extracted_key.strip() else fallback
852+
)
836853

837854

838855
# Initialization code
839-
service_mapping_str = os.getenv("DD_SERVICE_MAPPING", "")
856+
service_mapping_str = os.environ.get("DD_SERVICE_MAPPING", "")
840857
service_mapping = create_service_mapping(service_mapping_str)
841858

842859
_dd_origin = {"_dd.origin": "lambda"}
@@ -960,6 +977,7 @@ def create_inferred_span_from_api_gateway_websocket_event(
960977
"http.url": http_url,
961978
"endpoint": endpoint,
962979
"resource_names": endpoint,
980+
"span.kind": "server",
963981
"apiid": api_id,
964982
"apiname": api_id,
965983
"stage": request_context.get("stage"),
@@ -1018,6 +1036,7 @@ def create_inferred_span_from_api_gateway_event(
10181036
"endpoint": path,
10191037
"http.method": method,
10201038
"resource_names": resource,
1039+
"span.kind": "server",
10211040
"apiid": api_id,
10221041
"apiname": api_id,
10231042
"stage": request_context.get("stage"),
@@ -1122,12 +1141,13 @@ def create_inferred_span_from_sqs_event(event, context):
11221141
event_source_arn = event_record.get("eventSourceARN")
11231142
queue_name = event_source_arn.split(":")[-1]
11241143
service_name = determine_service_name(
1125-
service_mapping, queue_name, "lambda_sqs", "sqs"
1144+
service_mapping, queue_name, "lambda_sqs", queue_name, "sqs"
11261145
)
11271146
attrs = event_record.get("attributes") or {}
11281147
tags = {
11291148
"operation_name": "aws.sqs",
11301149
"resource_names": queue_name,
1150+
"span.kind": "server",
11311151
"queuename": queue_name,
11321152
"event_source_arn": event_source_arn,
11331153
"receipt_handle": event_record.get("receiptHandle"),
@@ -1189,11 +1209,12 @@ def create_inferred_span_from_sns_event(event, context):
11891209
topic_arn = sns_message.get("TopicArn")
11901210
topic_name = topic_arn.split(":")[-1]
11911211
service_name = determine_service_name(
1192-
service_mapping, topic_name, "lambda_sns", "sns"
1212+
service_mapping, topic_name, "lambda_sns", topic_name, "sns"
11931213
)
11941214
tags = {
11951215
"operation_name": "aws.sns",
11961216
"resource_names": topic_name,
1217+
"span.kind": "server",
11971218
"topicname": topic_name,
11981219
"topic_arn": topic_arn,
11991220
"message_id": sns_message.get("MessageId"),
@@ -1224,15 +1245,16 @@ def create_inferred_span_from_kinesis_event(event, context):
12241245
event_record = get_first_record(event)
12251246
event_source_arn = event_record.get("eventSourceARN")
12261247
event_id = event_record.get("eventID")
1227-
stream_name = event_source_arn.split(":")[-1]
1248+
stream_name = re.sub(r"^stream/", "", (event_source_arn or "").split(":")[-1])
12281249
shard_id = event_id.split(":")[0]
12291250
service_name = determine_service_name(
1230-
service_mapping, stream_name, "lambda_kinesis", "kinesis"
1251+
service_mapping, stream_name, "lambda_kinesis", stream_name, "kinesis"
12311252
)
12321253
kinesis = event_record.get("kinesis") or {}
12331254
tags = {
12341255
"operation_name": "aws.kinesis",
12351256
"resource_names": stream_name,
1257+
"span.kind": "server",
12361258
"streamname": stream_name,
12371259
"shardid": shard_id,
12381260
"event_source_arn": event_source_arn,
@@ -1259,12 +1281,13 @@ def create_inferred_span_from_dynamodb_event(event, context):
12591281
event_source_arn = event_record.get("eventSourceARN")
12601282
table_name = event_source_arn.split("/")[1]
12611283
service_name = determine_service_name(
1262-
service_mapping, table_name, "lambda_dynamodb", "dynamodb"
1284+
service_mapping, table_name, "lambda_dynamodb", table_name, "dynamodb"
12631285
)
12641286
dynamodb_message = event_record.get("dynamodb") or {}
12651287
tags = {
12661288
"operation_name": "aws.dynamodb",
12671289
"resource_names": table_name,
1290+
"span.kind": "server",
12681291
"tablename": table_name,
12691292
"event_source_arn": event_source_arn,
12701293
"event_id": event_record.get("eventID"),
@@ -1293,11 +1316,12 @@ def create_inferred_span_from_s3_event(event, context):
12931316
obj = s3.get("object") or {}
12941317
bucket_name = bucket.get("name")
12951318
service_name = determine_service_name(
1296-
service_mapping, bucket_name, "lambda_s3", "s3"
1319+
service_mapping, bucket_name, "lambda_s3", bucket_name, "s3"
12971320
)
12981321
tags = {
12991322
"operation_name": "aws.s3",
13001323
"resource_names": bucket_name,
1324+
"span.kind": "server",
13011325
"event_name": event_record.get("eventName"),
13021326
"bucketname": bucket_name,
13031327
"bucket_arn": bucket.get("arn"),
@@ -1323,11 +1347,12 @@ def create_inferred_span_from_s3_event(event, context):
13231347
def create_inferred_span_from_eventbridge_event(event, context):
13241348
source = event.get("source")
13251349
service_name = determine_service_name(
1326-
service_mapping, source, "lambda_eventbridge", "eventbridge"
1350+
service_mapping, source, "lambda_eventbridge", source, "eventbridge"
13271351
)
13281352
tags = {
13291353
"operation_name": "aws.eventbridge",
13301354
"resource_names": source,
1355+
"span.kind": "server",
13311356
"detail_type": event.get("detail-type"),
13321357
}
13331358
InferredSpanInfo.set_tags(
@@ -1401,9 +1426,21 @@ def create_function_execution_span(
14011426
tags["_dd.parent_source"] = trace_context_source
14021427
tags.update(trigger_tags)
14031428
tracer.set_tags(_dd_origin)
1429+
# Determine service name based on config and env var
1430+
if config.service:
1431+
service_name = config.service
1432+
else:
1433+
aws_service_representation = os.environ.get(
1434+
"DD_TRACE_AWS_SERVICE_REPRESENTATION_ENABLED", ""
1435+
).lower()
1436+
if aws_service_representation in ("false", "0"):
1437+
service_name = "aws.lambda"
1438+
else:
1439+
service_name = function_name if function_name else "aws.lambda"
1440+
14041441
span = tracer.trace(
14051442
"aws.lambda",
1406-
service="aws.lambda",
1443+
service=service_name,
14071444
resource=function_name,
14081445
span_type="serverless",
14091446
)

datadog_lambda/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "7.112.0"
1+
__version__ = "8.114.0.dev0"

datadog_lambda/wrapper.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from datadog_lambda.asm import asm_set_context, asm_start_response, asm_start_request
1313
from datadog_lambda.dsm import set_dsm_context
14+
from ddtrace.internal._exceptions import BlockingException
1415
from datadog_lambda.extension import should_use_extension, flush_extension
1516
from datadog_lambda.cold_start import (
1617
set_cold_start,
@@ -47,6 +48,14 @@
4748
extract_http_status_code_tag,
4849
)
4950

51+
if config.appsec_enabled:
52+
from datadog_lambda.asm import (
53+
asm_set_context,
54+
asm_start_response,
55+
asm_start_request,
56+
get_asm_blocked_response,
57+
)
58+
5059
if config.profiling_enabled:
5160
from ddtrace.profiling import profiler
5261

@@ -121,6 +130,7 @@ def __init__(self, func):
121130
self.span = None
122131
self.inferred_span = None
123132
self.response = None
133+
self.blocking_response = None
124134

125135
if config.profiling_enabled:
126136
self.prof = profiler.Profiler(env=config.env, service=config.service)
@@ -160,8 +170,12 @@ def __call__(self, event, context, **kwargs):
160170
"""Executes when the wrapped function gets called"""
161171
self._before(event, context)
162172
try:
173+
if self.blocking_response:
174+
return self.blocking_response
163175
self.response = self.func(event, context, **kwargs)
164176
return self.response
177+
except BlockingException:
178+
self.blocking_response = get_asm_blocked_response(self.event_source)
165179
except Exception:
166180
from datadog_lambda.metric import submit_errors_metric
167181

@@ -172,6 +186,8 @@ def __call__(self, event, context, **kwargs):
172186
raise
173187
finally:
174188
self._after(event, context)
189+
if self.blocking_response:
190+
return self.blocking_response
175191

176192
def _inject_authorizer_span_headers(self, request_id):
177193
reference_span = self.inferred_span if self.inferred_span else self.span
@@ -204,6 +220,7 @@ def _inject_authorizer_span_headers(self, request_id):
204220
def _before(self, event, context):
205221
try:
206222
self.response = None
223+
self.blocking_response = None
207224
set_cold_start(init_timestamp_ns)
208225

209226
if not should_use_extension:
@@ -257,6 +274,7 @@ def _before(self, event, context):
257274
)
258275
if config.appsec_enabled:
259276
asm_start_request(self.span, event, event_source, self.trigger_tags)
277+
self.blocking_response = get_asm_blocked_response(self.event_source)
260278
else:
261279
set_correlation_ids()
262280
if config.profiling_enabled and is_new_sandbox():
@@ -290,20 +308,24 @@ def _after(self, event, context):
290308
if status_code:
291309
self.span.set_tag("http.status_code", status_code)
292310

293-
if config.appsec_enabled:
311+
if config.appsec_enabled and not self.blocking_response:
294312
asm_start_response(
295313
self.span,
296314
status_code,
297315
self.event_source,
298316
response=self.response,
299317
)
318+
self.blocking_response = get_asm_blocked_response(self.event_source)
300319

301320
self.span.finish()
302321

303322
if self.inferred_span:
304323
if status_code:
305324
self.inferred_span.set_tag("http.status_code", status_code)
306325

326+
if self.trigger_tags and (route := self.trigger_tags.get("http.route")):
327+
self.inferred_span.set_tag("http.route", route)
328+
307329
if config.service:
308330
self.inferred_span.set_tag("peer.service", config.service)
309331

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "datadog_lambda"
3-
version = "7.112.0"
3+
version = "8.114.0.dev0"
44
description = "The Datadog AWS Lambda Library"
55
authors = ["Datadog, Inc. <dev@datadoghq.com>"]
66
license = "Apache-2.0"
@@ -28,7 +28,7 @@ classifiers = [
2828
python = ">=3.8.0,<4"
2929
datadog = ">=0.51.0,<1.0.0"
3030
wrapt = "^1.11.2"
31-
ddtrace = ">=3.10.2,<4"
31+
ddtrace = ">=3.11.0,<4"
3232
ujson = ">=5.9.0"
3333
botocore = { version = "^1.34.0", optional = true }
3434
requests = { version ="^2.22.0", optional = true }

scripts/check_layer_size.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
# Compares layer size to threshold, and fails if below that threshold
99

1010
set -e
11-
MAX_LAYER_COMPRESSED_SIZE_KB=$(expr 6 \* 1024)
12-
MAX_LAYER_UNCOMPRESSED_SIZE_KB=$(expr 15 \* 1024)
11+
MAX_LAYER_COMPRESSED_SIZE_KB=$(expr 8 \* 1024)
12+
MAX_LAYER_UNCOMPRESSED_SIZE_KB=$(expr 21 \* 1024)
1313

1414

1515
LAYER_FILES_PREFIX="datadog_lambda_py"

0 commit comments

Comments
 (0)