[アップデート] AWS CDKで合成時にAWS CloudFormation Guardによるコンプライアンスチェックができるようになりました (ベータ版)

2023.04.28

AWS CDKでもAWS CloudFormation Guardを使いたいな

こんにちは、のんピ(@non____97)です。

皆さんはAWS CDKでもAWS CloudFormation Guard(以降CFn Guard)を使いたいなと思ったことはありますか? 私はあります。

AWS CDKで作成されるリソースのコンプライアンスチェックと言えばcdk-nagもありますが、既にCloudFormationで構築している場合は、CFn Guardのルールを使いまわしたい場面もあると思います。

CFn Guardの説明は以下記事をご覧ください。

今回、ベータ版ですがAWS CDKで合成時にCFn Guardによるコンプライアンスチェックができるようになりました。

GitHubを確認するとCDK CFN Guard Validator Pluginをインストールして使うようですね。

試したみたので紹介します。

いきなりまとめ

  • AWS CDKで合成時にコンプライアンスチェックができる
  • 2023/4/28時点ではベータ版
  • AWS CDKのApps単位で設定
  • デフォルトではControl Towerのプロアクティブコントロールの一部がバンドルされている
  • ルール単位で無効化することが可能
    • 個別にリソース単位で抑制することはできなさそう
  • 独自ルールを定義することも可能

やってみた

インストール

CDK CFN Guard Validator PluginのREADMEを確認しながら動作確認してみます。

まず、CDK CFN Guard Validator Pluginをインストールします。簡単ですね。

$ npm i -D @cdklabs/cdk-validator-cfnguard

up to date, audited 346 packages in 471ms

33 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

package.jsonは以下の通りです。

{
  "name": "cdk",
  "version": "0.1.0",
  "bin": {
    "cdk": "bin/cdk.js"
  },
  "scripts": {
    "build": "tsc",
    "watch": "tsc -w",
    "test": "jest",
    "cdk": "cdk"
  },
  "devDependencies": {
    "@cdklabs/cdk-validator-cfnguard": "^0.0.24",
    "@types/jest": "^29.4.0",
    "@types/node": "18.14.6",
    "aws-cdk": "2.76.0",
    "jest": "^29.5.0",
    "ts-jest": "^29.0.5",
    "ts-node": "^10.9.1",
    "typescript": "~4.9.5"
  },
  "dependencies": {
    "aws-cdk-lib": "2.76.0",
    "constructs": "^10.0.0",
    "source-map-support": "^0.5.21"
  }
}

ルールにマッチした場合の挙動の確認

インストール後、CDK CFN Guard Validator Pluginを使いたいAppsのプロパティpolicyValidationBeta1new CfnGuardValidator()を指定します。

./bin/cdk.ts

#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { CdkStack } from "../lib/cdk-stack";
import { CfnGuardValidator } from "@cdklabs/cdk-validator-cfnguard";

const app = new cdk.App({
  policyValidationBeta1: [new CfnGuardValidator()],
});
new CdkStack(app, "CdkStack");

AWS CDKのAPI Referenceを確認するとIPolicyValidationPluginBeta1というインターフェイスが追加されていました。併せてチェックしておくと良いでしょう。

デフォルトでバンドルされているルールはControl Towerのプロアクティブコントロールの一部です。どんなルールがあるかはnode_modules配下から確認できます。

$ ls -R node_modules/@cdklabs/cdk-validator-cfnguard/rules/control-tower/cfn-guard/
ec2/                         identityandaccessmanagement/ rds/                         s3/

node_modules/@cdklabs/cdk-validator-cfnguard/rules/control-tower/cfn-guard//ec2:
ct-ec2-pr-1.guard   ct-ec2-pr-11.guard  ct-ec2-pr-2.guard   ct-ec2-pr-4.guard   ct-ec2-pr-6.guard   ct-ec2-pr-8.guard
ct-ec2-pr-10.guard  ct-ec2-pr-12.guard  ct-ec2-pr-3.guard   ct-ec2-pr-5.guard   ct-ec2-pr-7.guard   ct-ec2-pr-9.guard

node_modules/@cdklabs/cdk-validator-cfnguard/rules/control-tower/cfn-guard//identityandaccessmanagement:
ct-iam-pr-2.guard

