電通総研 テックブログ

電通総研が運営する技術ブログ

AWS Security Hub のアラートを減らすためのCDK実装例

こんにちは。X(クロス)イノベーション本部 ソフトウェアデザインセンター セキュリティグループの耿です。これは電通国際情報サービス Advent Calendar 2022 12/14の記事です。

プログラミング言語クラウドインフラをIaC化できるライブラリとして、AWS Cloud Development Kit (CDK)が使われることが増えています。CDKではリソースのデフォルト値が設定され、便利に短く記述できる一方で、デフォルト値が必ずしも推奨されるセキュリティ構成になっていない場合があります。Security Hubのセキュリティ基準を利用していると、リソースをデプロイした後にアラートに気付いて修正をすることもあるのではないでしょうか。

そこでこの記事では、Security Hubのセキュリティ基準でアラートを出さないためのCDKによるリソースの記述方法を、主要なサービスの種類ごとにまとめました。CDKでリソースを作成するときに、どのような設定を気にした方が良いのか、Security Hubアラートとのマッピングとして使っていただけると幸いです。

なお、矛盾しているように聞こえるかもしれませんが、Security Hubのチェックルールは全てクリアすること自体を目的とするものではないと考えています。AWSも全てのルールをクリアすることを期待していないでしょう。例えばS3バケットについては、アクセスログを出力するようにチェックするルールがありますが(S3.9)、アクセスログを格納するバケットアクセスログは出力しないため、全くアラートが上がらない状態にはできませんね。闇雲に全てのSecurity Hubアラートをクリアしようとするのではなく、アラートからリスクを評価し、対応するかどうかを総合的に判断するプロセスが必要です。「知らずに設定していなかった」ではなく、「知った上で設定するかどうか判断する」状態にするために、Security Hubを活用するのが良いと思います。

CDKとSecurity Hub

おことわり

  • セキュリティ基準として、記事執筆時点の AWS Foundational Security Best Practices を対象とします
  • AWS Foundational Security Best Practices では、30以上のAWSサービスについてのコントロール(チェックルール)がありますが、この記事ではよく使われるサービスで、CDKで作成することが多いと思われる13種のサービスのみを取り上げます
  • 全てのコントロールを網羅していません。CDKのデフォルト設定で対応できていないコントロールを中心に扱います。また可用性に関わるコントロールや、要件に応じて特別な構成が必要なコントロールは除外しているものがあります
  • 設定自体の意味、設定することによる影響は割愛しているため、公式ドキュメントなどでご確認ください
  • 設定することによって追加でかかる費用については、記事内では言及していません
  • 記載しているコードは実装方法の一つの例であり、他にも実装方法がある場合があります
  • コード例は分かりやすさのため、関連する最小限のリソースプロパティのみ記載しています
  • CDKで対応できる範囲、かつSecurity Hubのセキュリティ基準の範囲のセキュリティ対策であるため、当然ながらこれらの設定だけをすれば他にセキュリティ対策が不要というわけではありません
  • CDK v2.50.0 で検証しています。今後のアップデートにより実装方法が変更になったり、デフォルトの挙動が変わる可能性があります。
  • AWSドキュメントでは一部不自然な日本語訳があるため、わかりやすさのため独自の日本語訳にしているコントロールがあります。

VPC

EC2.2

英語タイトル:The VPC default security group should not allow inbound and outbound traffic
日本語訳:VPC のデフォルトのセキュリティグループでは、インバウンドトラフィックとアウトバウンドトラフィックを許可しないようにする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-2

(2023/6/27更新). CDK v2.78.0より、VpcrestrictDefaultSecurityGroup プロパティが利用できるようになったため、対応が簡単になりました。

new Vpc(this, "Vpc", {
  restrictDefaultSecurityGroup: true,
});

======以下は過去の対応方法です。======

CDKでは簡単にデフォルトのセキュリティグループのルールを変更できないため、現時点ではこの投稿の通り、カスタムリソースを利用してルールを削除することになります。
まずは Vpc を継承した BaseVpc というクラスを作り(名前自由)、セキュリティグループのルールを削除するカスタムリソースを含めます。

コードを表示

import { Stack } from "aws-cdk-lib";
import { CfnVPC, Vpc, VpcProps } from "aws-cdk-lib/aws-ec2";
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";
import { Construct } from "constructs";

