NTTドコモR&Dの技術ブログです。

cdk-nagを用いてセキュリティルールに沿ったAWSの環境構築を行う

はじめに

みなさん、こんにちは。サービスイノベーション部2年目社員の渡邉です。 今回は、AWSの環境をCDKを用いて構築している状況において、さらにセキュリティを向上してみるというテーマで進めていきます。 セキュリティ対策の参考になれば幸いです。

課題

AWSの環境がセキュリティルールに沿っているのかを検知する方法として代表的なものは、AWS Configを用いた管理です。

これは、現状の設定などが危険なものではないかを設定時や定期的にスキャンし、アラートを上げるものです。セキュリティ管理者の観点で、今運用している環境にセキュリティ上のリスクがないかを確認するためには非常に有用です。

しかし、Configによる管理には課題があります。それは、実際に環境が作られた状態にならないと検出されないことです。環境を作成して1日待つとアラートが上がり、それに対して翌日対処する……といった流れでの対処になります。問題のある環境を一定期間作成した状態になってしまいますし、問題のある状況を意識しないまま環境作成を行い、再度適合した環境に作り直す……という手順を取る必要があります。

解決方法の1つとして、Configの修復アクション*1を用いることです。これで、検出されたのと同時に自動で修正を走らせることができ、アラートと同時に対処できている環境を実現できます。しかし、CDKなどIaCを用いた環境でこれを実行してしまうと、定義された環境とズレた環境が構築されてしまうことになるので、デプロイするたびに修正が走る状態になってしまいせっかくのIaCが台無しです。

この課題を克服するために、cdk-nagを用いて、CDKのデプロイ前に問題を検出してみましょう。

cdk-nagを使ってみる

ここからは、cdk-nagで先述の問題を克服できるかを確認していきましょう。*2

lib配下、advent-cdk-nag-stack.tsに以下のコードを記述します

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';

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

    new s3.Bucket(this, 'Bucket', {}); // S3 Bucketを作りたい
  }
}

S3 Bucketを作るだけの簡単なコードです。

cdk-nagを有効にするために、bin配下、advent-cdk-nat.tsを以下に編集します。

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AdventCdkNagStack } from '../lib/advent-cdk-nag-stack';
import { AwsSolutionsChecks } from 'cdk-nag';
import { Aspects } from 'aws-cdk-lib';

const app = new cdk.App();
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); // cdk-nagを有効化
new AdventCdkNagStack(app, 'AdventCdkNagStack', {

});

編集後、$ cdk synthを実行すると以下のエラーが確認できると思います。

[Error at /AdventCdkNagStack/Bucket/Resource] AwsSolutions-S1: The S3 Bucket has server access logs disabled. The bucket should have server access logging enabled to provide detailed records for the requests that are made to the bucket.

[Error at /AdventCdkNagStack/Bucket/Resource] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL. You can use HTTPS (TLS) to help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle or similar attacks. You should allow only encrypted connections over HTTPS (TLS) using the aws:SecureTransport condition on Amazon S3 bucket policies.

Found errors

AwsSolutions-S1はサーバーアクセスログに関するアラート、AwsSolutions-S10はSSLに関するアラートが検出されています。AwsSolutions-S1を対処してみましょう。

    const logBucket = new s3.Bucket(this, "LogBucket", {}); // ログバケットを作成
    new s3.Bucket(this, 'Bucket', {
      serverAccessLogsBucket: logBucket // ログバケットを出力先に指定
    });

この状況で、$ cdk synthを実行してみましょう。

[Error at /AdventCdkNagStack/LogBucket/Resource] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL. You can use HTTPS (TLS) to help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle or similar attacks. You should allow only encrypted connections over HTTPS (TLS) using the aws:SecureTransport condition on Amazon S3 bucket policies.

[Error at /AdventCdkNagStack/LogBucket/Policy/Resource] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL. You can use HTTPS (TLS) to help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle or similar attacks. You should allow only encrypted connections over HTTPS (TLS) using the aws:SecureTransport condition on Amazon S3 bucket policies.

[Error at /AdventCdkNagStack/Bucket/Resource] AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL. You can use HTTPS (TLS) to help prevent potential attackers from eavesdropping on or manipulating network traffic using person-in-the-middle or similar attacks. You should allow only encrypted connections over HTTPS (TLS) using the aws:SecureTransport condition on Amazon S3 bucket policies.

Found errors

AwsSolutions-S1のアラートが解消したことが確認できました。

続いて、AwsSolutions-S10のアラートの抑止を試してみましょう。

    const logBucket = new s3.Bucket(this, "LogBucket", {});
    new s3.Bucket(this, 'Bucket', {
      serverAccessLogsBucket: logBucket
    });
    NagSuppressions.addStackSuppressions(this, [{
      id: "AwsSolutions-S10", // 抑止するRuleID
      reason: "HTTPS (TLS) cannot be used." // 理由も必須
    }]);