node_modules/@cdklabs/cdk-validator-cfnguard/rules/control-tower/cfn-guard//rds:
ct-rds-pr-1.guard   ct-rds-pr-12.guard  ct-rds-pr-15.guard  ct-rds-pr-18.guard  ct-rds-pr-20.guard  ct-rds-pr-23.guard  ct-rds-pr-4.guard   ct-rds-pr-7.guard
ct-rds-pr-10.guard  ct-rds-pr-13.guard  ct-rds-pr-16.guard  ct-rds-pr-19.guard  ct-rds-pr-21.guard  ct-rds-pr-24.guard  ct-rds-pr-5.guard   ct-rds-pr-8.guard
ct-rds-pr-11.guard  ct-rds-pr-14.guard  ct-rds-pr-17.guard  ct-rds-pr-2.guard   ct-rds-pr-22.guard  ct-rds-pr-3.guard   ct-rds-pr-6.guard   ct-rds-pr-9.guard

node_modules/@cdklabs/cdk-validator-cfnguard/rules/control-tower/cfn-guard//s3:
ct-s3-pr-1.guard  ct-s3-pr-2.guard  ct-s3-pr-3.guard  ct-s3-pr-4.guard  ct-s3-pr-5.guard  ct-s3-pr-6.guard  ct-s3-pr-7.guard  ct-s3-pr-8.guard

今回はCT.IAM.PR.2にマッチするようなIAMポリシーを持つIAMロールを定義します。

./lib/cdk-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const role = new cdk.aws_iam.Role(this, "Iam Role", {
      assumedBy: new cdk.aws_iam.ServicePrincipal("ec2.amazonaws.com"),
      managedPolicies: [
        new cdk.aws_iam.ManagedPolicy(this, "Iam Policy", {
          statements: [
            new cdk.aws_iam.PolicyStatement({
              effect: cdk.aws_iam.Effect.ALLOW,
              actions: ["*"],
              resources: ["*"],
            }),
          ],
        }),
      ],
    });
  }
}

この状態でcdk synthをすると以下のように怒られました。

$ npx cdk synth --no-version-reporting --no-path-metadata
Performing Policy Validations

Validation Report
-----------------

╔════════════════════════════════════╗
║           Plugin Report            ║
║   Plugin: cdk-validator-cfnguard   ║
║   Version: N/A                     ║
║   Status: failure                  ║
╚════════════════════════════════════╝


(Violations)

iam_managed_policy_no_statements_with_admin_access_check (1 occurrences)

  Occurrences:

    - Construct Path: CdkStack/Iam Policy/Resource
    - Template Path: cdk.out/CdkStack.template.json
    - Creation Stack:
	└──  CdkStack (CdkStack)
	     │ Construct: aws-cdk-lib.Stack
	     │ Library Version: 2.76.0
	     │ Location: Run with '--debug' to include location info
	     └──  Iam Policy (CdkStack/Iam Policy)
	          │ Construct: aws-cdk-lib.aws_iam.ManagedPolicy
	          │ Library Version: 2.76.0
	          │ Location: Run with '--debug' to include location info
	          └──  ImportedIam Policy (CdkStack/Iam Policy/ImportedIam Policy)
	               │ Construct: aws-cdk-lib.Resource
	               │ Library Version: 2.76.0
	               │ Location: Run with '--debug' to include location info
    - Resource ID: IamPolicy22E02181
    - Template Locations:
      > /Resources/IamPolicy22E02181/Properties/PolicyDocument/Statement/0/Resource

  Description: [CT.IAM.PR.2]: Require that AWS Identity and Access Management (IAM) customer-managed policies do not contain a statement that includes "*" in the Action and Resource elements
  How to fix: [FIX]: Remove AWS IAM inline policy statements with "Effect": "Allow" that permit "Action": "*" over "Resource": "*".
  Rule Metadata:
	DocumentationUrl: https://docs.aws.amazon.com/controltower/latest/userguide/identityandaccessmanagement-rules.html#ct-iam-pr-2-description

Policy Validation Report Summary

╔════════════════════════╤═════════╗
║ Plugin                 │ Status  ║
╟────────────────────────┼─────────╢
║ cdk-validator-cfnguard │ failure ║
╚════════════════════════╧═════════╝

Validation failed. See the validation report above for details

Subprocess exited with error 1

どのリソースが、どのルールにマッチしてステータスがfailureになったことが分かりますね。併せてドキュメントへのリンクや修正方法も紹介してくれて良きです。

ルールにマッチしなかった場合の挙動の確認

ルールにマッチしなかった場合も確認します。

Actionsを*からec2:*に変更します。