export class BaseVpc extends Vpc {
  constructor(scope: Construct, id: string, props: VpcProps) {
    super(scope, id, props);

    const cfnVpc = this.node.defaultChild as CfnVPC;
    const stack = Stack.of(this);

    const ingressParameters = {
      GroupId: cfnVpc.attrDefaultSecurityGroup,
      IpPermissions: [
        {
          IpProtocol: "-1",
          UserIdGroupPairs: [
            {
              GroupId: cfnVpc.attrDefaultSecurityGroup,
            },
          ],
        },
      ],
    };

    new AwsCustomResource(this, "RestrictSecurityGroupIngress", {
      onCreate: {
        service: "EC2",
        action: "revokeSecurityGroupIngress",
        parameters: ingressParameters,
        physicalResourceId: PhysicalResourceId.of(`restrict-ingress-${this.vpcId}-${cfnVpc.attrDefaultSecurityGroup}`),
      },
      onDelete: {
        service: "EC2",
        action: "authorizeSecurityGroupIngress",
        parameters: ingressParameters,
      },
      policy: AwsCustomResourcePolicy.fromSdkCalls({
        resources: [`arn:aws:ec2:${stack.region}:${stack.account}:security-group/${cfnVpc.attrDefaultSecurityGroup}`],
      }),
    });

    const egressParameters = {
      GroupId: cfnVpc.attrDefaultSecurityGroup,
      IpPermissions: [
        {
          IpProtocol: "-1",
          IpRanges: [
            {
              CidrIp: "0.0.0.0/0",
            },
          ],
        },
      ],
    };

    new AwsCustomResource(this, "RestrictSecurityGroupEgress", {
      onCreate: {
        service: "EC2",
        action: "revokeSecurityGroupEgress",
        parameters: egressParameters,
        physicalResourceId: PhysicalResourceId.of(`restrict-egress-${this.vpcId}-${cfnVpc.attrDefaultSecurityGroup}`),
      },
      onDelete: {
        service: "EC2",
        action: "authorizeSecurityGroupEgress",
        parameters: egressParameters,
      },
      policy: AwsCustomResourcePolicy.fromSdkCalls({
        resources: [`arn:aws:ec2:${stack.region}:${stack.account}:security-group/${cfnVpc.attrDefaultSecurityGroup}`],
      }),
    });
  }
}

それを Vpc の代わりに利用するだけです。

new BaseVpc(this, "Vpc", {});

======過去の対応方法ここまで======

EC2.6

英語タイトル:VPC flow logging should be enabled in all VPCs
日本語訳:すべての VPCVPC フローログ記録を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-6

VPCフローログを有効にします。以下の例ではS3バケットに出力します。

const logBucket = new s3.Bucket(this, "LogBucket", {});
const vpc = new ec2.Vpc(this, "Vpc", {});
new ec2.FlowLog(this, "VpcFlowLog", {
    resourceType: ec2.FlowLogResourceType.fromVpc(vpc),
    destination: ec2.FlowLogDestination.toS3(logBucket, "vpc/"),
    trafficType: ec2.FlowLogTrafficType.ALL,
});

EC2.18、EC2.19

英語タイトル:[EC2.18] Security groups should only allow unrestricted incoming traffic for authorized ports
日本語訳:セキュリティグループは、許可されたポートのみに対して制限されていない着信トラフィックを許可してください
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-18

英語タイトル:[EC2.19] Security groups should not allow unrestricted access to ports with high risk
日本語訳:セキュリティグループは、リスクの高いポートへの無制限アクセスを許可してはいけません
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-19

この2つは関連するコントロールであり、チェック対象となるポート番号だけが異なるので一緒に扱います。
セキュリティグループにHTTP(80)、HTTPS(443)以外のポートへのインバウンドルールを追加する時は無制限アクセス(0.0.0.0/0)を許可するのではなく、送信元IPアドレスか特定のセキュリティグループに制限します。

const sshSecurityGroup = new ec2.SecurityGroup(this, "SecurityGroup", {
    vpc,
});
const anotherSecurityGroup= new ec2.SecurityGroup(this, "AnotherSecurityGroup", {
    vpc,
});
// 許可するIPアドレスレンジを制限する
sshSecurityGroup.addIngressRule(ec2.Peer.ipv4("xx.xx.xx.xx/32"), ec2.Port.tcp(22));
// 別のセキュリティグループに制限する
sshSecurityGroup.addIngressRule(anotherSecurityGroup, ec2.Port.tcp(22));

ELB

ELB.4

英語タイトル:Application load balancers should be configured to drop HTTP headers
日本語訳:Application Load Balancer は、HTTP ヘッダーを削除するように設定する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-elb-4

dropInvalidHeaderFields プロパティ(CDK v2.47.0~)を利用し、無効なHTTPヘッダーの削除設定を有効にします。

new elbv2.ApplicationLoadBalancer(this, "Alb", {
    vpc: vpc,
    dropInvalidHeaderFields: true,
});

ELB.5

英語タイトル:Application and Classic Load Balancers logging should be enabled
日本語訳:Application Load Balancer および Classic Load Balancer のログ記録を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-elb-5

ロードバランサーのアクセスログをS3バケットに保存します。以下の例はALBの場合です。

const logBucket = new s3.Bucket(this, "LogBucket", {});
const alb = new elbv2.ApplicationLoadBalancer(this, "Alb", {
    vpc: vpc,
});
alb.logAccessLogs(logBucket, "alb");

