Skip to content
Open
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.14.0 (June 2, 2026)

- Add `datadog_agentless_saas.yaml` for SaaS-mode Agentless Scanning. The template provisions a single managed policy with the SaaS scanner permissions and attaches it to the local Datadog integration role — no scanner EC2/VPC/ASG resources and no delegate-role chaining. `SecurityAudit` is unconditionally attached to the integration role. `DatadogIntegrationRoleName` is required. Released alongside an entry added to `release.sh` so the new template participates in the standard placeholder substitution and upload pipeline.

# 4.13.0 (May 29, 2026)

- Add `uk1.datadoghq.com` site support. Affects `main_v2.yaml`, `main_workflow.yaml`, `main_extended.yaml`, and `main_extended_workflow.yaml`.
Expand Down
2 changes: 2 additions & 0 deletions aws_quickstart/datadog_agentless_api_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def call_datadog_agentless_api(context, event, method):
instance_role_arn = event["ResourceProperties"].get("InstanceRoleArn")
instance_profile_arn = event["ResourceProperties"].get("InstanceProfileArn")
scanner_policy_arn = event["ResourceProperties"].get("ScannerPolicyArn")
saas_scanning_policy_arn = event["ResourceProperties"].get("SaaSScanningPolicyArn")
orchestrator_policy_arn = event["ResourceProperties"].get("OrchestratorPolicyArn")
worker_policy_arn = event["ResourceProperties"].get("WorkerPolicyArn")
worker_dspm_policy_arn = event["ResourceProperties"].get("WorkerDSPMPolicyArn")
Expand Down Expand Up @@ -65,6 +66,7 @@ def call_datadog_agentless_api(context, event, method):
"instance_role_arn": instance_role_arn,
"instance_profile_arn": instance_profile_arn,
"scanner_policy_arn": scanner_policy_arn,
"saas_scanning_policy_arn": saas_scanning_policy_arn,
"orchestrator_policy_arn": orchestrator_policy_arn,
"worker_policy_arn": worker_policy_arn,
"worker_dspm_policy_arn": worker_dspm_policy_arn,
Expand Down
7 changes: 7 additions & 0 deletions aws_quickstart/datadog_agentless_api_call_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ def test_post_request_payload_structure(self, mock_is_enabled, mock_urlopen):
mock_is_enabled.return_value = False
mock_response = self.create_mock_response(200)
mock_urlopen.return_value = mock_response
self.base_event["ResourceProperties"]["SaaSScanningPolicyArn"] = (
"arn:aws:iam::123456789012:policy/datadog-agentless-saas-scanning"
)

call_datadog_agentless_api(self.context, self.base_event, "POST")

Expand All @@ -207,6 +210,10 @@ def test_post_request_payload_structure(self, mock_is_enabled, mock_urlopen):
self.assertIn("data", payload)
self.assertIn("type", payload["data"])
self.assertEqual(payload["data"]["type"], "aws_scan_options")
self.assertEqual(
payload["meta"]["resources"]["saas_scanning_policy_arn"],
"arn:aws:iam::123456789012:policy/datadog-agentless-saas-scanning",
)


class TestIsAgentlessScanningEnabled(unittest.TestCase):
Expand Down
293 changes: 293 additions & 0 deletions aws_quickstart/datadog_agentless_saas.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
# version: v<VERSION_PLACEHOLDER>
AWSTemplateFormatVersion: '2010-09-09'
Description: Attaches Datadog Agentless Scanning permissions for SaaS-mode deployments to the Datadog integration role.
Parameters:
AccountId:
Type: String
Description: The current AWS account ID. This parameter is for validation purposes only, and may be left empty.
AllowedPattern: "|[0-9]{12}"
Default: ""

DatadogAPIKey:
Type: String
AllowedPattern: "[0-9a-f]{32}"
Description: API key for the Datadog account
NoEcho: true

DatadogAPPKey:
Type: String
AllowedPattern: "([0-9a-f]{40})|(ddapp_[a-zA-Z0-9]{34})"
Description: Application key for the Datadog account
NoEcho: true

DatadogSite:
Type: String
Description: >-
The Datadog site to use for the Datadog Agentless Scanner.
Allowed values: datadoghq.com, datadoghq.eu, us3.datadoghq.com, us5.datadoghq.com,
ap1.datadoghq.com, ap2.datadoghq.com.
Default: datadoghq.com

AgentlessVulnerabilityScanning:
Type: String
AllowedValues:
- true
- false
Description: Enable Agentless Vulnerability Scanning (hosts, containers, and Lambda functions).
Default: false

AgentlessSensitiveDataScanning:
Type: String
AllowedValues:
- true
- false
Description: Enable Agentless Scanning of datastores (S3 buckets).
Default: false

