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
4 changes: 4 additions & 0 deletions aws_quickstart/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 4.20.0 (August 27, 2026)

- Add AWS Lambda as a managed instrumentation resource type and forward Lambda lifecycle, configuration, and tag changes for event-driven reconciliation.

# 4.19.1 (August 25, 2026)

- Fix Datadog Operator Marketplace agreement discovery and acceptance for Lambda runtimes that lack required agreement operations or paginators.
Expand Down
14 changes: 12 additions & 2 deletions aws_quickstart/attach_integration_permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,18 @@ def _role_arn_patterns_overlap(left, right):


def _validate_permissions_boundary_policy_documents(boundary_documents, account_id, partition):
if not isinstance(boundary_documents, list) or not boundary_documents:
raise DatadogAPIError("Datadog API returned no permissions boundary policy documents")
# An absent field means a response predating the boundary contract, so it stays fatal.
# An explicit empty list is a valid answer: resource types with no helper role, such as
# aws:lambda:function, need no boundary, and any boundary left from a previous selection
# is then correctly cleaned up.
if boundary_documents is None:
raise DatadogAPIError(
"Datadog API response omitted permissions_boundary_policy_documents"
)
if not isinstance(boundary_documents, list):
raise DatadogAPIError("Datadog API returned invalid permissions boundary policy documents")
if not boundary_documents:
return []
if len(boundary_documents) > MAX_BOUNDARY_POLICIES:
raise DatadogAPIError(
f"Datadog API returned {len(boundary_documents)} permissions boundaries; "
Expand Down
39 changes: 38 additions & 1 deletion aws_quickstart/attach_integration_permissions_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
sys.modules["cfnresponse"] = MagicMock()

from attach_integration_permissions import (
DatadogAPIError,
parse_resource_types,
build_instrumentation_permissions_url,
attach_instrumentation_permissions,
Expand Down Expand Up @@ -80,7 +81,7 @@ def test_release_embeds_tested_source(self):

def test_custom_resource_has_policy_attachment_schema_version(self):
template_path = Path(__file__).with_name("datadog_integration_permissions.yaml")
self.assertIn(' PolicyAttachmentSchemaVersion: "5"', template_path.read_text())
self.assertIn(' PolicyAttachmentSchemaVersion: "6"', template_path.read_text())

def test_execution_role_scopes_boundary_lifecycle_to_namespace(self):
template_path = Path(__file__).with_name("datadog_integration_permissions.yaml")
Expand Down Expand Up @@ -479,6 +480,28 @@ def test_missing_boundary_documents_preserves_existing_policies(self, mock_urlop
self.iam.create_policy.assert_not_called()
self.iam.detach_role_policy.assert_not_called()

@patch("attach_integration_permissions.urllib.request.urlopen")
def test_lambda_without_boundaries_attaches_instrumentation_policy(self, mock_urlopen):
document = {"Version": "2012-10-17", "Statement": []}
mock_urlopen.return_value = self._mock_response(
policy_documents=[document],
permissions_boundary_policy_documents=[],
)

self._attach(["aws:lambda:function"], fail_on_error=True)

self.assertEqual(self._created_policy_documents(), [document])
self.manage_boundaries.assert_called_once_with(
self.iam,
[],
self.owner_id,
)
self.cleanup_boundaries.assert_called_once_with(
self.iam,
self.owner_id,
retained_policy_arns=set(),
)

@patch("attach_integration_permissions._replace_instrumentation_policies")
@patch("attach_integration_permissions.urllib.request.urlopen")
def test_manages_boundaries_before_replacing_instrumentation_policies(
Expand Down Expand Up @@ -839,6 +862,20 @@ def test_validates_dynamic_boundary_contract(self):
sorted(BOUNDARY_POLICY_NAMES),
)

def test_rejects_omitted_boundary_documents(self):
with self.assertRaises(DatadogAPIError):
_validate_permissions_boundary_policy_documents(None, "123456789012", "aws")

def test_accepts_explicit_empty_boundary_documents(self):
self.assertEqual(
_validate_permissions_boundary_policy_documents(
[],
"123456789012",
"aws",
),
[],
)

def test_accepts_new_boundary_name_without_quickstart_change(self):
documents = permissions_boundary_documents(policy_names=("datadog-new-boundary",))

Expand Down
59 changes: 59 additions & 0 deletions aws_quickstart/cfn_common_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,64 @@ def test_shared_helper_composes_with_each_handler(self):
compile(source, filename, "exec")


class TestForwardingConditions(unittest.TestCase):
def test_parent_templates_gate_forwarding_on_supported_resource_types(self):
directory = Path(__file__).parent
condition = """ IncludeEC2:
Fn::Not:
- Fn::Equals:
- !Join
- ""
- !Split
- ",aws:ec2:instance,"
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
IncludeEKS:
Fn::Not:
- Fn::Equals:
- !Join
- ""
- !Split
- ",aws:eks:cluster,"
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
IncludeLambda:
Fn::Not:
- Fn::Equals:
- !Join
- ""
- !Split
- ",aws:lambda:function,"
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
ShouldForwardEvents:
Fn::Or:
- Condition: IncludeEC2
- Condition: IncludeEKS
- Condition: IncludeLambda
"""

for filename in (
"main_agent_installation.yaml",
"main_workflow.yaml",
"main_extended_workflow.yaml",
):
with self.subTest(filename=filename):
template = (directory / filename).read_text()
self.assertIn(condition, template)


if __name__ == "__main__":
unittest.main()
38 changes: 36 additions & 2 deletions aws_quickstart/datadog_agent_resource_update_forwarding.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Parameters:
Default: ""
Description: >-
Comma-separated list of AWS resource types (UDM form, e.g. aws:ec2:instance,
aws:eks:cluster) to forward CloudTrail events for. Only rules for the listed
types are deployed.
aws:eks:cluster, aws:lambda:function) to forward CloudTrail events for. Only
rules for the listed types are deployed.
Conditions:
IncludeEC2:
Fn::Not:
Expand All @@ -37,6 +37,11 @@ Conditions:
- Fn::Equals:
- !Join ["", !Split [",aws:eks:cluster,", !Sub ",${InstrumentationResourceTypes},"]]
- !Sub ",${InstrumentationResourceTypes},"
IncludeLAMBDA:
Fn::Not:
- Fn::Equals:
- !Join ["", !Split [",aws:lambda:function,", !Sub ",${InstrumentationResourceTypes},"]]
- !Sub ",${InstrumentationResourceTypes},"
Resources:
DDIntakeConnection:
Type: AWS::Events::Connection
Expand Down Expand Up @@ -165,3 +170,32 @@ Resources:
- Id: datadog-intake
Arn: !GetAtt DDIntakeApiDestination.Arn
RoleArn: !GetAtt DDEventBridgeInvocationRole.Arn
DDEventForwardingRuleLAMBDA:
Type: AWS::Events::Rule
Condition: IncludeLAMBDA
Properties:
Name: datadog-agent-resource-update-rule-lambda
Description: Forward LAMBDA CloudTrail events to the Datadog resource update intake
State: ENABLED
EventPattern:
source:
- aws.lambda
detail-type:
- "AWS API Call via CloudTrail"
detail:
errorCode:
- exists: false
$or:
- eventName:
- CreateFunction20150331
- UpdateFunctionConfiguration20150331v2
- eventName:
- TagResource20170331v2
- UntagResource20170331v2
requestParameters:
resource:
- wildcard: "*:function:*"
Targets:
- Id: datadog-intake
Arn: !GetAtt DDIntakeApiDestination.Arn
RoleArn: !GetAtt DDEventBridgeInvocationRole.Arn
10 changes: 5 additions & 5 deletions aws_quickstart/datadog_integration_permissions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ Parameters:
Default: ""
Description: >-
Comma-separated list of AWS resource types (UDM form, e.g. aws:ec2:instance, aws:ecs:cluster,
aws:eks:cluster) that the Datadog integration role should be granted the IAM permissions
required to instrument with the Datadog Agent. In the commercial AWS partition, selecting
EKS also accepts the free Datadog Operator AWS Marketplace agreement. Leave blank to skip.
aws:eks:cluster, aws:lambda:function) that the Datadog integration role should be granted the
IAM permissions required to instrument. In the commercial AWS partition, selecting EKS also
accepts the free Datadog Operator AWS Marketplace agreement. Leave blank to skip.
DatadogSite:
Type: String
Default: "datadoghq.com"
Expand Down Expand Up @@ -157,8 +157,8 @@ Resources:
Type: Custom::DatadogAttachIntegrationPermissionsFunctionTrigger
Properties:
ServiceToken: !GetAtt DatadogAttachIntegrationPermissionsFunction.Arn
# Bump this value when Lambda behavior changes so existing stacks invoke the custom resource.
PolicyAttachmentSchemaVersion: "5"
# Change only for migrations that must rerun policy attachment on existing stacks.
PolicyAttachmentSchemaVersion: "6"
DatadogIntegrationRole: !Ref IAMRoleName
AccountId: !Ref AWS::AccountId
Partition: !Sub "${AWS::Partition}"
Expand Down
4 changes: 2 additions & 2 deletions aws_quickstart/datadog_integration_role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Parameters:
Default: ""
Description: >-
Comma-separated list of AWS resource types (UDM form, e.g. aws:ec2:instance, aws:ecs:cluster,
aws:eks:cluster) that the Datadog integration role should be granted the IAM permissions
required to instrument with the Datadog Agent. Leave blank to skip.
aws:eks:cluster, aws:lambda:function) that the Datadog integration role should be granted the IAM permissions
required to instrument. Leave blank to skip.
DatadogSite:
Type: String
Default: "datadoghq.com"
Expand Down
64 changes: 47 additions & 17 deletions aws_quickstart/main_agent_installation.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
# version: <VERSION_PLACEHOLDER>
#
# Post-setup Agent installation add-on. Lets customers who declined the Agent installation option
# during initial AWS integration setup enable it later, against an existing integration role: it
# attaches the instrumentation IAM permissions and deploys the EventBridge forwarding pipeline,
# without touching the standard or resource-collection policies owned by the role stack.
#
AWSTemplateFormatVersion: 2010-09-09
Description: Datadog AWS Integration - Agent installation add-on
Description: Datadog AWS Integration - instrumentation add-on
Parameters:
APIKey:
Description: >-
Expand Down Expand Up @@ -35,21 +29,60 @@ Parameters:
InstrumentationResourceTypes:
Type: CommaDelimitedList
Description: >-
Comma-separated list of AWS resource types (UDM form, e.g. aws:ec2:instance, aws:eks:cluster) to enable
Datadog Agent installation for. The integration role is granted the IAM permissions required to instrument
these resources. CloudTrail update events are forwarded to Datadog for the supported resource types
(currently aws:ec2:instance and aws:eks:cluster); other types receive IAM permissions but no event forwarding.
Comma-separated list of AWS resource types (UDM form, e.g. aws:ec2:instance, aws:eks:cluster,
aws:lambda:function) to enable Datadog instrumentation for. The integration role is granted the IAM
permissions required to instrument these resources. CloudTrail update events are forwarded to Datadog
for supported resource types; other types receive IAM permissions but no event forwarding.
Rules:
ValidateAccountId:
Assertions:
- Assert: !Equals [!Ref AccountId, !Ref "AWS::AccountId"]
AssertDescription: "The AWS Account Id of the account integrated in Datadog does not match the AWS Account Id of the account where this stack will be created."
Conditions:
ShouldForwardEvents:
IncludeEC2:
Fn::Not:
- Fn::Equals:
- !Join
- ""
- !Split
- ",aws:ec2:instance,"
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
IncludeEKS:
Fn::Not:
- Fn::Equals:
- !Join ["", !Ref InstrumentationResourceTypes]
- ""
- !Join
- ""
- !Split
- ",aws:eks:cluster,"
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
IncludeLambda:
Fn::Not:
- Fn::Equals:
- !Join
- ""
- !Split
- ",aws:lambda:function,"
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
- !Sub
- ",${NormalizedResourceTypes},"
- NormalizedResourceTypes: !Join [",", !Ref InstrumentationResourceTypes]
ShouldForwardEvents:
Fn::Or:
- Condition: IncludeEC2
- Condition: IncludeEKS
- Condition: IncludeLambda
Resources:
# Attaches only the instrumentation IAM policies to the existing integration role. ManageBasePermissions
# is false so the standard and resource-collection policies owned by the role stack are left untouched.
Expand All @@ -64,9 +97,6 @@ Resources:
DatadogSite: !Ref DatadogSite
ManageBasePermissions: false
FailOnInstrumentationError: true
# EventBridge pipeline forwarding CloudTrail events to the Datadog resource update intake.
# Deployed only when at least one InstrumentationResourceTypes value is set; single-region
# (covers the region this stack is deployed in).
DatadogAgentResourceUpdateForwardingStack:
Type: AWS::CloudFormation::Stack
Condition: ShouldForwardEvents
Expand Down
6 changes: 3 additions & 3 deletions aws_quickstart/main_extended.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ Parameters:
InstrumentationResourceTypes:
Type: CommaDelimitedList
Description: >-
Comma-separated list of AWS resource types (UDM form, e.g. aws:ec2:instance, aws:ecs:cluster, aws:eks:cluster)
that the Datadog integration role should be granted the IAM permissions required to instrument with the Datadog
Agent. Leave blank to skip granting any extra instrumentation permissions.
Comma-separated list of AWS resource types (UDM form, e.g. aws:ec2:instance, aws:ecs:cluster,
aws:eks:cluster, aws:lambda:function) that the Datadog integration role should be granted the IAM permissions
required to instrument. Leave blank to skip granting any extra instrumentation permissions.
Default: ""
AgentlessVulnerabilityScanning:
Type: String
Expand Down
Loading
Loading