ELB.6

英語タイトル:Application Load Balancer deletion protection should be enabled
日本語訳:Application Load Balancer で削除保護を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-elb-6

deletionProtection プロパティを利用し、削除保護を有効にします。

const alb = new elbv2.ApplicationLoadBalancer(this, "Alb", {
    vpc: vpc,
    deletionProtection: true,
});

CloudFront

CloudFront.1

英語タイトル:CloudFront distributions should have a default root object configured
日本語訳:CloudFront ディストリビューションでは、デフォルトのルートオブジェクトが設定されている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-cloudfront-1

以下の例はS3バケットがオリジンの場合です。あらかじめルートオブジェクトをアップロードした上で、defaultRootObject プロパティでオブジェクトパスを指定します。

new cloudfront.Distribution(this, "Distribution", {
    defaultBehavior: {
        origin: new cloudfrontOrigins.S3Origin(originBucket),
    },
    defaultRootObject: "index.html",
});

CloudFront.2

英語タイトル:CloudFront distributions should have origin access identity enabled
日本語訳:CloudFront ディストリビューションでは、オリジンアクセスアイデンティティを有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-cloudfront-2

CDKではCloudFrontディストリビューションのオリジンをS3バケットに指定するだけで、OAIが自動的に設定され、特別な実装は必要ありません。
なお、OAIに代わるOACについては、記事執筆時点ではCDKのL2コンストラクトでは未対応です。

new cloudfront.Distribution(this, "Distribution", {
    defaultBehavior: {
        origin: new cloudfrontOrigins.S3Origin(originBucket),
    },
});

CloudFront.3

英語タイトル:CloudFront distributions should require encryption in transit
日本語訳:CloudFront ディストリビューションでは、転送中に暗号化が必要となります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-cloudfront-3

viewerProtocolPolicyREDIRECT_TO_HTTPS もしくは HTTPS_ONLY にすることで対応できます。

new cloudfront.Distribution(this, "Distribution", {
    defaultBehavior: {
        origin: new cloudfrontOrigins.S3Origin(originBucket),
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
    },
});

CloudFront.4

英語タイトル:CloudFront distributions should have origin failover configured
日本語訳:CloudFront ディストリビューションでは、オリジンフェイルオーバーが設定されている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-cloudfront-4

フェイルオーバー用のオリジンを別途作成し、OriginGroupprimaryOriginfallbackOrigin を設定します。(「フェイルオーバー」と「フェイルバック」の用語が統一されていませんが、同じものを指していると考えて良いでしょう)

const fallbackBucket = new s3.Bucket(this, "FallbackBucket", {});
new cloudfront.Distribution(this, "Distribution", {
    defaultBehavior: {
        origin: new cloudfrontOrigins.OriginGroup({
            primaryOrigin: new cloudfrontOrigins.S3Origin(originBucket),
            fallbackOrigin: new cloudfrontOrigins.S3Origin(fallbackBucket),
        }),
    },
});

CloudFront.5

英語タイトル:CloudFront distributions should have logging enabled
日本語訳:CloudFront ディストリビューションでは、ログ記録を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-cloudfront-5

logBucketlogFilePrefix プロパティを指定し、CloudFrontのログを出力します。

const logBucket = new s3.Bucket(this, "LogBucket", {});
new cloudfront.Distribution(this, "Distribution", {
    defaultBehavior: {
        origin: new cloudfrontOrigins.S3Origin(originBucket),
    },
    logBucket: logBucket,
    logFilePrefix: "cloudfront/",
});

CloudFront.6

英語タイトル:CloudFront distributions should have AWS WAF enabled
日本語訳:CloudFront ディストリビューションでは、AWS WAF を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-cloudfront-6

AWS WAFのWeb ACLを作成し、CloudFrontディストリビューションに関連付けます。

コードを表示

const wafWebAcl = new wafv2.CfnWebACL(this, "WafV2WebAclCloudFront", {
    defaultAction: { allow: {} },
    // scopeは CLOUDFRONT とする
    scope: "CLOUDFRONT",
    visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        sampledRequestsEnabled: true,
        metricName: "WafV2WebAclCloudFront",
    },
    rules: [
        // このサンプルではルールは AWSManagedRulesCommonRuleSet のみ
        {
            name: "AWSManagedRulesCommonRuleSet",
            priority: 1,
            statement: {
                managedRuleGroupStatement: {
                    vendorName: "AWS",
                    name: "AWSManagedRulesCommonRuleSet",
                },
            },
            overrideAction: { none: {} },
            visibilityConfig: {
                cloudWatchMetricsEnabled: true,
                sampledRequestsEnabled: true,
                metricName: "AWSManagedRulesCommonRuleSet",
            },
        },
    ],
});

new cloudfront.Distribution(this, "Distribution", {
    defaultBehavior: {
        origin: new cloudfrontOrigins.S3Origin(originBucket),
    },
    webAclId: wafWebAcl.attrArn,
});