AgentlessComplianceHostScanning:
Type: String
AllowedValues:
- true
- false
Description: Enable Agentless Compliance Scanning for hosts.
Default: false

DatadogIntegrationRoleName:
Type: String
Description: >-
The name of the IAM role used by the Datadog AWS integration. In SaaS mode, Datadog assumes this
role directly to perform agentless scans. The SecurityAudit policy is also attached to this role.
AllowedPattern: '[\w+=,.@-]{1,64}'

Rules:
MustMatchAccountId:
AssertDescription: 'Checking that AccountId matches the current AWS account ID'
Assertions:
- Assert: !Or [!Equals [!Ref AccountId, !Ref AWS::AccountId], !Equals [!Ref AccountId, ""]]
AssertDescription: >-
The current AWS account ID does not match the AWS account selected in Datadog.
Please log in to the AWS account where you want to set up Datadog Agentless Scanning and try again.

Resources:
DatadogAgentlessSaaSScanningPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
Description: Policy for Datadog Agentless Scanning in SaaS mode.
Roles:
- !Ref DatadogIntegrationRoleName
PolicyDocument:
Version: '2012-10-17'
Statement:
- Action: 'ec2:CreateTags'
Effect: Allow
Resource:
- 'arn:aws:ec2:*:*:volume/*'
- 'arn:aws:ec2:*:*:snapshot/*'
- 'arn:aws:ec2:*:*:image/*'
Condition:
StringEquals:
'ec2:CreateAction':
- CreateSnapshot
- CreateVolume
- CopySnapshot
- CopyImage
- Action: 'ec2:CreateSnapshot'
Effect: Allow
Resource: 'arn:aws:ec2:*:*:volume/*'
Condition:
StringNotEquals:
'aws:ResourceTag/DatadogAgentlessScanner': 'false'
- Action: 'ec2:CopySnapshot'
Effect: Allow
Resource: 'arn:aws:ec2:*:*:snapshot/snap-*'
- Action: 'ec2:CopySnapshot'
Effect: Allow
Resource: 'arn:aws:ec2:*:*:snapshot/${*}'
Condition:
'ForAllValues:StringLike':
'aws:TagKeys': DatadogAgentlessScanner*
StringEquals:
'aws:RequestTag/DatadogAgentlessScanner': 'true'
- Action: 'ec2:CreateSnapshot'
Effect: Allow
Resource: 'arn:aws:ec2:*:*:snapshot/*'
Condition:
'ForAllValues:StringLike':
'aws:TagKeys': DatadogAgentlessScanner*
StringEquals:
'aws:RequestTag/DatadogAgentlessScanner': 'true'
- Action: 'ec2:DeleteSnapshot'
Effect: Allow
Resource: 'arn:aws:ec2:*:*:snapshot/*'
Condition:
StringEquals:
'aws:ResourceTag/DatadogAgentlessScanner': 'true'
- Action: 'ec2:DescribeSnapshots'
Effect: Allow
Resource: '*'
- Action: 'kms:CreateGrant'
Effect: Allow
Resource: 'arn:aws:kms:*:*:key/*'
Condition:
'ForAnyValue:StringEquals':
'kms:EncryptionContextKeys': 'aws:ebs:id'
StringLike:
'kms:ViaService': 'ec2.*.amazonaws.com'
Bool:
'kms:GrantIsForAWSResource': true
- Action: 'kms:DescribeKey'
Effect: Allow
Resource: 'arn:aws:kms:*:*:key/*'
- Action: 'ec2:DeregisterImage'
Effect: Allow
Resource: 'arn:aws:ec2:*:*:image/*'
Condition:
StringEquals:
'aws:ResourceTag/DatadogAgentlessScanner': 'true'
- Action:
- 'ebs:ListSnapshotBlocks'
- 'ebs:ListChangedBlocks'
- 'ebs:GetSnapshotBlock'
Effect: Allow
Resource: 'arn:aws:ec2:*:*:snapshot/*'
Condition:
StringEquals:
'aws:ResourceTag/DatadogAgentlessScanner': 'true'
- Action: 'ec2:DescribeSnapshots'
Effect: Allow
Resource: '*'
- Action: 'ec2:DescribeVolumes'
Effect: Allow
Resource: '*'
- Action: 'kms:Decrypt'
Effect: Allow
Resource: 'arn:aws:kms:*:*:key/*'
Condition:
'ForAnyValue:StringEquals':
'kms:EncryptionContextKeys': 'aws:ebs:id'
StringLike:
'kms:ViaService': 'ec2.*.amazonaws.com'
- Action: 'kms:DescribeKey'
Effect: Allow
Resource: 'arn:aws:kms:*:*:key/*'
- Action: 'lambda:GetFunction'
Effect: Allow
Resource: 'arn:aws:lambda:*:*:function:*'
Condition:
StringNotEquals:
'aws:ResourceTag/DatadogAgentlessScanner': 'false'
- Action: 'lambda:GetLayerVersion'
Effect: Allow
Resource: 'arn:aws:lambda:*:*:layer:*:*'
Condition:
StringNotEquals:
'aws:ResourceTag/DatadogAgentlessScanner': 'false'
- Action:
- "ecr:GetAuthorizationToken"
Effect: Allow
Resource: "*"
- Action:
- "ecr:GetDownloadUrlForLayer"
- "ecr:BatchGetImage"
Condition:
StringNotEquals:
"ecr:ResourceTag/DatadogAgentlessScanner": "false"
Effect: Allow
Resource: "arn:aws:ecr:*:*:repository/*"
- Action: 's3:GetObject'
Effect: Allow
Resource: 'arn:aws:s3:::*/*'
- Action: 's3:ListBucket'
Effect: Allow
Resource: 'arn:aws:s3:::*'
- Action:
- 'kms:Decrypt'
- 'kms:GenerateDataKey'
Effect: Allow
Resource: 'arn:aws:kms:*:*:key/*'
Condition:
StringLike:
'kms:ViaService': 's3.*.amazonaws.com'