./lib/cdk-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const role = new cdk.aws_iam.Role(this, "Iam Role", {
      assumedBy: new cdk.aws_iam.ServicePrincipal("ec2.amazonaws.com"),
      managedPolicies: [
        new cdk.aws_iam.ManagedPolicy(this, "Iam Policy", {
          statements: [
            new cdk.aws_iam.PolicyStatement({
              effect: cdk.aws_iam.Effect.ALLOW,
              actions: ["ec2:*"],
              resources: ["*"],
            }),
          ],
        }),
      ],
    });
  }
}

この状態でcdk synthをすると以下のようにcdk-validator-cfnguardsuccessとなりました。

$ npx cdk synth --no-version-reporting --no-path-metadata
Performing Policy Validations

Validation Report
-----------------

Policy Validation Report Summary

╔════════════════════════╤═════════╗
║ Plugin                 │ Status  ║
╟────────────────────────┼─────────╢
║ cdk-validator-cfnguard │ success ║
╚════════════════════════╧═════════╝

Policy Validation Successful!
Resources:
  IamPolicy22E02181:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: ec2:*
            Effect: Allow
            Resource: "*"
        Version: "2012-10-17"
      Description: ""
      Path: /
  IamRoleB5F0CB96:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - Ref: IamPolicy22E02181
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
  CheckBootstrapVersion:
    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.

複数マッチするルールがあった場合

複数マッチするルールがあった場合にどんな見え方をするのかも確認します。

CT.EC2.PR.3CT.EC2.PR.4にマッチするようなセキュリティグループを2つ作成します。

./lib/cdk-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const role = new cdk.aws_iam.Role(this, "Iam Role", {
      assumedBy: new cdk.aws_iam.ServicePrincipal("ec2.amazonaws.com"),
      managedPolicies: [
        new cdk.aws_iam.ManagedPolicy(this, "Iam Policy", {
          statements: [
            new cdk.aws_iam.PolicyStatement({
              effect: cdk.aws_iam.Effect.ALLOW,
              actions: ["*"],
              resources: ["*"],
            }),
          ],
        }),
      ],
    });

    const vpc = new cdk.aws_ec2.Vpc(this, "Vpc", {
      ipAddresses: cdk.aws_ec2.IpAddresses.cidr("10.1.1.0/24"),
      natGateways: 0,
      maxAzs: 1,
      subnetConfiguration: [
        {
          name: "Public",
          subnetType: cdk.aws_ec2.SubnetType.PUBLIC,
          cidrMask: 27,
          mapPublicIpOnLaunch: false,
        },
      ],
    });

    const sg1 = new cdk.aws_ec2.SecurityGroup(this, "Sg1", {
      vpc,
    });
    sg1.addIngressRule(
      cdk.aws_ec2.Peer.anyIpv4(),
      cdk.aws_ec2.Port.allTraffic()
    );

    const sg2 = new cdk.aws_ec2.SecurityGroup(this, "Sg2", {
      vpc,
    });
    sg2.addIngressRule(
      cdk.aws_ec2.Peer.anyIpv4(),
      cdk.aws_ec2.Port.allTraffic()
    );
  }
}

この状態でcdk synthをすると以下のように怒られました。

$ npx cdk synth --no-version-reporting --no-path-metadata
Performing Policy Validations

Validation Report
-----------------

╔════════════════════════════════════╗
║           Plugin Report            ║
║   Plugin: cdk-validator-cfnguard   ║
║   Version: N/A                     ║
║   Status: failure                  ║
╚════════════════════════════════════╝


(Violations)