CloudFront.7、CloudFront.8

英語タイトル:[CloudFront.7] CloudFront distributions should use custom SSL/TLS certificates
日本語訳:CloudFront ディストリビューションでは、カスタム SSL/TLS 証明書を使用する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-cloudfront-7

英語タイトル:[CloudFront.8] CloudFront distributions should use SNI to serve HTTPS requests
日本語訳:CloudFront ディストリビューションでは、SNI を使用して HTTPS リクエストを処理する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-cloudfront-8

TLS証明書を発行し、CloudFront ディストリビューションに関連付けます。 こうすることで自動的にSNIも有効になります。

コードを表示

const hostedZone = route53.PublicHostedZone.fromHostedZoneAttributes(this, "MyHostedZone", {
    hostedZoneId: hostedZoneId, // ホストゾーンID
    zoneName: hostedZoneName, // ホストゾーン名
});

const cloudfrontCertificate = new certificatemanager.DnsValidatedCertificate(this, "CloudFrontCertificate", {
    domainName: "my-domain.com",
    hostedZone: hostedZone,
    validation: certificatemanager.CertificateValidation.fromDns(hostedZone),
});

new cloudfront.Distribution(this, "Distribution", {
    defaultBehavior: {
        origin: new cloudfrontOrigins.S3Origin(originBucket),
    },
    certificate: cloudfrontCertificate,
    domainNames: ["my-domain.com"],
});

EC2

EC2.3

英語タイトル:Attached EBS volumes should be encrypted at rest
日本語訳:アタッチされた EBS ボリュームは、保管時の暗号化を有効に必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-3

AWSマネージドキーでの暗号化であれば blockDevicesencrypted プロパティで実現できます(参考)。
なお、リージョンでEBSボリュームのデフォルト暗号化を設定しておくことによって、blockDevicesencrypted プロパティを明示的に指定しなくても暗号化されるようになるので楽です。

コードを表示

const ami = new ec2.AmazonLinuxImage({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
    cpuType: ec2.AmazonLinuxCpuType.X86_64,
});
new ec2.Instance(this, "Instance", {
    vpc,
    instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
    machineImage: ami,
    blockDevices: [
        {
            deviceName: "/dev/xvda",
            mappingEnabled: true,
            volume: ec2.BlockDeviceVolume.ebs(30, {
                deleteOnTermination: true,
                volumeType: ec2.EbsDeviceVolumeType.GP2,
                encrypted: true,
            }),
        },
    ],
});

EC2.8

英語タイトル:EC2 instances should use IMDSv2
日本語訳:EC2 インスタンスは IMDSv2 を使用する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ec2-8

requireImdsv2 プロパティでIMDSv2のみを有効にします。EC2インスタンスの通信内容によっては動作に問題が発生する可能性があるため、慎重な検証が必要です。

const ami = new ec2.AmazonLinuxImage({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
    cpuType: ec2.AmazonLinuxCpuType.X86_64,
});
new ec2.Instance(this, "Instance", {
    vpc,
    instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
    machineImage: ami,
    requireImdsv2: true,
});

SSM.1

英語タイトル:EC2 instances should be managed by AWS Systems Manager
日本語訳:EC2 インスタンスAWS Systems Manager により管理される必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ssm-1

SSMでEC2インスタンスを管理するためにはいくつか条件がありますが、特にハマりやすいのは以下の3点だと思います。(参考

  • インスタンスにSSM エージェントがインストールされている
  • SSMサービスへの通信ルートがある(VPCエンドポイント or インターネット経由)
    • VPCエンドポイントの場合、ssmec2messagesssmmessages の3つが必要
  • インスタンスロールが AmazonSSMManagedInstanceCore ポリシーを含んでいる

以下は SSM エージェントがインストールされているAmazon Linux 2を使用し、VPCエンドポイントでSSMサービスに接続している例です。

コードを表示

const vpc = new ec2.Vpc(this, "Vpc", {
    subnetConfiguration: [
        {
            name: "private-with-egress",
            subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
            cidrMask: 20,
        },
    ],
});
vpc.addInterfaceEndpoint("SSMEndpoint", {
    service: ec2.InterfaceVpcEndpointAwsService.SSM,
    subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});
vpc.addInterfaceEndpoint("SSMMessagesEndpoint", {
    service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
    subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});
vpc.addInterfaceEndpoint("EC2MESSAGESEndpoint", {
    service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES,
    subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
});

const ami = new ec2.AmazonLinuxImage({
    generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
    cpuType: ec2.AmazonLinuxCpuType.X86_64,
});

const role = new iam.Role(this, "Role", {
    assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com"),
    managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMManagedInstanceCore")],
});

new ec2.Instance(this, "Instance", {
    vpc,
    role,
    instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO),
    machineImage: ami,
});

