diff --git a/lib/account-baseline-stack.ts b/lib/account-baseline-stack.ts index 3cadd24..128d423 100644 --- a/lib/account-baseline-stack.ts +++ b/lib/account-baseline-stack.ts @@ -37,6 +37,10 @@ export class AccountBaselineStack extends cdk.Stack { super(scope, id, props); const trailName = "seahaven-org-trail"; + // AWS Organizations org id (o-9kufuzz6b4). CloudTrail org trusted-access is + // already enabled on the management account; promoting this trail to an org + // trail (INFRA-73) makes it collect member-account events into this bucket. + const orgId = "o-9kufuzz6b4"; // Static trail ARN (built from name, not trail.trailArn) so the key policy // does not create a circular dependency with the Trail resource. const trailArn = cdk.Arn.format( @@ -71,6 +75,33 @@ export class AccountBaselineStack extends cdk.Stack { }, }) ); + // INFRA-73 (org trail): member accounts deliver their CloudTrail events to + // this CMK-encrypted bucket, so the CloudTrail service principal must be able + // to GenerateDataKey using each member trail's own encryption context. + // + // Cross-review BLOCK (GPT-4.1): the SourceArn must NOT be pinned to the + // management account 328440206208 — org-trail shadow trails in member + // accounts present their OWN account id in both the SourceArn and the + // encryption-context arn, so pinning to the management account would silently + // block all member-account delivery. Both are wildcarded across accounts and + // the statement is org-scoped by aws:PrincipalOrgID so only accounts in + // o-9kufuzz6b4 — not arbitrary CloudTrail principals — can use the key. + trailKey.addToResourcePolicy( + new iam.PolicyStatement({ + sid: "AllowOrgMemberCloudTrailEncrypt", + effect: iam.Effect.ALLOW, + principals: [new iam.ServicePrincipal("cloudtrail.amazonaws.com")], + actions: ["kms:GenerateDataKey*", "kms:DescribeKey"], + resources: ["*"], + conditions: { + StringEquals: { "aws:PrincipalOrgID": orgId }, + StringLike: { + "kms:EncryptionContext:aws:cloudtrail:arn": `arn:${this.partition}:cloudtrail:*:*:trail/*`, + "aws:SourceArn": `arn:${this.partition}:cloudtrail:*:*:trail/*`, + }, + }, + }) + ); trailKey.addToResourcePolicy( new iam.PolicyStatement({ sid: "AllowCloudTrailDescribeKey", @@ -132,6 +163,13 @@ export class AccountBaselineStack extends cdk.Stack { bucket: logBucket, encryptionKey: trailKey, isMultiRegionTrail: true, + // INFRA-73: promote to an organization trail. CloudTrail org + // trusted-access is already enabled on the management account; this makes + // the trail collect every member account's events into this bucket. + // Passing orgId lets the L2 construct auto-attach the AWSLogs//* + // bucket-policy PutObject statement (scoped to this trail's SourceArn). + isOrganizationTrail: true, + orgId, includeGlobalServiceEvents: true, enableFileValidation: true, sendToCloudWatchLogs: true,