vpc_sg_open_only_to_authorized_ports_check (2 occurrences)

  Occurrences:

    - Construct Path: CdkStack/Sg1/Resource
    - Template Path: cdk.out/CdkStack.template.json
    - Creation Stack:
	└──  CdkStack (CdkStack)
	     │ Construct: aws-cdk-lib.Stack
	     │ Library Version: 2.76.0
	     │ Location: Object.<anonymous> (/<ディレクトリパス>/cdk/bin/cdk.ts:10:1)
	     └──  Sg1 (CdkStack/Sg1)
	          │ Construct: aws-cdk-lib.aws_ec2.SecurityGroup
	          │ Library Version: 2.76.0
	          │ Location: new CdkStack (/<ディレクトリパス>/cdk/lib/cdk-stack.ts:37:17)
	          └──  Resource (CdkStack/Sg1/Resource)
	               │ Construct: aws-cdk-lib.aws_ec2.CfnSecurityGroup
	               │ Library Version: 2.76.0
	               │ Location: new SecurityGroup (/<ディレクトリパス>/cdk/node_modules/aws-cdk-lib/aws-ec2/lib/security-group.ts:515:26)
    - Resource ID: Sg1CAF6452B
    - Template Locations:
      > /Resources/Sg1CAF6452B/Properties/SecurityGroupIngress/0/IpProtocol

    - Construct Path: CdkStack/Sg2/Resource
    - Template Path: cdk.out/CdkStack.template.json
    - Creation Stack:
	└──  CdkStack (CdkStack)
	     │ Construct: aws-cdk-lib.Stack
	     │ Library Version: 2.76.0
	     │ Location: Object.<anonymous> (/<ディレクトリパス>/cdk/bin/cdk.ts:10:1)
	     └──  Sg1 (CdkStack/Sg1)
	          │ Construct: aws-cdk-lib.aws_ec2.SecurityGroup
	          │ Library Version: 2.76.0
	          │ Location: new CdkStack (/<ディレクトリパス>/cdk/lib/cdk-stack.ts:37:17)
	          └──  Resource (CdkStack/Sg1/Resource)
	               │ Construct: aws-cdk-lib.aws_ec2.CfnSecurityGroup
	               │ Library Version: 2.76.0
	               │ Location: new SecurityGroup (/<ディレクトリパス>/cdk/node_modules/aws-cdk-lib/aws-ec2/lib/security-group.ts:515:26)
    - Resource ID: Sg25F6699F6
    - Template Locations:
      > /Resources/Sg25F6699F6/Properties/SecurityGroupIngress/0/IpProtocol

  Description: [CT.EC2.PR.3]: Require an Amazon EC2 security group to allow incoming traffic on authorized ports only
  How to fix: [FIX]: Ensure that security groups with ingress rules that allow TCP or UDP traffic from '0.0.0.0/0' or ' only allow traffic from ports 80 or 443.
  Rule Metadata:
	DocumentationUrl: https://docs.aws.amazon.com/controltower/latest/userguide/ec2-rules.html#ct-ec2-pr-3-description

vpc_sg_restricted_common_ports_check (2 occurrences)

  Occurrences:

    - Construct Path: CdkStack/Sg1/Resource
    - Template Path: cdk.out/CdkStack.template.json
    - Creation Stack:
	└──  CdkStack (CdkStack)
	     │ Construct: aws-cdk-lib.Stack
	     │ Library Version: 2.76.0
	     │ Location: Object.<anonymous> (/<ディレクトリパス>/cdk/bin/cdk.ts:10:1)
	     └──  Sg1 (CdkStack/Sg1)
	          │ Construct: aws-cdk-lib.aws_ec2.SecurityGroup
	          │ Library Version: 2.76.0
	          │ Location: new CdkStack (/<ディレクトリパス>/cdk/lib/cdk-stack.ts:37:17)
	          └──  Resource (CdkStack/Sg1/Resource)
	               │ Construct: aws-cdk-lib.aws_ec2.CfnSecurityGroup
	               │ Library Version: 2.76.0
	               │ Location: new SecurityGroup (/<ディレクトリパス>/cdk/node_modules/aws-cdk-lib/aws-ec2/lib/security-group.ts:515:26)
    - Resource ID: Sg1CAF6452B
    - Template Locations:
      > /Resources/Sg1CAF6452B/Properties/SecurityGroupIngress/0/IpProtocol

    - Construct Path: CdkStack/Sg2/Resource
    - Template Path: cdk.out/CdkStack.template.json
    - Creation Stack:
	└──  CdkStack (CdkStack)
	     │ Construct: aws-cdk-lib.Stack
	     │ Library Version: 2.76.0
	     │ Location: Object.<anonymous> (/<ディレクトリパス>/cdk/bin/cdk.ts:10:1)
	     └──  Sg1 (CdkStack/Sg1)
	          │ Construct: aws-cdk-lib.aws_ec2.SecurityGroup
	          │ Library Version: 2.76.0
	          │ Location: new CdkStack (/<ディレクトリパス>/cdk/lib/cdk-stack.ts:37:17)
	          └──  Resource (CdkStack/Sg1/Resource)
	               │ Construct: aws-cdk-lib.aws_ec2.CfnSecurityGroup
	               │ Library Version: 2.76.0
	               │ Location: new SecurityGroup (/<ディレクトリパス>/cdk/node_modules/aws-cdk-lib/aws-ec2/lib/security-group.ts:515:26)
    - Resource ID: Sg25F6699F6
    - Template Locations:
      > /Resources/Sg25F6699F6/Properties/SecurityGroupIngress/0/IpProtocol

  Description: [CT.EC2.PR.4]: Require that an Amazon EC2 security group does not allow incoming traffic for high-risk ports
  How to fix: [FIX]: Remove Amazon EC2 security group ingress rules that allow traffic from '0.0.0.0/0' or '::/0' to high risk ports: '3389', '20', '23', '110', '143', '3306', '8080', '1433', '9200', '9300', '25', '445', '135', '21', '1434', '4333', '5432', '5500', '5601', '22', '3000', '5000', '8088', '8888'.
  Rule Metadata:
	DocumentationUrl: https://docs.aws.amazon.com/controltower/latest/userguide/ec2-rules.html#ct-ec2-pr-4-description