ECS

ECS.5

英語タイトル:ECS containers should be limited to read-only access to root filesystems
日本語訳:ECS コンテナは、ルートファイルシステムへの読み取り専用アクセスに制限する必要があります。
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ecs-5

ECSタスク定義のコンテナ定義で、readonlyRootFilesystemtrue とすることでルートファイルシステムへのアクセスを読み取り専用にします。ただしこの設定をするとコンテナのファイルシステムに書き込むことができなくなり、ECS Execも使用できなくなります。

const taskDefinition = new ecs.FargateTaskDefinition(this, "TaskDefinition", {
    family: "my-task-definition",
    runtimePlatform: { operatingSystemFamily: ecs.OperatingSystemFamily.LINUX },
});
taskDefinition.addContainer("Container", {
    containerName: "my-container",
    image: ecs.ContainerImage.fromRegistry("my-ecr"),
    readonlyRootFilesystem: true,
});

ECS.12

英語タイトル:ECS clusters should have Container Insights enabled
日本語訳:ECS クラスターでは、Container Insights を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ecs-12

ECSクラスターの containerInsights プロパティを true にすることで、Container Insightsを有効にできます。

new ecs.Cluster(this, "Cluster", {
    vpc: vpc,
    containerInsights: true,
});

ECR

ECR.1

英語タイトル:ECR private repositories should have image scanning configured
日本語訳:ECR プライベートリポジトリでは、イメージスキャニングが設定されている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ecr-1

ECRリポジトリimageScanOnPush プロパティを true にすることで、プッシュ時の基本スキャンを有効にできます。

new ecr.Repository(this, "Repository", {
    repositoryName: "my-repository",
    imageScanOnPush: true,
});

ECR.2

英語タイトル:ECR private repositories should have tag immutability configured
日本語訳:ECR プライベートリポジトリでは、タグのイミュータビリティが設定されている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ecr-2

imageTagMutabilityIMMUTABLE にすることで、タグを変更不可にします。

new ecr.Repository(this, "Repository", {
    repositoryName: "my-repository",
    imageTagMutability: ecr.TagMutability.IMMUTABLE,
});

ECR.3

英語タイトル:ECR repositories should have at least one lifecycle policy configured
日本語訳:ECR リポジトリには、少なくとも 1 つのライフサイクルポリシーが設定されている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-ecr-3

lifecycleRules プロパティを設定することで、ECRリポジトリのライフライクルポリシーを設定します。

new ecr.Repository(this, "Repository", {
    repositoryName: "my-repository",
    lifecycleRules: [{ maxImageCount: 5 }],
});

RDS

RDS.3

英語タイトル:RDS DB instances should have encryption at rest enabled
日本語訳:RDS DB インスタンスでは、保管時の暗号化が有効になっている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-3

storageEncrypted プロパティでストレージの暗号化を有効化します。

new rds.DatabaseInstance(this, "DB", {
    vpc,
    engine: rds.DatabaseInstanceEngine.MYSQL,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    storageEncrypted: true,
});

RDS.7、RDS.8

英語タイトル:[RDS.7] RDS clusters should have deletion protection enabled
日本語訳:RDS クラスターでは、削除保護が有効になっている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-7

英語タイトル:[RDS.8] RDS DB instances should have deletion protection enabled
日本語訳:RDS DB インスタンスで、削除保護が有効になっている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-8

deletionProtectionremovalPolicy のどちらかのプロパティを使用して、削除保護を有効にします。