$ cdk synthを実行すると、アラートが上がらず、synthの実行結果が表示されることが確認できます。

スタック単位やリソース単位など、粒度を指定して抑止の設定が可能です。

カスタマイズする

今回指定したAWS Solutionsでは、例えばS3では以下をチェックしています*3

  • AwsSolutions-S1: サーバアクセスログ
  • AwsSolutions-S2: パブリックアクセス
  • AwsSolutions-S5: 静的ウェブサイトホスティング時のポリシー
  • AwsSolutions-S10: SSL

(余談: EC2に関するルールはEC23,EC26,EC27と数字が振られていますがS3はS31ではなくS1と振られているのが気になります)

これ以外のルールも設定する必要がある場合があるかと思います。例えば、常にS3のデータをKMS暗号化した状態をルール化したいとします。AWS Solutionsでは定義されていませんが、HIPAA.Securityというルールセットの「S3DefaultEncryptionKMS」がそれにあたるので、このルールを追加してみましょう。既存のAwsSolutionsChecksにさらに追加する形で定義できます。

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { AdventCdkNagStack } from '../lib/advent-cdk-nag-stack';
import { AwsSolutionsChecks, NagMessageLevel, NagPack, NagPackProps, rules } from 'cdk-nag'; // 追加
import { Aspects } from 'aws-cdk-lib';
import { IConstruct } from 'constructs';

const app = new cdk.App();
Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); // cdk-nagを有効化

// cdk-nagのCustomruleを追加
export class CustomruleChecks extends NagPack {
  constructor(props?: NagPackProps) {
    super(props);
    this.packName = 'Customrule';
  }
  public visit(node: IConstruct): void {
    if (node instanceof cdk.CfnResource) {
      this.applyRule({
        info: 'The S3 Bucket is not encrypted with a KMS Key by default.',
        explanation:
          'Ensure that encryption is enabled for your Amazon Simple Storage Service (Amazon S3) buckets. Because sensitive data can exist at rest in an Amazon S3 bucket, enable encryption at rest to help protect that data',
        level: NagMessageLevel.ERROR,
        rule: rules.s3.S3DefaultEncryptionKMS,
        node: node,
      });
    }
  }
}
Aspects.of(app).add(new CustomruleChecks({ verbose: true }));

new AdventCdkNagStack(app, 'AdventCdkNagStack', {

});

$ cdk synthの実行結果

[Error at /AdventCdkNagStack/LogBucket/Resource] Customrule-S3DefaultEncryptionKMS: The S3 Bucket is not encrypted with a KMS Key by default. Ensure that encryption is enabled for your Amazon Simple Storage Service (Amazon S3) buckets. Because sensitive data can exist at rest in an Amazon S3 bucket, enable encryption at rest to help protect that data

[Error at /AdventCdkNagStack/Bucket/Resource] Customrule-S3DefaultEncryptionKMS: The S3 Bucket is not encrypted with a KMS Key by default. Ensure that encryption is enabled for your Amazon Simple Storage Service (Amazon S3) buckets. Because sensitive data can exist at rest in an Amazon S3 bucket, enable encryption at rest to help protect that data

Found errors

ルールを追加できていることが確認できました。実際にはもっと様々なルールの追加が考えられるので、別ファイルでクラスの宣言を行った方がベターでしょう。また、アラートのレベルも指定可能なので、例えばWarning,Errorなどルール化されている場合はそのルールに従って定義を行うことで、アラートに対する対処の順位付けを行うことが可能になります。

さらに詳細なcdk-nag利用方法は、GitHub - cdklabs/cdk-nag: Check CDK applications for best practices using a combination of available rule packsを参照してください。

展望

さらにcdk-nagを活用するために、少し検討をしてみます。

cdk-nagで評価される際に、cdk.outフォルダ配下に検出結果のcsvが出力されます。現在、私が携わっているプロジェクトでは、GitHub Actionsを用いてcdk deployを行っているため、その過程でこのcsvを用いた検出結果を例えばプルリクエストの際にコメントで表示させることが可能です。これにより、PRと同時に検出、レビュアーが確認する前にレビューイは問題に気づき、対処することが可能ですし、レビュアーが気付けない、といった状況も減るでしょう。

このような環境を作ることで、より堅牢なAWS環境を構築していきましょう!

cdk-nag動作の様子

*1:自動修復の設定 - AWS Config

*2:CDKを$ cdk init app --language typescriptで構築した環境で話を進めていきます。

*3:cdk-nag/RULES.md at main · cdklabs/cdk-nag · GitHub