iam_managed_policy_no_statements_with_admin_access_check (1 occurrences)

  Occurrences:

    - Construct Path: CdkStack/Iam Policy/Resource
    - Template Path: cdk.out/CdkStack.template.json
    - Creation Stack:
	└──  CdkStack (CdkStack)
	     │ Construct: aws-cdk-lib.Stack
	     │ Library Version: 2.76.0
	     │ Location: Object.<anonymous> (/<ディレクトリパス>/cdk/bin/cdk.ts:10:1)
	     └──  Sg1 (CdkStack/Sg1)
	          │ Construct: aws-cdk-lib.aws_ec2.SecurityGroup
	          │ Library Version: 2.76.0
	          │ Location: new CdkStack (/<ディレクトリパス>/cdk/lib/cdk-stack.ts:37:17)
	          └──  Resource (CdkStack/Sg1/Resource)
	               │ Construct: aws-cdk-lib.aws_ec2.CfnSecurityGroup
	               │ Library Version: 2.76.0
	               │ Location: new SecurityGroup (/<ディレクトリパス>/cdk/node_modules/aws-cdk-lib/aws-ec2/lib/security-group.ts:515:26)
    - Resource ID: IamPolicy22E02181
    - Template Locations:
      > /Resources/IamPolicy22E02181/Properties/PolicyDocument/Statement/0/Resource

  Description: [CT.IAM.PR.2]: Require that AWS Identity and Access Management (IAM) customer-managed policies do not contain a statement that includes "*" in the Action and Resource elements
  How to fix: [FIX]: Remove AWS IAM inline policy statements with "Effect": "Allow" that permit "Action": "*" over "Resource": "*".
  Rule Metadata:
	DocumentationUrl: https://docs.aws.amazon.com/controltower/latest/userguide/identityandaccessmanagement-rules.html#ct-iam-pr-2-description

Policy Validation Report Summary

╔════════════════════════╤═════════╗
║ Plugin                 │ Status  ║
╟────────────────────────┼─────────╢
║ cdk-validator-cfnguard │ failure ║
╚════════════════════════╧═════════╝

Validation failed. See the validation report above for details

Subprocess exited with error 1

全リソースそれぞれに検出結果が出力されていますね。

ただしiam_managed_policy_no_statements_with_admin_access_checkの結果にも関わらず、Creation Stackのツリーで表示されているリソースがセキュリティグループであったりとちょっと怪しさもあります。ベータ版なので割り切ります。

一部のルールを無効化する

一部のルールを無効化することができます。

その場合はdisabledRulesでルール名を指定します。なお、スタック単位やリソース単位での抑制は出来なさそうなので注意が必要です。

今回はCT.EC2.PR.3CT.EC2.PR.4のルールを無効化します。

./bin/cdk.ts

#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { CdkStack } from "../lib/cdk-stack";
import { CfnGuardValidator } from "@cdklabs/cdk-validator-cfnguard";

const app = new cdk.App({
  policyValidationBeta1: [
    new CfnGuardValidator({ disabledRules: ["ct-ec2-pr-3", "ct-ec2-pr-4"] }),
  ],
});
new CdkStack(app, "CdkStack");

この状態でcdk synthをすると以下のように1件だけ出力されるようになりました。

$ npx cdk synth --no-version-reporting --no-path-metadata
Performing Policy Validations

Validation Report
-----------------

╔════════════════════════════════════╗
║           Plugin Report            ║
║   Plugin: cdk-validator-cfnguard   ║
║   Version: N/A                     ║
║   Status: failure                  ║
╚════════════════════════════════════╝


(Violations)