// Aurora
new rds.DatabaseCluster(this, "Aurora", {
    vpc,
    engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
    instanceProps: {
        vpcSubnets: {
            subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
    },
    // deletionProtection か removalPolicy のどちらかを使用する
    deletionProtection: true,
    removalPolicy: cdk.RemovalPolicy.RETAIN,
});

// Aurora以外
new rds.DatabaseInstance(this, "DB", {
    vpc,
    engine: rds.DatabaseInstanceEngine.MYSQL,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    // deletionProtection か removalPolicy のどちらかを使用する
    deletionProtection: true,
    removalPolicy: cdk.RemovalPolicy.RETAIN,
});

RDS.9

英語タイトル:Database logging should be enabled
日本語訳:データベースログ記録を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-9

MySQLPostgreSQLでルールをクリアするために有効にしなければならないログが異なります。
試した際には、MySQLでは「エラーログ」「一般ログ」「スロークエリログ」の3つを最低限出力していればルールはクリアになりました。
PostgreSQLでは「PostgreSQLログ」と「アップグレードログ」の両方を出力する必要がありました。

const mysqlParameterGroup = new rds.ParameterGroup(this, "MysqlParameterGroup", {
    engine: rds.DatabaseInstanceEngine.mysql({
        version: rds.MysqlEngineVersion.VER_8_0,
    }),
    parameters: {
        general_log: "1",
        slow_query_log: "1",
        log_output: "FILE",
    },
});

new rds.DatabaseInstance(this, "Mysql", {
    vpc,
    engine: rds.DatabaseInstanceEngine.MYSQL,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    parameterGroup: mysqlParameterGroup,
    // 監査ログも含める場合は "audit" を追加
    cloudwatchLogsExports: ["error", "general", "slowquery"],
});

const postgresParameterGroup = new rds.ParameterGroup(this, "PostgresqlParameterGroup", {
    engine: rds.DatabaseInstanceEngine.postgres({
        version: rds.PostgresEngineVersion.VER_14,
    }),
    parameters: {
        log_statement: "all",
    },
});

new rds.DatabaseInstance(this, "Postgresql", {
    vpc,
    engine: rds.DatabaseInstanceEngine.POSTGRES,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    parameterGroup: postgresParameterGroup,
    cloudwatchLogsExports: ["postgresql", "upgrade"],
});

(2023/4/19追記)ログ出力に際し、パラメータグループの設定が抜けていたため追記しました。詳細は以下。
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_LogAccess.MySQL.LogFileSize.html
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_LogAccess.Concepts.PostgreSQL.html

RDS.10、RDS.12

英語タイトル:[RDS.10] IAM authentication should be configured for RDS instances
日本語訳:IAM 認証は RDS インスタンス用に設定する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-10

英語タイトル:[RDS.12] IAM authentication should be configured for RDS clusters
日本語訳:IAM 認証は RDS クラスター用に設定する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-12

iamAuthentication プロパティを true にすることで、RDSへの接続にIAM認証を利用できます。

// Aurora
new rds.DatabaseCluster(this, "Aurora", {
    vpc,
    engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
    instanceProps: {
        vpcSubnets: {
            subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
    },
    iamAuthentication: true,
});

// Aurora以外
new rds.DatabaseInstance(this, "DB", {
    vpc,
    engine: rds.DatabaseInstanceEngine.MYSQL,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    iamAuthentication: true,
});

RDS.11

英語タイトル:Amazon RDS instances should have automatic backups enabled
日本語訳:Amazon RDS インスタンスでは、自動バックアップが有効になっている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-11

backupRetention プロパティを使用して自動バックアップを設定し、7日以上にすることでルールをクリアできます。

new rds.DatabaseInstance(this, "DB", {
    vpc,
    engine: rds.DatabaseInstanceEngine.MYSQL,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    // 7日以上にする
    backupRetention: cdk.Duration.days(7),
});

RDS.13

英語タイトル:RDS automatic minor version upgrades should be enabled
日本語訳:RDS 自動マイナーバージョンアップグレードを有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-13

CDKでRDSインスタンスを作成すると、DBエンジンのマイナーバージョンアップグレードはデフォルトで有効ですが、メンテナンスウィンドウは自動的に選択されるため、preferredMaintenanceWindow で指定すると良いでしょう。なお、UTCで記載する必要があることにご注意ください。

new rds.DatabaseInstance(this, "DB", {
    vpc,
    engine: rds.DatabaseInstanceEngine.MYSQL,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    autoMinorVersionUpgrade: true, // デフォルトでも true
    preferredMaintenanceWindow: "sat:18:00-sat:18:30", // メンテナンスウィンドウ (UTC)
});

RDS.14

英語タイトル:Amazon Aurora clusters should have backtracking enabled
日本語訳:Amazon Aurora クラスターはバックトラッキングを有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-14

Auroraクラスターの backtrackWindow プロパティでバックトラックウィンドウを指定することで、 バックトラックを有効にできます。

new rds.DatabaseCluster(this, "Aurora", {
    engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
    instanceProps: {
        vpc,
        vpcSubnets: {
            subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
    },
    backtrackWindow: cdk.Duration.hours(24),
});

RDS.23

英語タイトル:RDS databases and clusters should not use a database engine default port
日本語訳:RDS データベースとクラスターはデータベースエンジンのデフォルトポートを使用しないでください
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-23

port プロパティにより接続ポートをデフォルト値以外に変更できます。RDSインスタンス/クラスター作成後の変更は不可です。

// Aurora
new rds.DatabaseCluster(this, "Aurora", {
    engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
    instanceProps: {
        vpc,
        vpcSubnets: {
            subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
    },
    port: 13306,
});

// Aurora以外
new rds.DatabaseInstance(this, "DB", {
    vpc,
    engine: rds.DatabaseInstanceEngine.MYSQL,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    port: 13306,
});

RDS.24、RDS.25

英語タイトル:[RDS.24] RDS database clusters should use a custom administrator username
日本語訳:RDS データベースクラスターはカスタム管理者ユーザーネームを使用する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-24

英語タイトル:[RDS.25] RDS database instances should use a custom administrator username
日本語訳:RDS データベースインスタンスはカスタム管理者ユーザーネームを使用する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-rds-25

credentials プロパティで、管理者ユーザー名をデフォルト値以外に変更できます。RDSインスタンス/クラスター作成後の変更は不可です。

// Aurora
new rds.DatabaseCluster(this, "Aurora", {
    engine: rds.DatabaseClusterEngine.AURORA_MYSQL,
    instanceProps: {
        vpc,
        vpcSubnets: {
            subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
    },
    credentials: rds.Credentials.fromUsername("customName"),
});

// Aurora以外
new rds.DatabaseInstance(this, "DB", {
    vpc,
    engine: rds.DatabaseInstanceEngine.MYSQL,
    vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
    },
    credentials: rds.Credentials.fromUsername("customName"),
});

DynamoDB

DynamoDB.2

英語タイトル:DynamoDB tables should have point-in-time recovery enabled
日本語訳:DynamoDB テーブルでは、ポイントインタイムリカバリが有効になっている必要があります。
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-dynamodb-2

pointInTimeRecovery プロパティでポイントインタイムリカバリを有効にできます。

new dynamodb.Table(this, "Table", {
    partitionKey: { name: "pk", type: dynamodb.AttributeType.STRING },
    sortKey: { name: "sk", type: dynamodb.AttributeType.STRING },
    pointInTimeRecovery: true,
});

S3

S3.4

英語タイトル:S3 buckets should have server-side encryption enabled
日本語訳:S3 バケットでは、サーバーサイドの暗号化を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-4

以下の例はS3マネージドの鍵(SSE-S3)を使ってS3バケットサーバサイド暗号化を設定する例です。

new s3.Bucket(this, "Bucket", {
    encryption: s3.BucketEncryption.S3_MANAGED,
});

S3.5

英語タイトル:S3 buckets should require requests to use Secure Socket Layer
日本語訳:S3 バケットでは、Secure Socket Layer を使用するためのリクエストの要求が必要です
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-5

暗号化されていない通信を拒否するにはS3バケットポリシーを設定しますが、CDKでは enforceSSL プロパティで簡単にバケットポリシーを追加してくれます。

new s3.Bucket(this, "Bucket", {
    enforceSSL: true,
});

S3.8

英語タイトル:S3 Block Public Access setting should be enabled at the bucket level
日本語訳:S3 ブロックパブリックアクセス設定は、バケットレベルで有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-8

4つのブロックパブリックアクセス設定を全て有効にするには、blockPublicAccess プロパティに BLOCK_ALL を指定します。

new s3.Bucket(this, "Bucket", {
    blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
});

S3.9

英語タイトル:S3 bucket server access logging should be enabled
日本語訳:S3 バケットサーバーアクセスログ記録を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-9

serverAccessLogsBucket プロパティでS3バケットサーバーアクセスログを出力する先のS3バケットを、serverAccessLogsPrefix プロパティでプレフィックスを指定します。

const logBucket = new s3.Bucket(this, "LogBucket", {});
new s3.Bucket(this, "Bucket", {
    serverAccessLogsBucket: logBucket,
    serverAccessLogsPrefix: "prefix",
});

S3.13

英語タイトル:S3 buckets should have lifecycle policies configured
日本語訳:S3 バケットでは、ライフサイクルポリシーを設定する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-s3-13

lifecycleRules プロパティでS3バケットライフサイクルポリシーを設定します。以下の例は30日後にIAクラスにオブジェクトを移動し、90日後にオブジェクトを削除するポリシーです。

new s3.Bucket(this, "Bucket", {
    lifecycleRules: [
        {
            id: "log lifecycle",
            transitions: [
            {
                storageClass: s3.StorageClass.INFREQUENT_ACCESS,
                transitionAfter: cdk.Duration.days(30),
            },
            ],
            expiration: cdk.Duration.days(90),
        },
    ],
});

EFS

EFS.1

英語タイトル:Amazon EFS should be configured to encrypt file data at rest using AWS KMS
日本語訳:Amazon EFS は、AWS KMS を使用して保管中のファイルデータを暗号化するように設定する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-efs-1

encrypted プロパティで暗号化を設定しますが、CDK v2 ではデフォルト true なので、特別な実装は必要ありません。
(ドキュメントでは @aws-cdk/aws-efs:defaultEncryptionAtRest フィーチャーフラグを有効にしない限り encrypted のデフォルトは false である、とありますが、CDK v2ではこのフィーチャーフラグは廃止されており、有効になっているのと同じ動きになっています)

new efs.FileSystem(this, "FileSystem", {
    vpc,
    encrypted: true, // CDK v2 ではデフォルトで true
});

SNS

SNS.1

英語タイトル:SNS topics should be encrypted at rest using AWS KMS
日本語訳:SNS トピックは、AWS KMS を使用して保管時に暗号化する必要があります。
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-sns-1

次のように暗号化に利用するKMSキーを取得し、masterKey プロパティに渡すことで暗号化されます。
※トピックがCloudWatch AlarmやEventbridgeルールのターゲットの場合、デフォルトキー(alias/aws/sns)を利用できないため要注意です。
https://repost.aws/ja/knowledge-center/cloudwatch-receive-sns-for-alarm-trigger
https://repost.aws/ja/knowledge-center/sns-not-getting-eventbridge-notification

const key = kms.Key.fromLookup(this, "SNSKey", { aliasName: "alias/aws/sns" });
new sns.Topic(this, "Topic2", {
    masterKey: key,
});

SNS.2

英語タイトル:Logging of delivery status should be enabled for notification messages sent to a topic
日本語訳:トピックに送信される通知メッセージでは、配信ステータスのログ記録を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-sns-2

配信ステータスのログ記録はCloudFormationでもサポートされておらず、CDKで実装するにはカスタムリソースでSDK呼び出しをする必要があり(参考)、大変です。今後のアップデートに期待します。

SQS

SQS.1

英語タイトル:Amazon SQS queues should be encrypted at rest
日本語訳:Amazon SQS キューは保管中に暗号化する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-sqs-1

2022年10月より、新しく作成されるSQSキューはデフォルトで暗号化されるようになったため、特別な実装は不要になりました。

new sqs.Queue(this, "Queue", {
    // 指定しなくても新規作成されるキューはデフォルトで暗号化される
    encryption: sqs.QueueEncryption.SQS_MANAGED,
});

API Gateway

APIGateway.1

英語タイトル:API Gateway REST and WebSocket API logging should be enabled
日本語訳:API Gateway REST および WebSocket API ログ記録を有効にする必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-apigateway-1

CloudWatch Logs ロググループを作成し、loggingLevelaccessLogDestinationaccessLogFormat プロパティでログの出力を設定します。

const log = new logs.LogGroup(this, "ApiGatewayLog", {
    logGroupName: "api-gateway-log",
    retention: logs.RetentionDays.SIX_MONTHS,
});
new apigateway.RestApi(this, "RestApi", {
    deployOptions: {
        stageName: "dev",
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
        accessLogDestination: new apigateway.LogGroupLogDestination(log),
        accessLogFormat: apigateway.AccessLogFormat.clf(),
    },
});

APIGateway.4

英語タイトル:API Gateway should be associated with an AWS WAF web ACL
日本語訳:API Gateway は、AWS WAF ウェブ ACL に関連付けられている必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-apigateway-4

AWS WAFのWeb ACLを作成し、API Gatewayのステージに関連付けます。

コードを表示

const api = new apigateway.RestApi(this, "RestApi", {
    deployOptions: {
        stageName: "dev",
    },
});

const wafWebAcl = new wafv2.CfnWebACL(this, "WafV2WebAcl", {
    defaultAction: { allow: {} },
    // scopeは REGIONAL とする
    scope: "REGIONAL",
    visibilityConfig: {
        cloudWatchMetricsEnabled: true,
        sampledRequestsEnabled: true,
        metricName: "wafWebAcl",
    },
    rules: [
        {
            // このサンプルではルールは AWSManagedRulesCommonRuleSet のみ
            name: "AWSManagedRulesCommonRuleSet",
            priority: 1,
            statement: {
                managedRuleGroupStatement: {
                    vendorName: "AWS",
                    name: "AWSManagedRulesCommonRuleSet",
                },
            },
            overrideAction: { none: {} },
            visibilityConfig: {
                cloudWatchMetricsEnabled: true,
                sampledRequestsEnabled: true,
                metricName: "AWSManagedRulesCommonRuleSet",
            },
        },
    ],
});
const webAclAssociation = new wafv2.CfnWebACLAssociation(this, "WebAclAssociation", {
    resourceArn: `arn:aws:apigateway:${region}::/restapis/${api4.restApiId}/stages/dev`,
    webAclArn: wafWebAcl.attrArn,
});
webAclAssociation.addDependsOn(wafWebAcl);
webAclAssociation.addDependsOn(api.deploymentStage.node.defaultChild as cdk.CfnResource);

APIGateway.5

英語タイトル:API Gateway REST API cache data should be encrypted at rest
日本語訳:API Gateway REST API のキャッシュデータは、保管中に暗号化する必要があります
https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/securityhub-standards-fsbp-controls.html#fsbp-apigateway-5

cachingEnabledtrue にすることでこのルールの検出対象になり、cacheDataEncrypted プロパティでキャッシュデータの暗号化を有効にできます。

new apigateway.RestApi(this, "RestApi", {
    deployOptions: {
        stageName: "dev",
        cachingEnabled: true,
        cacheDataEncrypted: true,
    },
});

大変長い記事になりました。読んでいただき、ありがとうございました。

私たちは同じチームで働いてくれる仲間を大募集しています!たくさんのご応募をお待ちしています。

執筆:@kou.kinyo、レビュー:Ishizawa Kento (@kent)Shodoで執筆されました