From cc85e3d6cb7f1ca58561e48718cd51bb2ff3cb39 Mon Sep 17 00:00:00 2001 From: Georgi Date: Mon, 3 Feb 2025 13:17:23 +0100 Subject: [PATCH 01/15] [WIP] add a template to automate aws account integration --- aws_account_integration/main.yaml | 141 ++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 aws_account_integration/main.yaml diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml new file mode 100644 index 00000000..06b9f712 --- /dev/null +++ b/aws_account_integration/main.yaml @@ -0,0 +1,141 @@ +AWSTemplateFormatVersion: "2010-09-09" +Description: Datadog AWS Account 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 + DdEnableResourceCollection: + Description: Enable Resource Collection + Type: String + Default: true + AllowedValues: + - true + - false +Resources: + DatadogAccountIntegrationLambdaFunctionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Action: + - sts:AssumeRole + Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Policies: + - PolicyName: DatadogAccountIntegrationLambdaFunctionPolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - cloudformation:CreateStack + - cloudformation:UpdateStack + Resource: "*" + + DatadogAccountIntegrationLambdaFunction: + Type: AWS::Lambda::Function + Properties: + Handler: index.handler + Runtime: python3.12 + Timeout: 600 + Code: + ZipFile: | + import boto3 + + def timeout(): + # TBD + raise Exception('Timeout') + + def handler(event, context): + # TBD + print(event) + Environment: + Variables: + DD_API_KEY: !Ref DdApiKey + DD_APP_KEY: !Ref DdAppKey + DD_SITE: !Ref DdSite + DD_INSTALL_LAMBDA_LOG_FORWARDER: !Ref DdInstallLambdaLogForwarder + DD_ENABLE_CSPM: !Ref DdEnableCspm + DD_DISABLE_METRIC_COLLECTION: !Ref DdDisableMetricCollection + DD_ENABLE_RESOURCE_COLLECTION: !Ref DdEnableResourceCollection + 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-type: + - 'AWS create account notificaiton' + detail: + eventName: + - CreateAccountResult + serviceEventDetails: + createAccountStatus: + - SUCCEEDED + State: ENABLED + Targets: + - + Arn: + Fn::GetAtt: + - "DatadogAccountIntegrationLambdaFunction" + - "Arn" + Id: 'DatadogAccountIntegration' From 1e7c3d8db7ec8f03431a6225d2d9a8a9acd9ce57 Mon Sep 17 00:00:00 2001 From: Georgi Date: Mon, 3 Feb 2025 15:14:03 +0100 Subject: [PATCH 02/15] update lambda policy to assume role --- aws_account_integration/main.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 06b9f712..02aaf158 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -61,6 +61,8 @@ Resources: Principal: Service: - lambda.amazonaws.com + ManagedPolicyArns: + - Fn::Sub: "arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" Policies: - PolicyName: DatadogAccountIntegrationLambdaFunctionPolicy PolicyDocument: @@ -68,9 +70,8 @@ Resources: Statement: - Effect: Allow Action: - - cloudformation:CreateStack - - cloudformation:UpdateStack - Resource: "*" + - sts:AssumeRole + Resource: "arn:${AWS::Partition}:iam::*:role/OrganizationAccountAccessRole" DatadogAccountIntegrationLambdaFunction: Type: AWS::Lambda::Function From dde6de737fe7809637b90b325861735041ec417c Mon Sep 17 00:00:00 2001 From: Georgi Date: Mon, 3 Feb 2025 15:21:41 +0100 Subject: [PATCH 03/15] update env vars --- aws_account_integration/main.yaml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 02aaf158..51a0e087 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -41,13 +41,17 @@ Parameters: AllowedValues: - true - false - DdEnableResourceCollection: + DdDisableResourceCollection: Description: Enable Resource Collection Type: String Default: true AllowedValues: - true - false + DdIamIntegrationRoleName: + Description: Datadog IAM Integration Role Name + Type: String + Default: DatadogIntegrationRole Resources: DatadogAccountIntegrationLambdaFunctionRole: Type: AWS::IAM::Role @@ -98,7 +102,8 @@ Resources: DD_INSTALL_LAMBDA_LOG_FORWARDER: !Ref DdInstallLambdaLogForwarder DD_ENABLE_CSPM: !Ref DdEnableCspm DD_DISABLE_METRIC_COLLECTION: !Ref DdDisableMetricCollection - DD_ENABLE_RESOURCE_COLLECTION: !Ref DdEnableResourceCollection + DD_DISABLE_RESOURCE_COLLECTION: !Ref DdDisableResourceCollection + DD_IAM_INTEGRATION_ROLE_NAME: !Ref DdIamIntegrationRoleName Role: Fn::GetAtt: - "DatadogAccountIntegrationLambdaFunctionRole" From 48443474324004c423a24346221fd61cd1616165 Mon Sep 17 00:00:00 2001 From: Ivan Klishch Date: Tue, 4 Feb 2025 13:01:10 +0100 Subject: [PATCH 04/15] Add Lambda function for account provisioning --- aws_account_integration/main.yaml | 102 +++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 9 deletions(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 51a0e087..42951b3d 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -75,25 +75,109 @@ Resources: - Effect: Allow Action: - sts:AssumeRole - Resource: "arn:${AWS::Partition}:iam::*:role/OrganizationAccountAccessRole" + Resource: + - Fn::Sub: "arn:${AWS::Partition}:iam::*:role/OrganizationAccountAccessRole" DatadogAccountIntegrationLambdaFunction: Type: AWS::Lambda::Function Properties: Handler: index.handler - Runtime: python3.12 + Runtime: python3.13 Timeout: 600 Code: ZipFile: | + import os + import time + import boto3 - - def timeout(): - # TBD - raise Exception('Timeout') - + + def handler(event, context): - # TBD - print(event) + + 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. + 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") + response = sts.assume_role(RoleArn=role, RoleSessionName="datadog-aws-provisioning") + + new_session = boto3.Session( + aws_access_key_id=response["Credentials"]["AccessKeyId"], + aws_secret_access_key=response["Credentials"]["SecretAccessKey"], + aws_session_token=response["Credentials"]["SessionToken"], + ) + + client = new_session.client("cloudformation") + + response = client.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": os.environ["DD_API_KEY"], + }, + { + "ParameterKey": "APPKey", + "ParameterValue": os.environ["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"] + + while True: + response = client.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") + print(response) + RuntimeError(f"Datadog Integration for account {newAccountId} failed to create") Environment: Variables: DD_API_KEY: !Ref DdApiKey From 0280b1fe1448281b3b1fc194b5cc0cdfee26e7a1 Mon Sep 17 00:00:00 2001 From: Georgi Date: Tue, 4 Feb 2025 13:47:23 +0100 Subject: [PATCH 05/15] update eventbridge rule + add trail for logging --- aws_account_integration/main.yaml | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 42951b3d..505a6e1a 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -42,9 +42,9 @@ Parameters: - true - false DdDisableResourceCollection: - Description: Enable Resource Collection + Description: Disable Resource Collection Type: String - Default: true + Default: false AllowedValues: - true - false @@ -77,7 +77,22 @@ Resources: - sts:AssumeRole Resource: - Fn::Sub: "arn:${AWS::Partition}:iam::*:role/OrganizationAccountAccessRole" - + DatadogAccountIntegrationTrailBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: "datadog-account-integration-trail-bucket" + DatadogAccountIntegrationTrail: + Type: AWS::CloudTrail::Trail + Properties: + TrailName: "DatadogAccountIntegrationTrail" + IsLogging: true + IsMultiRegionTrail: true + EventSelectors: + IncludeManagementEvents: true + ReadWriteType: WriteOnly + S3BucketName: !Ref "DatadogAccountIntegrationTrailBucket" + S3KeyPrefix: "datadog-account-integration" + DatadogAccountIntegrationLambdaFunction: Type: AWS::Lambda::Function Properties: @@ -88,12 +103,9 @@ Resources: ZipFile: | import os import time - import boto3 - def handler(event, context): - if event.get("source") != "aws.organizations": return @@ -213,8 +225,6 @@ Resources: EventPattern: source: - aws.organizations - detail-type: - - 'AWS create account notificaiton' detail: eventName: - CreateAccountResult From 4bfacbdbc1ef2262803d99207d2de7efc9b798b7 Mon Sep 17 00:00:00 2001 From: Ivan Klishch Date: Tue, 4 Feb 2025 14:21:32 +0100 Subject: [PATCH 06/15] Add IncludeGlobalServiceEvents, fix a typo with array --- aws_account_integration/main.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 505a6e1a..4367f540 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -87,9 +87,10 @@ Resources: TrailName: "DatadogAccountIntegrationTrail" IsLogging: true IsMultiRegionTrail: true + IncludeGlobalServiceEvents: true EventSelectors: - IncludeManagementEvents: true - ReadWriteType: WriteOnly + - IncludeManagementEvents: true + ReadWriteType: WriteOnly S3BucketName: !Ref "DatadogAccountIntegrationTrailBucket" S3KeyPrefix: "datadog-account-integration" From 916dc120c380a0720f5f8ce3a14e2d24bf1ad1b9 Mon Sep 17 00:00:00 2001 From: Georgi Date: Tue, 4 Feb 2025 14:35:06 +0100 Subject: [PATCH 07/15] add cloudtrail bucket policy --- aws_account_integration/main.yaml | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 4367f540..37bb56fc 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -81,6 +81,33 @@ Resources: Type: AWS::S3::Bucket Properties: BucketName: "datadog-account-integration-trail-bucket" + 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::Sub: "arn:${AWS::Partition}:s3:::datadog-account-integration-trail-bucket" + Condition: + StringLike: + "aws:SourceArn": + - Fn::Sub: "arn:${AWS::Partition}:cloudtrail:*:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail" + - Effect: Allow + Principal: + Service: "cloudtrail.amazonaws.com" + Action: "s3:PutObject" + Resource: + - Fn::Sub: "arn:${AWS::Partition}:s3:::datadog-account-integration-trail-bucket/datadog-account-integration/*" + Condition: + StringLike: + "aws:SourceArn": + - Fn::Sub: "arn:${AWS::Partition}:cloudtrail:*:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail" DatadogAccountIntegrationTrail: Type: AWS::CloudTrail::Trail Properties: @@ -93,7 +120,6 @@ Resources: ReadWriteType: WriteOnly S3BucketName: !Ref "DatadogAccountIntegrationTrailBucket" S3KeyPrefix: "datadog-account-integration" - DatadogAccountIntegrationLambdaFunction: Type: AWS::Lambda::Function Properties: From a40e9aa07286cec4b792be0144abfd992758cd1f Mon Sep 17 00:00:00 2001 From: Georgi Date: Tue, 4 Feb 2025 14:55:01 +0100 Subject: [PATCH 08/15] add dependency for trail --- aws_account_integration/main.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 37bb56fc..f9d7007f 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -120,6 +120,9 @@ Resources: ReadWriteType: WriteOnly S3BucketName: !Ref "DatadogAccountIntegrationTrailBucket" S3KeyPrefix: "datadog-account-integration" + DependsOn: + - DatadogAccountIntegrationTrailBucketPolicy + DatadogAccountIntegrationTrailBucket DatadogAccountIntegrationLambdaFunction: Type: AWS::Lambda::Function Properties: From b46fdadf1114ccf19f8d64cc63419e3b92ce6e89 Mon Sep 17 00:00:00 2001 From: Georgi Date: Tue, 4 Feb 2025 15:11:28 +0100 Subject: [PATCH 09/15] update bucket policy - take 2 --- aws_account_integration/main.yaml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index f9d7007f..01995a7b 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -80,7 +80,7 @@ Resources: DatadogAccountIntegrationTrailBucket: Type: AWS::S3::Bucket Properties: - BucketName: "datadog-account-integration-trail-bucket" + BucketName: "automatic-account-integration-trail-bucket" DatadogAccountIntegrationTrailBucketPolicy: Type: AWS::S3::BucketPolicy Properties: @@ -93,21 +93,25 @@ Resources: Service: "cloudtrail.amazonaws.com" Action: "s3:GetBucketAcl" Resource: - - Fn::Sub: "arn:${AWS::Partition}:s3:::datadog-account-integration-trail-bucket" + - Fn::GetAtt: ["DatadogAccountIntegrationTrailBucket", "Arn"] Condition: - StringLike: + StringEquals: "aws:SourceArn": - - Fn::Sub: "arn:${AWS::Partition}:cloudtrail:*:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail" + - Fn::Sub: "arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail" - Effect: Allow Principal: Service: "cloudtrail.amazonaws.com" Action: "s3:PutObject" Resource: - - Fn::Sub: "arn:${AWS::Partition}:s3:::datadog-account-integration-trail-bucket/datadog-account-integration/*" + - Fn::Join: + - "" + - - Fn::GetAtt: ["DatadogAccountIntegrationTrailBucket", "Arn"] + - "/datadog-account-integration/*" Condition: - StringLike: + StringEquals: + "s3:x-amz-acl": "bucket-owner-full-control" "aws:SourceArn": - - Fn::Sub: "arn:${AWS::Partition}:cloudtrail:*:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail" + - Fn::Sub: "arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail" DatadogAccountIntegrationTrail: Type: AWS::CloudTrail::Trail Properties: @@ -120,9 +124,6 @@ Resources: ReadWriteType: WriteOnly S3BucketName: !Ref "DatadogAccountIntegrationTrailBucket" S3KeyPrefix: "datadog-account-integration" - DependsOn: - - DatadogAccountIntegrationTrailBucketPolicy - DatadogAccountIntegrationTrailBucket DatadogAccountIntegrationLambdaFunction: Type: AWS::Lambda::Function Properties: From 23dc10ec5065b48d2810fab1748c4f4e4e7ee990 Mon Sep 17 00:00:00 2001 From: Georgi Date: Tue, 4 Feb 2025 16:52:22 +0100 Subject: [PATCH 10/15] add dependency for trail on policy --- aws_account_integration/main.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 01995a7b..25c7db0b 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -113,6 +113,8 @@ Resources: "aws:SourceArn": - Fn::Sub: "arn:${AWS::Partition}:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/DatadogAccountIntegrationTrail" DatadogAccountIntegrationTrail: + DependsOn: + - DatadogAccountIntegrationTrailBucketPolicy Type: AWS::CloudTrail::Trail Properties: TrailName: "DatadogAccountIntegrationTrail" From a2572ea7714db74a67ea1d43585a94a02a612a8f Mon Sep 17 00:00:00 2001 From: Georgi Date: Tue, 4 Feb 2025 17:02:42 +0100 Subject: [PATCH 11/15] update rule matching --- aws_account_integration/main.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 25c7db0b..4f800fbd 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -263,7 +263,8 @@ Resources: - CreateAccountResult serviceEventDetails: createAccountStatus: - - SUCCEEDED + state: + - SUCCEEDED State: ENABLED Targets: - From c00cb0b3fda6e76dd32f366bd5d83f57a7cbfdee Mon Sep 17 00:00:00 2001 From: Georgi Date: Tue, 4 Feb 2025 17:24:59 +0100 Subject: [PATCH 12/15] add a random string to trail bucket name + add logs --- aws_account_integration/main.yaml | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 4f800fbd..7b172a93 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -80,7 +80,19 @@ Resources: DatadogAccountIntegrationTrailBucket: Type: AWS::S3::Bucket Properties: - BucketName: "automatic-account-integration-trail-bucket" + BucketName: + Fn::Join: + - "-" + - - "datadog-account-integration" + - !Select + - 0 + - !Split + - "-" + - !Select + - 2 + - !Split + - "/" + - !Ref "AWS::StackId" DatadogAccountIntegrationTrailBucketPolicy: Type: AWS::S3::BucketPolicy Properties: @@ -149,6 +161,7 @@ Resources: 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") @@ -209,7 +222,9 @@ Resources: ) stack_id = response["StackId"] - + + print(f"Wait for Datadog Integration for account {newAccountId} to be created") + while True: response = client.describe_stacks(StackName=stack_id) if response["Stacks"][0]["StackStatus"] == "CREATE_IN_PROGRESS": @@ -221,7 +236,6 @@ Resources: break print(f"Datadog Integration for account {newAccountId} failed to create") - print(response) RuntimeError(f"Datadog Integration for account {newAccountId} failed to create") Environment: Variables: From b0dae391e0c388bd45193b485056cb40c9d02eb5 Mon Sep 17 00:00:00 2001 From: Georgi Date: Wed, 5 Feb 2025 13:59:26 +0100 Subject: [PATCH 13/15] store keys in a secret --- aws_account_integration/main.yaml | 38 ++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main.yaml index 7b172a93..d5f78dab 100644 --- a/aws_account_integration/main.yaml +++ b/aws_account_integration/main.yaml @@ -1,5 +1,5 @@ AWSTemplateFormatVersion: "2010-09-09" -Description: Datadog AWS Account Integration +Description: Datadog AWS Account Auto Integration Parameters: DdApiKey: Description: Datadog API Key @@ -53,6 +53,17 @@ Parameters: 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: @@ -77,6 +88,12 @@ Resources: - 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: @@ -149,6 +166,7 @@ Resources: import os import time import boto3 + import json def handler(event, context): if event.get("source") != "aws.organizations": @@ -171,25 +189,30 @@ Resources: sts = session.client("sts") response = 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=response["Credentials"]["AccessKeyId"], aws_secret_access_key=response["Credentials"]["SecretAccessKey"], aws_session_token=response["Credentials"]["SessionToken"], ) - client = new_session.client("cloudformation") - - response = client.create_stack( + 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": os.environ["DD_API_KEY"], + "ParameterValue": dd_api_key, }, { "ParameterKey": "APPKey", - "ParameterValue": os.environ["DD_APP_KEY"], + "ParameterValue": dd_app_key, }, { "ParameterKey": "DatadogSite", @@ -239,8 +262,7 @@ Resources: RuntimeError(f"Datadog Integration for account {newAccountId} failed to create") Environment: Variables: - DD_API_KEY: !Ref DdApiKey - DD_APP_KEY: !Ref DdAppKey + DD_SECRET_ID: !Ref DatadogKeysSecret DD_SITE: !Ref DdSite DD_INSTALL_LAMBDA_LOG_FORWARDER: !Ref DdInstallLambdaLogForwarder DD_ENABLE_CSPM: !Ref DdEnableCspm From cc7e7e21a56e2fe01b0145dd87f193d66266c726 Mon Sep 17 00:00:00 2001 From: Georgi Date: Wed, 5 Feb 2025 15:16:00 +0100 Subject: [PATCH 14/15] add readme + release script --- aws_account_integration/README.md | 29 +++++++++++ ...yaml => main_aws_account_integration.yaml} | 0 aws_account_integration/release.sh | 48 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 aws_account_integration/README.md rename aws_account_integration/{main.yaml => main_aws_account_integration.yaml} (100%) create mode 100755 aws_account_integration/release.sh diff --git a/aws_account_integration/README.md b/aws_account_integration/README.md new file mode 100644 index 00000000..34f886a2 --- /dev/null +++ b/aws_account_integration/README.md @@ -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 +``` + +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. + + + diff --git a/aws_account_integration/main.yaml b/aws_account_integration/main_aws_account_integration.yaml similarity index 100% rename from aws_account_integration/main.yaml rename to aws_account_integration/main_aws_account_integration.yaml diff --git a/aws_account_integration/release.sh b/aws_account_integration/release.sh new file mode 100755 index 00000000..ada4a04e --- /dev/null +++ b/aws_account_integration/release.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +# Usage: ./release.sh + +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}/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!" From e87a0cbb87973a5b415691de83d881fe487a6142 Mon Sep 17 00:00:00 2001 From: Ivan Klishch Date: Wed, 5 Feb 2025 16:41:57 +0100 Subject: [PATCH 15/15] Fix few typos after refactoring --- .../main_aws_account_integration.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aws_account_integration/main_aws_account_integration.yaml b/aws_account_integration/main_aws_account_integration.yaml index d5f78dab..bdad3a82 100644 --- a/aws_account_integration/main_aws_account_integration.yaml +++ b/aws_account_integration/main_aws_account_integration.yaml @@ -187,7 +187,7 @@ Resources: session = boto3.Session() sts = session.client("sts") - response = sts.assume_role(RoleArn=role, RoleSessionName="datadog-aws-provisioning") + 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"]) @@ -196,9 +196,9 @@ Resources: dd_app_key = secret.get("DD_APP_KEY") new_session = boto3.Session( - aws_access_key_id=response["Credentials"]["AccessKeyId"], - aws_secret_access_key=response["Credentials"]["SecretAccessKey"], - aws_session_token=response["Credentials"]["SessionToken"], + 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") @@ -249,7 +249,7 @@ Resources: print(f"Wait for Datadog Integration for account {newAccountId} to be created") while True: - response = client.describe_stacks(StackName=stack_id) + response = cloudformation.describe_stacks(StackName=stack_id) if response["Stacks"][0]["StackStatus"] == "CREATE_IN_PROGRESS": time.sleep(10) continue