はじめに
最近AWS ConfigをCDKで有効化したのですが、少しハマったのでコードと共に記録を残しておきます
ハマったところ
ハマったのはAWS Configの仕様です。
具体的にはConfigを有効化する際にAWS::Config::ConfigurationRecorder
とAWS::Config::ConfigDeliveryChannel
を作成する必要があるのですが、これらは同時に有効化する必要があるようです。DependsOnなどでどちらかを優先すると一向に作成が終わりませんでした。
公式にはRecorderを先に作るべしという記述を見つけてDependsOnしていたので、少し気づくのに時間がかかりました。
なぜ同時に有効化する必要があるのか。APIリファレンスを見たところ、Configuration Recorderの有効化にあたって2つのAPIを実行する必要があるとわかりました。一つ目はPutConfigurationRecorder、二つ目はStartConfigurationRecorderです。なお1つ目のAPIはDeliveryChannel作成の前、二つ目は作成の後に叩く必要があります。
CDK/CloudFormation経由でConfiguration Recorderを有効化するとPutConfigurationRecorderとStartConfigurationRecorderのタイミングを制御できないので、DependsOnをどちらかにつけるとエラーになってしまうようです。
コード
というわけでCDK(Stack部分)のコードを置いておきます
import * as cdk from "aws-cdk-lib";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as iam from "aws-cdk-lib/aws-iam";
import * as config from "aws-cdk-lib/aws-config";
import { Construct } from "constructs";
export class ConfigStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const configBucket = new cdk.aws_s3.Bucket(this, "configBucket", {
blockPublicAccess: cdk.aws_s3.BlockPublicAccess.BLOCK_ALL,
encryption: cdk.aws_s3.BucketEncryption.S3_MANAGED,
enforceSSL: true,
versioned: false,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
// Add bucket policy
const bucketPolicy = new iam.PolicyStatement({
sid: "AWSConfigBucketPermissionsCheck",
effect: iam.Effect.ALLOW,
principals: [new iam.ServicePrincipal("config.amazonaws.com")],
actions: ["s3:GetBucketAcl"],
resources: [configBucket.bucketArn],
conditions: {
StringEquals: {
"AWS:SourceAccount": this.account, // Using the account ID of the stack
},
},
});
const listBucketPolicy = new iam.PolicyStatement({
sid: "AWSConfigBucketExistenceCheck",
effect: iam.Effect.ALLOW,
principals: [new iam.ServicePrincipal("config.amazonaws.com")],
actions: ["s3:ListBucket"],
resources: [configBucket.bucketArn],
conditions: {
StringEquals: {
"AWS:SourceAccount": this.account,
},
},
});
const putObjectPolicy = new iam.PolicyStatement({
sid: "AWSConfigBucketDelivery",
effect: iam.Effect.ALLOW,
principals: [new iam.ServicePrincipal("config.amazonaws.com")],
actions: ["s3:PutObject"],
resources: [`${configBucket.bucketArn}/AWSLogs/${this.account}/Config/*`],
conditions: {
StringEquals: {
"s3:x-amz-acl": "bucket-owner-full-control",
"AWS:SourceAccount": this.account,
},
},
});
// Add the policy statements to the bucket
configBucket.addToResourcePolicy(bucketPolicy);
configBucket.addToResourcePolicy(listBucketPolicy);
configBucket.addToResourcePolicy(putObjectPolicy);
const configRole = new iam.Role(this, "ConfigRole", {
assumedBy: new iam.ServicePrincipal("config.amazonaws.com"),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWS_ConfigRole"
),
],
});
const configRecorder = new config.CfnConfigurationRecorder(
this,
"ConfigRecorder",
{
name: "CloudFrontVpcOriginConfigRecorder",
roleArn: configRole.roleArn,
recordingGroup: {
allSupported: true,
includeGlobalResourceTypes: true,
},
}
);
const configDeliveryChannel = new config.CfnDeliveryChannel(
this,
"ConfigDeliveryChannel",
{
name: "CloudFrontVpcOriginConfigDeliveryChannel",
s3BucketName: configBucket.bucketName, // S3バケット名を指定
configSnapshotDeliveryProperties: {
deliveryFrequency: "TwentyFour_Hours",
},
}
);
configDeliveryChannel.node.addDependency(configBucket);
configDeliveryChannel.node.addDependency(configRole);
configRecorder.node.addDependency(configRole);
configRecorder.node.addDependency(configBucket);
}
}
おわりに
本記事が誰かのお役に立てば幸いです。