iam_managed_policy_no_statements_with_admin_access_check (1 occurrences)

  Occurrences:

    - Construct Path: CdkStack/Iam Policy/Resource
    - Template Path: cdk.out/CdkStack.template.json
    - Creation Stack:
	└──  CdkStack (CdkStack)
	     │ Construct: aws-cdk-lib.Stack
	     │ Library Version: 2.76.0
	     │ Location: Run with '--debug' to include location info
	     └──  Iam Policy (CdkStack/Iam Policy)
	          │ Construct: aws-cdk-lib.aws_iam.ManagedPolicy
	          │ Library Version: 2.76.0
	          │ Location: Run with '--debug' to include location info
	          └──  ImportedIam Policy (CdkStack/Iam Policy/ImportedIam Policy)
	               │ Construct: aws-cdk-lib.Resource
	               │ Library Version: 2.76.0
	               │ Location: Run with '--debug' to include location info
    - Resource ID: IamPolicy22E02181
    - Template Locations:
      > /Resources/IamPolicy22E02181/Properties/PolicyDocument/Statement/0/Resource

  Description: [CT.IAM.PR.2]: Require that AWS Identity and Access Management (IAM) customer-managed policies do not contain a statement that includes "*" in the Action and Resource elements
  How to fix: [FIX]: Remove AWS IAM inline policy statements with "Effect": "Allow" that permit "Action": "*" over "Resource": "*".
  Rule Metadata:
	DocumentationUrl: https://docs.aws.amazon.com/controltower/latest/userguide/identityandaccessmanagement-rules.html#ct-iam-pr-2-description

Policy Validation Report Summary

╔════════════════════════╤═════════╗
║ Plugin                 │ Status  ║
╟────────────────────────┼─────────╢
║ cdk-validator-cfnguard │ failure ║
╚════════════════════════╧═════════╝

Validation failed. See the validation report above for details

Subprocess exited with error 1

Control Towerのルールを全て無効化する

Control Towerのルールを全て無効化したい場合もあると思います。その場合は、controlTowerRulesEnabled: falseを指定します。

./bin/cdk.ts

#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { CdkStack } from "../lib/cdk-stack";
import { CfnGuardValidator } from "@cdklabs/cdk-validator-cfnguard";

const app = new cdk.App({
  policyValidationBeta1: [
    new CfnGuardValidator({
      disabledRules: ["ct-ec2-pr-3", "ct-ec2-pr-4"],
      controlTowerRulesEnabled: false,
    }),
  ],
});
new CdkStack(app, "CdkStack");

この状態でcdk synthをすると以下のようにcdk-validator-cfnguardsuccessとなりました。

$ npx cdk synth --no-version-reporting --no-path-metadata
Performing Policy Validations

Validation Report
-----------------

Policy Validation Report Summary

╔════════════════════════╤═════════╗
║ Plugin                 │ Status  ║
╟────────────────────────┼─────────╢
║ cdk-validator-cfnguard │ success ║
╚════════════════════════╧═════════╝

Policy Validation Successful!
Resources:
  IamPolicy22E02181:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      PolicyDocument:
        Statement:
          - Action: "*"
            Effect: Allow
            Resource: "*"
        Version: "2012-10-17"
      Description: ""
      Path: /
  IamRoleB5F0CB96:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
        Version: "2012-10-17"
      ManagedPolicyArns:
        - Ref: IamPolicy22E02181
  Vpc8378EB38:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.1.1.0/24
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: CdkStack/Vpc
  VpcPublicSubnet1Subnet5C2D37C4:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      CidrBlock: 10.1.1.0/27
      MapPublicIpOnLaunch: false
      Tags:
        - Key: aws-cdk:subnet-name
          Value: Public
        - Key: aws-cdk:subnet-type
          Value: Public
        - Key: Name
          Value: CdkStack/Vpc/PublicSubnet1
  VpcPublicSubnet1RouteTable6C95E38E:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: CdkStack/Vpc/PublicSubnet1
  VpcPublicSubnet1RouteTableAssociation97140677:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPublicSubnet1RouteTable6C95E38E
      SubnetId:
        Ref: VpcPublicSubnet1Subnet5C2D37C4
  VpcPublicSubnet1DefaultRoute3DA9E72A:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPublicSubnet1RouteTable6C95E38E
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VpcIGWD7BA715C
    DependsOn:
      - VpcVPCGWBF912B6E
  VpcIGWD7BA715C:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: CdkStack/Vpc
  VpcVPCGWBF912B6E:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      InternetGatewayId:
        Ref: VpcIGWD7BA715C
  Sg1CAF6452B:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: CdkStack/Sg1
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: from 0.0.0.0/0:ALL TRAFFIC
          IpProtocol: "-1"
      VpcId:
        Ref: Vpc8378EB38
  Sg25F6699F6:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: CdkStack/Sg2
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: from 0.0.0.0/0:ALL TRAFFIC
          IpProtocol: "-1"
      VpcId:
        Ref: Vpc8378EB38
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
  CheckBootstrapVersion:
    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.

