Skip to content
Draft
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
29 changes: 29 additions & 0 deletions aws_account_integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Datadog AWS Account Automatic Integration

[![Launch Stack](https://s3.amazonaws.com/cloudformation-examples/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home#/stacks/create/review?stackName=datadog-aws-account-integration&templateURL=https://datadog-cloudformation-template.s3.amazonaws.com/aws/main_aws_account_integration.yaml)

## AWS Resources

This template creates a stack with a list of resources which captures create account events and integrates them automatically to Datadog.

- `DatadogKeysSecret` a Secret which stores both API and APP keys.
- `DatadogAccountIntegrationTrail` a Cloudtrail trail capturing management events which include create account events.
- `DatadogAccountIntegrationTrailBucket` a S3 bucket to store Cloudtrail events.
- `DatadogAccountIntegrationTrailBucketPolicy` a Policy for the above S3 bucket
- `DatadogAccountIntegrationEventRule` an Eventbridge rule capturing Cloudtrail events to trigger a Lambda function to execute some logic.
- `DatadogAccountIntegrationEventLambdaPermission` an Invocation Permission allowing Eventbridge to trigger Lambda.
- `DatadogAccountIntegrationLambdaFunction` a Lambda function which gets triggered upon receiving Eventbridge events indicating the creation of new accounts.
- `DatadogAccountIntegrationLambdaFunctionRole` a Lambda function role allowing reading secrets and assume role into newly created accounts.

## Publishing the template
Use the release script to upload the template to a S3 bucket following the example below. Make sure you have correct access credentials before launching the script.

```
./release <bucket_name>
```

Use an optional argument `--private` to prevent granting public access to the uploaded template (good for testing purposes).
The uploaded template file can be found at `/aws/main_aws_account_integration.yaml` key on the chosen S3 bucket.



311 changes: 311 additions & 0 deletions aws_account_integration/main_aws_account_integration.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
AWSTemplateFormatVersion: "2010-09-09"
Description: Datadog AWS Account Auto Integration
Parameters:
DdApiKey:
Description: Datadog API Key
Type: String
NoEcho: true
DdAppKey:
Description: Datadog Application Key
Type: String
NoEcho: true
DdSite:
Description: Datadog Site
Type: String
Default: datadoghq.com
AllowedValues:
- datadoghq.com
- datadoghq.eu
- us3.datadoghq.com
- us5.datadoghq.com
- ap1.datadoghq.com
- ddog-gov.com
DdInstallLambdaLogForwarder:
Description: Install Lambda Log Forwarder
Type: String
Default: true
AllowedValues:
- true
- false
DdEnableCspm:
Description: Enable Cloud Security Posture Management
Type: String
Default: false
AllowedValues:
- true
- false
DdDisableMetricCollection:
Description: Disable Metric Collection
Type: String
Default: false
AllowedValues:
- true
- false
DdDisableResourceCollection:
Description: Disable Resource Collection
Type: String
Default: false
AllowedValues:
- true
- false
DdIamIntegrationRoleName:
Description: Datadog IAM Integration Role Name
Type: String
Default: DatadogIntegrationRole
Resources:
DatadogKeysSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: "DatadogKeysSecret"
Description: "Datadog API and Application Keys"
SecretString:
!Sub |
{
"DD_API_KEY": "${DdApiKey}",
"DD_APP_KEY": "${DdAppKey}"
}
DatadogAccountIntegrationLambdaFunctionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
ManagedPolicyArns:
- Fn::Sub: "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
Policies:
- PolicyName: DatadogAccountIntegrationLambdaFunctionPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Resource:
- Fn::Sub: "arn:${AWS::Partition}:iam::*:role/OrganizationAccountAccessRole"
- Effect: Allow
Action:
- secretsmanager:DescribeSecret
- secretsmanager:GetSecretValue
Resource:
- !Ref DatadogKeysSecret
DatadogAccountIntegrationTrailBucket:
Type: AWS::S3::Bucket
Properties:
BucketName:
Fn::Join:
- "-"
- - "datadog-account-integration"
- !Select
- 0
- !Split
- "-"
- !Select
- 2
- !Split
- "/"
- !Ref "AWS::StackId"
DatadogAccountIntegrationTrailBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref "DatadogAccountIntegrationTrailBucket"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: "cloudtrail.amazonaws.com"
Action: "s3:GetBucketAcl"
Resource:
- Fn::GetAtt: ["DatadogAccountIntegrationTrailBucket", "Arn"]
Condition:
StringEquals:
"aws:SourceArn":
- Fn::Sub: "arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail"
- Effect: Allow
Principal:
Service: "cloudtrail.amazonaws.com"
Action: "s3:PutObject"
Resource:
- Fn::Join:
- ""
- - Fn::GetAtt: ["DatadogAccountIntegrationTrailBucket", "Arn"]
- "/datadog-account-integration/*"
Condition:
StringEquals:
"s3:x-amz-acl": "bucket-owner-full-control"
"aws:SourceArn":
- Fn::Sub: "arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail"
DatadogAccountIntegrationTrail:
DependsOn:
- DatadogAccountIntegrationTrailBucketPolicy
Type: AWS::CloudTrail::Trail
Properties:
TrailName: "DatadogAccountIntegrationTrail"
IsLogging: true
IsMultiRegionTrail: true
IncludeGlobalServiceEvents: true
EventSelectors:
- IncludeManagementEvents: true
ReadWriteType: WriteOnly
S3BucketName: !Ref "DatadogAccountIntegrationTrailBucket"
S3KeyPrefix: "datadog-account-integration"
DatadogAccountIntegrationLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.handler
Runtime: python3.13
Timeout: 600
Code:
ZipFile: |
import os
import time
import boto3
import json

def handler(event, context):
if event.get("source") != "aws.organizations":
return

if event.get("detail").get("eventName") != "CreateAccountResult":
return

if event.get("detail").get("serviceEventDetails").get("createAccountStatus").get("state") != "SUCCEEDED":
return

# We need to add a sleep here, to allow AWS finish provisioning of a new AWS account.
print(f"Sleep for 3 minutes to allow AWS finish provisioning of a new AWS account")
time.sleep(180)

newAccountId = event.get("detail").get("serviceEventDetails").get("createAccountStatus").get("accountId")
role = f"arn:aws:iam::{newAccountId}:role/OrganizationAccountAccessRole"
session = boto3.Session()

sts = session.client("sts")
assumeRoleResponse = sts.assume_role(RoleArn=role, RoleSessionName="datadog-aws-provisioning")

secrets = session.client("secretsmanager")
response = secrets.get_secret_value(SecretId=os.environ["DD_SECRET_ID"])
secret = json.loads(response["SecretString"])
dd_api_key = secret.get("DD_API_KEY")
dd_app_key = secret.get("DD_APP_KEY")

new_session = boto3.Session(
aws_access_key_id=assumeRoleResponse["Credentials"]["AccessKeyId"],
aws_secret_access_key=assumeRoleResponse["Credentials"]["SecretAccessKey"],
aws_session_token=assumeRoleResponse["Credentials"]["SessionToken"],
)

cloudformation = new_session.client("cloudformation")
response = cloudformation.create_stack(
StackName="Datadog-Integration-New-AWS-Account-cf-stack",
TemplateURL="https://datadog-cloudformation-template-quickstart.s3.amazonaws.com/aws/v2.0.5/main_v2.yaml",
Parameters=[
{
"ParameterKey": "APIKey",
"ParameterValue": dd_api_key,
},
{
"ParameterKey": "APPKey",
"ParameterValue": dd_app_key,
},
{
"ParameterKey": "DatadogSite",
"ParameterValue": os.environ["DD_SITE"],
},
{
"ParameterKey": "InstallLambdaLogForwarder",
"ParameterValue": os.environ["DD_INSTALL_LAMBDA_LOG_FORWARDER"],
},
{
"ParameterKey": "DisableMetricCollection",
"ParameterValue": os.environ["DD_DISABLE_METRIC_COLLECTION"],
},
{
"ParameterKey": "DisableResourceCollection",
"ParameterValue": os.environ["DD_DISABLE_RESOURCE_COLLECTION"],
},
{
"ParameterKey": "CloudSecurityPostureManagement",
"ParameterValue": os.environ["DD_ENABLE_CSPM"],
},
{
"ParameterKey": "IAMRoleName",
"ParameterValue": os.environ["DD_IAM_INTEGRATION_ROLE_NAME"],
},
],
Capabilities=["CAPABILITY_IAM", "CAPABILITY_NAMED_IAM", "CAPABILITY_AUTO_EXPAND"],
EnableTerminationProtection=False,
TimeoutInMinutes=10,
)

stack_id = response["StackId"]

print(f"Wait for Datadog Integration for account {newAccountId} to be created")

while True:
response = cloudformation.describe_stacks(StackName=stack_id)
if response["Stacks"][0]["StackStatus"] == "CREATE_IN_PROGRESS":
time.sleep(10)
continue

if response["Stacks"][0]["StackStatus"] == "CREATE_COMPLETE":
print(f"Datadog Integration for account {newAccountId} has been successfully created")
break

print(f"Datadog Integration for account {newAccountId} failed to create")
RuntimeError(f"Datadog Integration for account {newAccountId} failed to create")
Environment:
Variables:
DD_SECRET_ID: !Ref DatadogKeysSecret
DD_SITE: !Ref DdSite
DD_INSTALL_LAMBDA_LOG_FORWARDER: !Ref DdInstallLambdaLogForwarder
DD_ENABLE_CSPM: !Ref DdEnableCspm
DD_DISABLE_METRIC_COLLECTION: !Ref DdDisableMetricCollection
DD_DISABLE_RESOURCE_COLLECTION: !Ref DdDisableResourceCollection
DD_IAM_INTEGRATION_ROLE_NAME: !Ref DdIamIntegrationRoleName
Role:
Fn::GetAtt:
- "DatadogAccountIntegrationLambdaFunctionRole"
- "Arn"
DatadogAccountIntegrationEventLambdaPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName:
Fn::GetAtt:
- "DatadogAccountIntegrationLambdaFunction"
- "Arn"
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn:
Fn::GetAtt:
- "DatadogAccountIntegrationEventRule"
- "Arn"
DatadogAccountIntegrationEventRule:
Type: 'AWS::Events::Rule'
Properties:
Name: 'DatadogAccountIntegrationEventBridgeRule'
Description: 'Datadog Account Integration EventBridge Rule'
EventPattern:
source:
- aws.organizations
detail:
eventName:
- CreateAccountResult
serviceEventDetails:
createAccountStatus:
state:
- SUCCEEDED
State: ENABLED
Targets:
-
Arn:
Fn::GetAtt:
- "DatadogAccountIntegrationLambdaFunction"
- "Arn"
Id: 'DatadogAccountIntegration'
48 changes: 48 additions & 0 deletions aws_account_integration/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/env bash

# Usage: ./release.sh <S3_Bucket>

set -e

# Read the S3 bucket
if [ -z "$1" ]; then
echo "Must specify a S3 bucket to publish the template"
exit 1
else
BUCKET=$1
fi

# Upload templates to a private bucket -- useful for testing
if [[ $# -eq 2 ]] && [[ $2 = "--private" ]]; then
PRIVATE_TEMPLATE=true
else
PRIVATE_TEMPLATE=false
fi

# Confirm to proceed
for i in *.yaml; do
[ -f "$i" ] || break
echo "About to upload $i to s3://${BUCKET}/aws/$i"
done
read -p "Continue (y/n)?" CONT
if [ "$CONT" != "y" ]; then
echo "Exiting"
exit 1
fi

# Update bucket placeholder
cp main_aws_account_integration.yaml main_aws_account_integration.yaml.bak
perl -pi -e "s/<BUCKET_PLACEHOLDER>/${BUCKET}/g" main_aws_account_integration.yaml
trap 'mv main_aws_account_integration.yaml.bak main_aws_account_integration.yaml' EXIT

# Upload
if [ "$PRIVATE_TEMPLATE" = true ] ; then
aws s3 cp . s3://${BUCKET}/aws --recursive --exclude "*" --include "*.yaml"
else
aws s3 cp . s3://${BUCKET}/aws --recursive --exclude "*" --include "*.yaml" \
--grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers
fi
echo "Done uploading the template, and here is the CloudFormation quick launch URL"
echo "https://console.aws.amazon.com/cloudformation/home#/stacks/create/review?stackName=datadog-aws-account-integration&templateURL=https://${BUCKET}.s3.amazonaws.com/aws/main_aws_account_integration.yaml"

echo "Done!"