LambdaExecutionRoleDatadogAgentlessAPICall:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: "/"
ManagedPolicyArns:
- !Sub "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"

SecurityAuditPolicyAttachmentPermissions:
Type: AWS::IAM::RolePolicy
Properties:
RoleName: !Ref LambdaExecutionRoleDatadogAgentlessAPICall
PolicyName: SecurityAuditPolicyAttachmentPermissions
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- iam:ListAttachedRolePolicies
Resource: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${DatadogIntegrationRoleName}"
- Effect: Allow
Action:
- iam:AttachRolePolicy
Resource: !Sub "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/${DatadogIntegrationRoleName}"
Condition:
ArnEquals:
'iam:PolicyARN':
- !Sub "arn:${AWS::Partition}:iam::aws:policy/SecurityAudit"

DatadogAgentlessAPICall:
Type: "Custom::DatadogAgentlessAPICall"
Properties:
ServiceToken: !GetAtt "DatadogAgentlessAPICallFunction.Arn"
TemplateVersion: "<VERSION_PLACEHOLDER>"
APIKey: !Ref "DatadogAPIKey"
APPKey: !Ref "DatadogAPPKey"
DatadogSite: !Ref "DatadogSite"
AccountId: !Ref "AWS::AccountId"
VulnerabilityScanning: !Ref "AgentlessVulnerabilityScanning"
SensitiveData: !Ref "AgentlessSensitiveDataScanning"
ComplianceHost: !Ref "AgentlessComplianceHostScanning"
IntegrationRoleName: !Ref "DatadogIntegrationRoleName"
Partition: !Ref "AWS::Partition"
# Optional parameters
SaaSScanningPolicyArn: !Ref "DatadogAgentlessSaaSScanningPolicy"

DatadogAgentlessAPICallFunction:
Type: "AWS::Lambda::Function"
Properties:
Description: A function to call the Datadog Agentless API.
Role: !GetAtt LambdaExecutionRoleDatadogAgentlessAPICall.Arn
Handler: "index.handler"
LoggingConfig:
ApplicationLogLevel: "INFO"
LogFormat: "JSON"
Runtime: "python3.13"
Timeout: 30
Code:
ZipFile: |
<ZIPFILE_PLACEHOLDER>

Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Required"
Parameters:
- DatadogIntegrationRoleName
- Label:
default: "Advanced"
Parameters:
- AgentlessSensitiveDataScanning
- AgentlessComplianceHostScanning
- AccountId
2 changes: 1 addition & 1 deletion aws_quickstart/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ for template in main_workflow.yaml main_extended_workflow.yaml main_v2.yaml main
done

# Process Agentless Scanning templates
for template in datadog_agentless_delegate_role.yaml datadog_agentless_scanning.yaml datadog_agentless_delegate_role_snapshot.yaml datadog_integration_autoscaling_policy.yaml datadog_integration_sds_policy.yaml datadog_agentless_delegate_role_stackset.yaml; do
for template in datadog_agentless_delegate_role.yaml datadog_agentless_scanning.yaml datadog_agentless_delegate_role_snapshot.yaml datadog_integration_autoscaling_policy.yaml datadog_integration_sds_policy.yaml datadog_agentless_delegate_role_stackset.yaml datadog_agentless_saas.yaml; do
# Note: unlike above, here we remove the 'v' prefix from the version
perl -pi -e "s/<VERSION_PLACEHOLDER>/${VERSION#v}/g" "$template"

Expand Down
2 changes: 1 addition & 1 deletion aws_quickstart/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v4.13.0
v4.14.0
Loading