独自ルールの追加

独自ルールを追加することも可能です。

今回はデフォルトでバンドルされていないCT.IAM.PR.3を追加してみます。

まず、ルールを定義したファイルを作成します。

./lib/rule/ct-iam-pr-3.guard

# ###################################
##       Rule Specification        ##
#####################################
# 
# Rule Name:
#   iam_managed_policy_no_statements_with_full_access_check
# 
# Description:
#   This control checks that AWS Identity and Access Management (IAM) customer-managed policies do not contain statements of "Effect": "Allow" with "Action": "Service:*" (for example, s3:*) for individual AWS services, and that the policies do not use the combination of "NotAction" with an "Effect" of "Allow".
# 
# Reports on:
#   AWS::IAM::ManagedPolicy
# 
# Rule Parameters:
#   None
# 
# Scenarios:
#   Scenario: 1
#     Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document
#       And: The input document does not contain any IAM Managed Policy resources
#      Then: SKIP
#   Scenario: 2
#     Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document
#       And: The input document contains an IAM Managed Policy resource
#       And: The policy has no statements with 'Effect' set to 'Allow'
#      Then: SKIP
#   Scenario: 3
#     Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document
#       And: The input document contains an IAM Managed Policy resource
#       And: The policy has a statement with 'Effect' set to 'Allow'
#       And: The policy has one or more 'Action' statements
#       And: At least one 'Action' statement allows full access to a service ('Action' has a value 'service:*')
#      Then: FAIL
#   Scenario: 4
#     Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document
#       And: The input document contains an IAM Managed Policy resource
#       And: The policy has a statement with 'Effect' set to 'Allow'
#       And: The policy has one or more 'NotAction' statements
#      Then: FAIL
#   Scenario: 5
#     Given: The input document is an AWS CloudFormation or AWS CloudFormation hook document
#       And: The input document contains an IAM Managed Policy resource
#       And: The policy has a statement with 'Effect' set to 'Allow'
#       And: The policy has one or more 'Action' statements
#       And: No 'Action' statements allow full access to a service ('Action' does not have a value 'service:*')
#      Then: PASS

#
# Constants
#
let AWS_IAM_MANAGED_POLICY_TYPE = "AWS::IAM::ManagedPolicy"
let WILDCARD_ACTION_PATTERN = /^[\w]*[:]*\*$/
let INPUT_DOCUMENT = this

#
# Assignments
#
let iam_managed_policies = Resources.*[ Type == %AWS_IAM_MANAGED_POLICY_TYPE ]

#
# Primary Rules
#
rule iam_managed_policy_no_statements_with_full_access_check when is_cfn_template(%INPUT_DOCUMENT)
                                                                  %iam_managed_policies not empty {
    check(%iam_managed_policies.Properties)
        <<
        [CT.IAM.PR.3]: Require that AWS Identity and Access Management (IAM) customer-managed policies do not have wildcard service actions
            [FIX]: Remove statements from IAM customer-managed policies with "Effect": "Allow" and "Action": "service:*" or "Effect": "Allow" and "NotAction".
        >>
}

rule iam_managed_policy_no_statements_with_full_access_check when is_cfn_hook(%INPUT_DOCUMENT, %AWS_IAM_MANAGED_POLICY_TYPE) {
    check(%INPUT_DOCUMENT.%AWS_IAM_MANAGED_POLICY_TYPE.resourceProperties)
        <<
        [CT.IAM.PR.3]: Require that AWS Identity and Access Management (IAM) customer-managed policies do not have wildcard service actions
            [FIX]: Remove statements from IAM customer-managed policies with "Effect": "Allow" and "Action": "service:*" or "Effect": "Allow" and "NotAction".
        >>
}

#
# Parameterized Rules
#
rule check(policy) {
    %policy [
        filter_policy_document_with_statement_provided(this)
    ] {
        PolicyDocument {
            check_statement_no_wildcard_actions(Statement)
            check_statement_no_not_action(Statement)
       }
    }
}

rule check_statement_no_wildcard_actions(statement) {
    %statement [
        filter_allow_on_action(this)
    ] {
        Action exists
        check_no_wildcard_action(Action)
    }
}

rule check_statement_no_not_action(statement) {
    %statement [
        filter_allow(this)
    ] {
        NotAction not exists
    }
}

