This post is on how to create a CloudTrail Trail with S3 and CloudWatch in CloudFormation.
In my previous article, I had provided 8 tips on how to configure CloudTrail for secure logging and auditing via the AWS management console. In this post, I have covered the same secure options using the much preferred and popular Infrastructure as Code using Cloud Formation.
This template covers the following:
- Create a CloudTrail Trail to log management events for all accounts under AWS Organization
- Create an encrypted S3 bucket associated with the trail
- Create CloudWatch logs and role associated with the trail
- Create CMK keys (SSE-KMS) for S3 and CloudTrail
AWSTemplateFormatVersion: 2010-09-09
Description: test createtrail function
Parameters:
CloudTrailName:
Type: String
Default: 'management-events-maghilda'
CloudTrailBucketName:
Type: String
Default: 'cloudtrail-maghilda'
CloudTrailBucketPrefix:
Type: String
Default: 'maghildaevents'
OrganizationRoot:
Type: String
Default: 'o-2rty7as5fl'
Resources:
# Create a new log group
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
RetentionInDays: 365 # optional
#Create a new bucket with all the secure options and encryption
CloudTrailBucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain #delete manually after the delete stack command
Properties:
BucketName: !Ref CloudTrailBucketName
AccessControl: Private
PublicAccessBlockConfiguration:
BlockPublicAcls: true
IgnorePublicAcls: true
BlockPublicPolicy: true
RestrictPublicBuckets: true
ObjectLockEnabled: true
ObjectLockConfiguration:
ObjectLockEnabled: 'Enabled'
Rule:
DefaultRetention:
Mode: GOVERNANCE
Days: 90
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
KMSMasterKeyID: !Sub 'arn:aws:kms:${AWS::Region}:${AWS::AccountId}:${CloudTrailKeyS3Alias}'
SSEAlgorithm: 'aws:kms'
DependsOn:
- CloudTrailKeyS3
- CloudTrailKeyS3Alias
#Create a bucket policy
CloudTrailBucketPolicy:
DependsOn:
- CloudTrailBucket
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref CloudTrailBucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: AWSCloudTrailAclCheck
Effect: Allow
Principal:
Service: 'cloudtrail.amazonaws.com'
Action: 's3:GetBucketAcl'
Resource: !Sub 'arn:aws:s3:::${CloudTrailBucket}'
- Sid: AWSCloudTrailWrite
Effect: Allow
Principal:
Service: 'cloudtrail.amazonaws.com'
Action: 's3:PutObject'
Resource: !Sub 'arn:aws:s3:::${CloudTrailBucket}/${CloudTrailBucketPrefix}/AWSLogs/${AWS::AccountId}/*'
Condition:
StringEquals:
's3:x-amz-acl': 'bucket-owner-full-control'
'aws:SourceArn': !Sub 'arn:aws:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${CloudTrailName}'
- Sid: AWSCloudTrailWriteOrg
Effect: Allow
Principal:
Service: 'cloudtrail.amazonaws.com'
Action: 's3:PutObject'
Resource: !Sub 'arn:aws:s3:::${CloudTrailBucket}/${CloudTrailBucketPrefix}/AWSLogs/${OrganizationRoot}/*' #log for all accounts in the org
Condition:
StringEquals:
's3:x-amz-acl': 'bucket-owner-full-control'
'aws:SourceArn': !Sub 'arn:aws:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${CloudTrailName}'
# Create a role for CloudWatch logs
CloudTrailLogsRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: cloudtrail.amazonaws.com
# Define a policy for the role
CloudTrailLogsPolicy:
Type: AWS::IAM::Policy
Properties:
PolicyDocument:
Statement:
- Action:
- logs:PutLogEvents
- logs:CreateLogStream
Effect: Allow
Resource: !GetAtt LogGroup.Arn
Version: '2012-10-17'
PolicyName: DefaultPolicy
Roles:
- Ref: CloudTrailLogsRole
#Create a new trail with secure options and KMS encryption
CloudTrail:
Type: AWS::CloudTrail::Trail
Properties:
TrailName: !Ref CloudTrailName
CloudWatchLogsLogGroupArn: !GetAtt LogGroup.Arn
CloudWatchLogsRoleArn: !GetAtt CloudTrailLogsRole.Arn
EnableLogFileValidation: true
IncludeGlobalServiceEvents: true
S3BucketName: !Ref CloudTrailBucket
S3KeyPrefix: !Ref CloudTrailBucketPrefix
IsLogging: true
IsMultiRegionTrail: true
IsOrganizationTrail: true
KMSKeyId: !GetAtt CloudTrailKey.Arn
DependsOn:
- CloudTrailLogsPolicy
- CloudTrailLogsRole
- CloudTrailBucket
- CloudTrailBucketPolicy
- CloudTrailKey
#Create a new CMK for the trail
CloudTrailKey:
Type: AWS::KMS::Key
Properties:
KeyPolicy:
Version: 2012-10-17
Id: key-cloudtrail
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS:
- !Sub 'arn:aws:iam::${AWS::AccountId}:root'
- !Sub 'arn:aws:sts::${AWS::AccountId}:assumed-role/AWSReservedSSO_AdministratorAccess_234hdfj4857555/maghilda_user'
Action: 'kms:*'
Resource: '*'
- Sid: Allow CloudTrail to encrypt logs
Effect: Allow
Principal:
Service:
- cloudtrail.amazonaws.com
Action: 'kms:GenerateDataKey*'
Resource: '*'
Condition:
StringLike:
'kms:EncryptionContext:aws:cloudtrail:arn': !Sub 'arn:aws:cloudtrail:*:${AWS::AccountId}:trail/*'
'aws:SourceArn': !Sub 'arn:aws:cloudtrail:${AWS::Region}:${AWS::AccountId}:trail/${CloudTrailName}'
- Sid: Allow CloudTrail to describe key
Effect: Allow
Principal:
Service:
- cloudtrail.amazonaws.com
Action: 'kms:DescribeKey'
Resource: '*'
- Sid: Allow principals in the account to decrypt log files
Effect: Allow
Principal:
AWS: '*'
Action:
- 'kms:Decrypt'
- 'kms:ReEncryptFrom'
Resource: '*'
Condition:
StringEquals:
'kms:CallerAccount': !Sub '${AWS::AccountId}'
StringLike:
'kms:EncryptionContext:aws:cloudtrail:arn': !Sub 'arn:aws:cloudtrail:*:${AWS::AccountId}:trail/*'
- Sid: Allow alias creation during setup
Effect: Allow
Principal:
AWS: '*'
Action: 'kms:CreateAlias'
Resource: '*'
Condition:
StringEquals:
'kms:ViaService': ec2.us-east-1.amazonaws.com
'kms:CallerAccount': !Sub '${AWS::AccountId}'
- Sid: Enable cross account log decryption
Effect: Allow
Principal:
AWS: '*'
Action:
- 'kms:Decrypt'
- 'kms:ReEncryptFrom'
Resource: '*'
Condition:
StringEquals:
'kms:CallerAccount': !Sub '${AWS::AccountId}'
StringLike:
'kms:EncryptionContext:aws:cloudtrail:arn': !Sub 'arn:aws:cloudtrail:*:${AWS::AccountId}:trail/*'
#Create an alias for the CloudTrail key
CloudTrailKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: alias/cloudtrail
TargetKeyId:
Ref: CloudTrailKey
#Create a new CMK for S3 bucket
CloudTrailKeyS3:
Type: AWS::KMS::Key
Properties:
KeyPolicy:
Version: 2012-10-17
Id: key-cloudtrails3
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS:
- !Sub 'arn:aws:iam::${AWS::AccountId}:root'
- !Sub 'arn:aws:sts::${AWS::AccountId}:assumed-role/AWSReservedSSO_AdministratorAccess_234hdfj4857555/maghilda_user'
Action: 'kms:*'
Resource: '*'
- Sid: Allow VPC Flow Logs to use the key
Effect: Allow
Principal:
Service:
- delivery.logs.amazonaws.com
Action: 'kms:GenerateDataKey*'
Resource: '*'
#Create an alias for the s3 bucket key
CloudTrailKeyS3Alias:
Type: AWS::KMS::Alias
Properties:
AliasName: alias/cloudtrails3
TargetKeyId:
Ref: CloudTrailKeyS3
Outputs:
CloudTrailLogsRoleArn:
Value: !GetAtt CloudTrailLogsRole.Arn
LogGroupArn:
Value: !GetAtt LogGroup.Arn
Save the file as createtrail-template.yml. Please note that I am using service linked role for IAM Identity Center with the name prefix AWSReservedSSO_
Create the above stack via the CLI
aws cloudformation create-stack --stack-name testcreatetrail --template-body file://createtrail-template.yml --capabilities CAPABILITY_IAM
This stack will take a few minutes to create. Run the following command to check the status
aws cloudformation describe-stacks --stack-name testcreatetrail
Clean up resources
#delete stack
aws cloudformation delete-stack --stack-name testcreatetrail
Since CloudFormation cannot delete a non-empty S3 bucket, you have to manually empty and delete the bucket
#empty
aws s3 rm s3://cloudtrail-maghilda —recursive
#delete
aws s3api delete-bucket --bucket cloudtrail-maghilda
References
- https://docs.aws.amazon.com/awscloudtrail/latest/userguide/create-s3-bucket-policy-for-cloudtrail.html
- https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-set-bucket-policy-for-multiple-accounts.html
- https://github.com/widdix/aws-cf-templates/blob/master/security/cloudtrail.yaml
- https://github.com/getcft/aws-cloudtrail-cf-template/blob/master/cloudtrail-cf-template.yml
- https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html
- https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html
- https://docs.aws.amazon.com/singlesignon/latest/userguide/using-service-linked-roles.html
Hope you find this useful.