NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    cdk-nagを使用したAWS CDKのセキュリティチェック ~独自ルール作成~

    こんにちは、上野です。

    前回の続きです。前回はcdk-nagの基本的な使い方を紹介しました。

    AWS Solutionsのルール群を見て思ったのは、少し厳しすぎる内容も多いかも?という点です。特に既存環境にcdk-nagを導入した場合、多くのErrorを検知するかもしれません。多くのErrorを検知するとその抑制対応に時間も取られることになり、抑制分のコード量も増えてしまいます。

    そこで今回は、任意のルールをピックアップしてオリジナルのルール群を作成する方法を試してみます。

    独自のルール群を作成する

    cdk-nagが提供しているルール群は、NagPackという仕組みが使われており、同じ仕組みを使用して独自のルール群を作成できます。GithubのNagPack.mdでも紹介されています。

    前回とは異なる新規CDKアプリケーションを作成します。cdk-nagもinstallしておきます。

    $ mkdir cdk-nag-myrule && cd cdk-nag-myrule
    $ cdk init app --language typescript
    $ npm install cdk-nag
    

    独自ルールを作成してきます。 今回は私の独断で以下のルールをピックアップしてみました。ルールの一覧はGithubのrulesで確認できます。

    ルール名 概要
    EC2RestrictedInbound Security Groupの公開禁止
    S3BucketPublicWriteProhibited S3バケットの書き込み公開禁止
    RDSInstancePublicAccess RDSのパブリックインスタンス禁止
    ECROpenAccess ECRリポジトリの公開禁止
    S3BucketServerSideEncryptionEnabled S3のデフォルト暗号化チェック
    EC2EBSVolumeEncrypted EBSのデフォルト暗号化チェック
    RDSStorageEncrypted RDSストレージのデフォルト暗号化チェック
    VPCFlowLogsEnabled VPC Flow Logs有効チェック
    CloudFrontDistributionAccessLogging CloudFrontアクセスログ有効チェック
    ELBLoggingEnabled ELBのログ有効チェック
    IAMNoWildcardPermissions IAMポリシーのワイルドカード禁止

    実装していきます。bin 配下にmyrule-checks.tsというファイルを新規作成します。 ファイル内容は以下のとおりです。infoやexplainなどの表示文言は、AWS Solutionsの内容を参考にしました。
    ※長いので初期表示は折りたたんでいます。

    作成したルールがチェックされるよう、bin/cdk-nag-myrule.tsに以下のように追加しておきます。

    #!/usr/bin/env node
    import 'source-map-support/register';
    import * as cdk from 'aws-cdk-lib';
    import { CdkNagMyruleStack } from '../lib/cdk-nag-myrule-stack';
    import { MyruleChecks } from './myrule-checks' //追加
    
    const app = new cdk.App();
    new CdkNagMyruleStack(app, 'CdkNagMyruleStack', {});
    cdk.Aspects.of(app).add(new MyruleChecks()); //追加
    

    この状態で、lib/cdk-nag-myrule-stack.tsにリソースを定義していきます。

    import { Size, Stack, StackProps } from 'aws-cdk-lib';
    import { Construct } from 'constructs';
    import * as ec2 from 'aws-cdk-lib/aws-ec2';
    import * as iam from 'aws-cdk-lib/aws-iam';
    import * as s3  from 'aws-cdk-lib/aws-s3'
    
    export class CdkNagMyruleStack extends Stack {
      constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);
    
        const vpc = new ec2.Vpc(this, 'vpc', {
          natGateways: 0,
          subnetConfiguration: [
            {
              cidrMask: 24,
              name: 'public',
              subnetType: ec2.SubnetType.PUBLIC,
            },
          ]
        })
    
        const securityGroup = new ec2.SecurityGroup(this, 'securityGroup', {
          vpc,
          allowAllOutbound: true,
        })
        securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'SSH Open!!')
    
        const role = new iam.Role(this, 'role', {
          assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com')
        })
        role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'))
        role.addToPolicy(
          new iam.PolicyStatement({
            actions: ['s3:*'],
            resources:['*'],
        }))
    
        const instance = new ec2.Instance(this, 'instance', {
          vpc,
          instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MICRO),
          machineImage: new ec2.AmazonLinuxImage({
            generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
            cpuType: ec2.AmazonLinuxCpuType.ARM_64,
          }),
          role: role,
        })
    
        const bucket = new s3.Bucket(this, 'bucket')
    
      }
    }
    

    構成は以下の通りです。すべてのルールを検知させるわけではないですが、VPCがFlow Logs無し、SGがSSH公開、バケットが暗号化無し、IAMロールがs3:*という強い権限を持つ、といったいくつかルール違反の設定があります。

    この状態でcdk deploy (もしくはcdk synth)を実行すると、以下のように検知します。

    [Error at /CdkNagMyruleStack/vpc/Resource] Myrule-VPCFlowLogsEnabled: The VPC does not have an associated Flow Log.
    
    [Error at /CdkNagMyruleStack/securityGroup/Resource] Myrule-EC2RestrictedInbound: The Security Group allows for 0.0.0.0/0 or ::/0 inbound access.
    
    [Error at /CdkNagMyruleStack/role/DefaultPolicy/Resource] Myrule-IAMNoWildcardPermissions[Action::s3:*]: The IAM entity contains wildcard permissions and does not have a cdk-nag rule suppression with evidence for those permission.
    
    [Error at /CdkNagMyruleStack/role/DefaultPolicy/Resource] Myrule-IAMNoWildcardPermissions[Resource::*]: The IAM entity contains wildcard permissions and does not have a cdk-nag rule suppression with evidence for those permission.
    
    [Error at /CdkNagMyruleStack/bucket/Resource] Myrule-S3BucketPublicWriteProhibited: The S3 Bucket does not have public write access restricted and blocked.
    
    [Error at /CdkNagMyruleStack/bucket/Resource] Myrule-S3BucketServerSideEncryptionEnabled: The S3 Bucket does not default encryption enabled.
    
    
    Found errors
    

    自分が選んで設定したルールだけ検知できました。意図的な設定でエラーを消したい場合は抑制(Suppressing)すればOKです。詳細は前回の記事に書いています。

    ここでやってみて気づいたのですが、「EBSのデフォルト暗号化チェック」がEC2インスタンスに対してできていません。EBS Volumeを個別に定義した場合に、暗号化オプションをチェックする仕組みになっており、EC2インスタンスの定義として設定されたEBSボリュームはチェックされませんでした。(=暗号化無しでもErrorにならない)

    ということでこの部分について独自のルールを作成してみます。

    独自ルールを作成する

    先ほどのコードでは、rulesから好きなものを選んで書いた程度でした。

    NagPackと既存のルールの書き方を参考に、独自ルールを追加します。

    bin/myrule-checks.tsに書いていきます。

    まず上部に以下のように追加します。

    import { CfnResource, Stack } from 'aws-cdk-lib';
    import { IConstruct } from 'constructs';
    import {
        NagMessageLevel,
        NagPack,
        NagPackProps,
        NagRuleCompliance, //追加
        NagRuleResult, //追加
        NagRules, //追加
        rules,
      } from 'cdk-nag';
    import { CfnInstance } from 'aws-cdk-lib/aws-ec2'; //追加
    

    既存のルールをたくさん書いているところに、1つルールを以下のように追加します。

            //独自ルール EBS暗号化チェック
            this.applyRule({
              ruleSuffixOverride: 'EBSVolumeEncryptedWithEC2',
              info: 'The EBS volume with the instance has encryption disabled.',
              explanation:
                "EBS encryption uses KMS keys when creating encrypted volumes and snapshots. This helps protect data at rest.",
              level: NagMessageLevel.ERROR,
              rule: function (node2: CfnResource): NagRuleResult {
                if (node2 instanceof CfnInstance) {
                  const blockDevices = Stack.of(node2).resolve(node2.blockDeviceMappings);
                  if (blockDevices != undefined) {
                    //Iterate through blockDevices checking if encryption is not enabled
                    for (const blockDevice of blockDevices) {
                      const resolvedDevice = Stack.of(node).resolve(blockDevice);
                      const encryption = NagRules.resolveIfPrimitive(
                        node,
                        resolvedDevice.ebs.encrypted
                      );
                      if (encryption != true) {
                        return NagRuleCompliance.NON_COMPLIANT;
                      }
                    }
                    return NagRuleCompliance.COMPLIANT;
                  }
                  return NagRuleCompliance.NON_COMPLIANT;
                } else {
                  return NagRuleCompliance.NOT_APPLICABLE;
                }
              },
              node: node,
            }); 
    

    ※あくまでサンプルとして参考程度に書いているコードです。動作を保証するものではないのでご認識ください。

    中身としては、EC2内で定義されたブロックデバイス一覧にそれぞれ暗号化設定(encrypted)が入っているか確認し、入ってないものが1つでもあればNON_COMPLIANTになるようにしています。

    この状態で、再度cdk deployを実行してみます。すると先ほどは無かったEBS暗号化が無効になっている旨のメッセージが表示されます。(他のメッセージは省略)

    [Error at /CdkNagMyruleStack/instance/Resource] Myrule-EBSVolumeEncryptedWithEC2: The EBS volume with the instance has encryption disabled.
    

    以下のようにEC2の定義にblockDevicesおよびencrypted: trueを定義して意図的に暗号化設定すればエラーは解消します。

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

    独自ルールの検証は以上です。

    まとめ

    独自のルール群と、ルールそのものも独自で作成してみました。ここまで自由度が高ければ、ワークロードごとの独自のルールや設計チェックに活用できそうです。独自ルールについては、あまり作りこみすぎても管理が大変なので、基本は用意されているルールを使えば良いかなと思いました。必要最低限のルールから始めて、導入すすめていきたいですね。

    執筆者上野史瑛

    Japan APN Ambassador
    AWSを中心としたクラウドの導入、最適化を専門に行っています。