rule filter_allow_on_action(statement) {
    %statement {
        Effect == "Allow"
        Action exists
    }
}

rule filter_allow(statement) {
    %statement {
        Effect == "Allow"
    }
}

rule filter_policy_document_with_statement_provided(policy) {
    %policy {
        PolicyDocument exists
        PolicyDocument is_struct
        PolicyDocument {
            Statement exists
            filter_statement_non_empty_list(Statement) or
            Statement is_struct
        }
    }
}

rule filter_statement_non_empty_list(statement) {
    %statement {
        this is_list
        this not empty
    }
}

rule check_no_wildcard_action(actions) {
    %actions[*] {
        this != %WILDCARD_ACTION_PATTERN
    }
}

#
# Utility Rules
#
rule is_cfn_template(doc) {
    %doc {
        AWSTemplateFormatVersion exists  or
        Resources exists
    }
}

rule is_cfn_hook(doc, RESOURCE_TYPE) {
    %doc.%RESOURCE_TYPE.resourceProperties exists
}

作成したファイルを読み込むようにCfnGuardValidatorを設定します。

./bin/cdk.ts

#!/usr/bin/env node
import "source-map-support/register";
import * as cdk from "aws-cdk-lib";
import { CdkStack } from "../lib/cdk-stack";
import { CfnGuardValidator } from "@cdklabs/cdk-validator-cfnguard";
import * as path from "path";

const app = new cdk.App({
  policyValidationBeta1: [
    new CfnGuardValidator({
      disabledRules: ["ct-ec2-pr-3", "ct-ec2-pr-4"],
      controlTowerRulesEnabled: false,
      rules: [path.join(__dirname, "../lib/rule/ct-iam-pr-3.guard")],
    }),
  ],
});
new CdkStack(app, "CdkStack");

この状態でcdk synthをすると以下のように怒られました。

$ npx cdk synth --no-version-reporting --no-path-metadata
Performing Policy Validations

Validation Report
-----------------

╔════════════════════════════════════╗
║           Plugin Report            ║
║   Plugin: cdk-validator-cfnguard   ║
║   Version: N/A                     ║
║   Status: failure                  ║
╚════════════════════════════════════╝


(Violations)

iam_managed_policy_no_statements_with_full_access_check (1 occurrences)

  Occurrences:

    - Construct Path: CdkStack/Iam Policy/Resource
    - Template Path: cdk.out/CdkStack.template.json
    - Creation Stack:
	└──  CdkStack (CdkStack)
	     │ Construct: aws-cdk-lib.Stack
	     │ Library Version: 2.76.0
	     │ Location: Run with '--debug' to include location info
	     └──  Iam Policy (CdkStack/Iam Policy)
	          │ Construct: aws-cdk-lib.aws_iam.ManagedPolicy
	          │ Library Version: 2.76.0
	          │ Location: Run with '--debug' to include location info
	          └──  ImportedIam Policy (CdkStack/Iam Policy/ImportedIam Policy)
	               │ Construct: aws-cdk-lib.Resource
	               │ Library Version: 2.76.0
	               │ Location: Run with '--debug' to include location info
    - Resource ID: IamPolicy22E02181
    - Template Locations:
      > /Resources/IamPolicy22E02181/Properties/PolicyDocument/Statement/0/Action

  Description: [CT.IAM.PR.3]: Require that AWS Identity and Access Management (IAM) customer-managed policies do not have wildcard service actions
  How to fix: [FIX]: Remove statements from IAM customer-managed policies with "Effect": "Allow" and "Action": "service:*" or "Effect": "Allow" and "NotAction".
  Rule Metadata:
	DocumentationUrl: https://docs.aws.amazon.com/controltower/latest/userguide/rule-rules.html#ct-iam-pr-3-description

Policy Validation Report Summary

╔════════════════════════╤═════════╗
║ Plugin                 │ Status  ║
╟────────────────────────┼─────────╢
║ cdk-validator-cfnguard │ failure ║
╚════════════════════════╧═════════╝

Validation failed. See the validation report above for details

Subprocess exited with error 1

良い感じで検出されましたね。

CloudFormation Guardを既に使っていた時に役立ちそう

2023/4/28時点ではベータ版ですが、AWS CDKで合成時にAWS CloudFormation GuardによるコンプライアンスチェックができるようになるCDK CFN Guard Validator Pluginを紹介しました。

CFn Guardを既に使われている方は非常に役立ちそうですね。

ルールファイルの作成方法など詳細なお作法はAWS公式ドキュメントをウォッチしておくと良いでしょう。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!