🐁

AWS CDK|セキュリティスキャンツール cdk-nag に入門しました

2024/03/18に公開

昨年 CDK に入門し、しばらくして cdk-nag のことを知りました。当時から気になっていたのですが、この度社内エンジニアチーム活動時間で触れることができたので、cdk-nag の概要を入門者のアウトプットとして記録することにしました。

cdk-nag 自体は目新しいものではなく、2022 年に re:Invent でも紹介されており[1]、cdk-nag を検索すると既にさまざまな Tips が公開されています。使いこなしていきたいです。

cdk-nag とは

https://aws.amazon.com/jp/blogs/news/manage-application-security-and-compliance-with-the-aws-cloud-development-kit-and-cdk-nag/

cdk-nag (cfn_nag により影響) は、与えられたスコープ内のコンストラクトの状態が、与えられたルール群に準拠しているかどうかを検証します。さらに、cdk-nag は、ルール抑制とコンプライアンスレポートシステムを提供します。

影響元になっている cfn_nag とは、CloudFormation のテンプレートにセキュリティリスクのある構成が含まれていないかチェックする OSS ツールです。コマンドを打つと、指定のルールセットに従って CloudFormation のテンプレートをスキャンするもののようです。

https://github.com/stelligent/cfn_nag

cdk-nag は cfn_nag からヒントを得て作成された AWS CDK 用の OSS ツールです。定義したリソースが、予め指定したルールに沿っているかを開発中に確認することができ、デプロイする前にセキュリティ上の問題をチェックできるから嬉しい、というものになっています。

https://github.com/cdklabs/cdk-nag

AWS のパターンガイダンスにも cdk-nag の記載があるのを見つけました。AWS が提唱するベストプラクティスを、開発中のコードベースでチェックしていくことができるのはとても便利です。
https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/patterns/check-aws-cdk-applications-or-cloudformation-templates-for-best-practices-by-using-cdk-nag-rule-packs.html

やってみる

cdk-nag の挙動を先述のリンク AWS Cloud Development Kit と cdk-nag でアプリケーションのセキュリティとコンプライアンスを管理する を見ながら確認してみました。

cdk-nag なしで作ってみる

まずは cdk-nag なしに、CDK でシンプルに S3 バケットを作ってみます。

new Bucket(this, "Bucket", {
  bucketName: "cdknagstack-test-bucket",
});

作られたバケットは以下のような設定になっています。

  • サーバーアクセスのログ記録は無効
    cdk-s3-server-log-invalid
  • SSL 通信制限はなし
    cdk-s3-ssl-invalid

これらに対して、CDK で設定する場合はコードにオプションを追加するだけで OK です。

new Bucket(this, "Bucket", {
  bucketName: "cdknagstack-test-bucket",
  serverAccessLogsBucket: new s3.Bucket(this, "LogBucket"),
  serverAccessLogsPrefix: "log/",
  enforceSSL: true,
});

作られたバケットは以下のような設定になりました。

  • サーバーアクセスのログ記録を有効(保存先の bucket を設定)
    cdk-s3-server-log-valid
  • SSL 通信制限(SSL 通信設定がない操作は拒否)
    cdk-s3-ssl-valid

便利すぎる。

ただ、CDK による設定はオプショナルであるため、設定があるということに気づかず(あまり注意することなく)デプロイすることもしばしば発生します。ベストプラクティスと言われるものに沿っていない状態で作成されてしまうのです。
そこでお役立ちなのが cdk-nag ということです。華麗な登場です。

cdk-nag を試してみる

どのような動きをするか試してみます。
一旦 S3 バケットのオプションを全て外しておきます。

new Bucket(this, "Bucket", {
  bucketName: "cdknagstack-test-bucket",
});

まずは、CDK で作成するアプリケーション全体に、cdk-nag で用意されたスキャンルールを設定します。
今回は AWS Solutions というルールを設定します。ルールについては後述します。

const app = new cdk.App();
cdk.Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true })); // ルールの適用

new CdkNagStack(app, "CdkNagStack", {});

この状態で、cdk synth または cdk deploy のコマンドを叩くと、エラーが出ます。
cdk-synth-error-1

[Error at /CdkNagStack/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 /CdkNagStack/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.

= SSL 通信を要求していません、攻撃を防ぐために SSL 通信を要求すべきです。というエラー

コマンド実行による CloudFormation テンプレート作成時にスキャンを行い、適用したルールに沿っていない構成が含まれている場合は、エラーを出してくれるようになっています。
エラーが存在する場合は環境へのデプロイができないという仕組みです。

スキャン結果を見てみる

今回適用した AWS Solutions ルールにおいて、S3 に関するチェックポイントは上記の 2 つだけなのでしょうか?
cdk-nag では、作成するリソースがルールにどれだけ準拠しているのかを確認することができます。

CloudFormation テンプレートに対するスキャン結果が ./cdk.out フォルダ内に出力されています。見てみましょう。

./cdk.out/AwsSolutions-CdkNagStack-NagReport.csv
Rule ID,Resource ID,Compliance,Exception Reason,Rule Level,Rule Info
"AwsSolutions-S1","CdkNagStack/Bucket/Resource","Non-Compliant","N/A","Error","The S3 Bucket has server access logs disabled."
"AwsSolutions-S2","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 Bucket does not have public access restricted and blocked."
"AwsSolutions-S5","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 static website bucket either has an open world bucket policy or does not use a CloudFront Origin Access Identity (OAI) in the bucket policy for limited getObject and/or putObject permissions."
"AwsSolutions-S10","CdkNagStack/Bucket/Resource","Non-Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL."

各カラムで以下の情報を示しています。

No. カラム 説明
1 Rule ID ルールの識別 ID
2 Resource ID リソースの物理 ID
3 Compliance ルールに準拠しているか否かの判定
4 Exception Reason ルールを抑制した場合、その理由
5 Rule Level ルールのレベル(Error や Warn)
6 Rule Info ルールの説明

どうやら今回作成したバケットは、4 つのルール S1,S2,S5,S10 によってチェックされたようです。
No-Compliantがルールに準拠せず、エラーとして出力されたもの
Compliant がルールに準拠しており、OK となったものです。

Compliant と評価されたルールの 1 つを見てみましょう。

"AwsSolutions-S2","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 Bucket does not have public access restricted and blocked."

これは、S3 バケットに対してパブリックアクセスブロックをしましょうというルールです。
2023 年 4 月から、新しいバケットは自動的にパブリックアクセスブロックが有効になるようになっています[2] 。そのため、CDK のリソース定義で特にオプションを指定せずとも、作成されたバケットは AwsSolutions-S2 のルールに準拠しているという評価になったということです。

エラーに対応してみる

No-Compliant と評価されたエラーに対応してみましょう。
試験的に、まずは 1 つ目のエラーだけに対応してみます。1 つ目は、サーバーアクセスログが無効である、というエラーでした。ログ用のバケット設定を追加します。

new Bucket(this, "Bucket", {
  bucketName: "cdknagstack-test-bucket",
  serverAccessLogsBucket: new s3.Bucket(this, "LogBucket"), // 追加
  serverAccessLogsPrefix: "log/", // 追加
});

上記のようにして、cdk synth を叩いてみます。

cdk-synth-error-2

いっぱいエラーが出ました(増えた、、。)。
内容は全て AwsSolutions-S10: The S3 Bucket or bucket policy does not require requests to use SSL. ですね。追加されたサーバーアクセスログ用バケットに対してもエラーが出ているようです。

バケット設定で SSL 通信を強制しエラーを鎮静化します。鎮まれ。

new Bucket(this, "Bucket", {
  bucketName: "cdknagstack-test-bucket",
  serverAccessLogsBucket: new s3.Bucket(this, "LogBucket", {
    enforceSSL: true, // 追加
  }),
  serverAccessLogsPrefix: "log/",
  enforceSSL: true, // 追加
});

cdk synth をすると、先ほどまで出ていたエラーはなくなり、CloudFormation テンプレートがコンソールに表示されました。

cdk-synth-success

スキャン結果の CSV も見てみます。
全て Compliant で、ルールに準拠していることが示されました。
ログ用バケットが増えたので、先ほどよりもスキャン対象が増えています。

./cdk.out/AwsSolutions-CdkNagStack-NagReport.csv
Rule ID,Resource ID,Compliance,Exception Reason,Rule Level,Rule Info
"AwsSolutions-S1","CdkNagStack/LogBucket/Resource","Compliant","N/A","Error","The S3 Bucket has server access logs disabled."
"AwsSolutions-S2","CdkNagStack/LogBucket/Resource","Compliant","N/A","Error","The S3 Bucket does not have public access restricted and blocked."
"AwsSolutions-S5","CdkNagStack/LogBucket/Resource","Compliant","N/A","Error","The S3 static website bucket either has an open world bucket policy or does not use a CloudFront Origin Access Identity (OAI) in the bucket policy for limited getObject and/or putObject permissions."
"AwsSolutions-S10","CdkNagStack/LogBucket/Resource","Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL."
"AwsSolutions-S10","CdkNagStack/LogBucket/Policy/Resource","Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL."
"AwsSolutions-S1","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 Bucket has server access logs disabled."
"AwsSolutions-S2","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 Bucket does not have public access restricted and blocked."
"AwsSolutions-S5","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 static website bucket either has an open world bucket policy or does not use a CloudFront Origin Access Identity (OAI) in the bucket policy for limited getObject and/or putObject permissions."
"AwsSolutions-S10","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL."
"AwsSolutions-S10","CdkNagStack/Bucket/Policy/Resource","Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL."

ルールを抑制してみる

「ルールになっているのはわかるけど、この時は適用したくないんだよな〜」という時もありますよね。
その場合に、ルールを抑制する(サプレス/suppress する)、ということができます。
Stack レベルやリソースレベルでのサプレスが可能です。

たとえば以下の場合だと、CdkNagStack 配下で作成した S3 バケットについて、AwsSolutions-S1 というルールは準拠しなくていいよ、とサプレスしていることになります。

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

    // Stack level suppression
    NagSuppressions.addStackSuppressions(this, [
      {
        id: "AwsSolutions-S1", // サプレスしたいRuleID
        reason: "No need to export logs itself.", // サプレスしたい理由
      },
    ]);

AwsSolutions-S1 は「サーバーアクセスのログ記録を無効にしないで」というルールでした。
そのルールが抑制されたということは、「サーバーアクセスのログ記録を無効にしてよい」、つまりログ用バケットを設定しなくてよいということです。

そのためバケットは、以下の設定でデプロイすることができます。

new Bucket(this, "Bucket", {
  bucketName: "cdknagstack-test-bucket",
  enforceSSL: true,
});

このようにした場合、スキャン結果はどのように表示されるのでしょうか?
確認したところ、Suppressed という結果になっていることがわかりました。
加えて、 Exception Reason カラムにコード上で定義したサプレス理由 No need to export logs itself. が出力されています。

./cdk.out/AwsSolutions-CdkNagStack-NagReport.csv
Rule ID,Resource ID,Compliance,Exception Reason,Rule Level,Rule Info
"AwsSolutions-S1","CdkNagStack/Bucket/Resource","Suppressed","No need to export logs itself.","Error","The S3 Bucket has server access logs disabled."
"AwsSolutions-S2","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 Bucket does not have public access restricted and blocked."
"AwsSolutions-S5","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 static website bucket either has an open world bucket policy or does not use a CloudFront Origin Access Identity (OAI) in the bucket policy for limited getObject and/or putObject permissions."
"AwsSolutions-S10","CdkNagStack/Bucket/Resource","Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL."
"AwsSolutions-S10","CdkNagStack/Bucket/Policy/Resource","Compliant","N/A","Error","The S3 Bucket or bucket policy does not require requests to use SSL."

また、作成された CloudFormation テンプレートのメタデータにサプレス情報が載っていることも確認できました。

./cdk.out/CdkNagStack.template.json
{
 "Metadata": {
  "cdk_nag": {
   "rules_to_suppress": [
    {
     "reason": "No need to export logs itself.",
     "id": "AwsSolutions-S1"
    }
   ]

サプレス理由として定義する reason には日本語も使うことができます[3]
「なぜこのルールが抑制されているか?」をコード上に情報として残すことができて便利ですね。

ルールを抑制しすぎると、チェックすべき項目をスルーしてしまうこともあります。
Stack レベルで抑制していいもの、リソースレベルで抑制していいものについて、プロジェクト内で整理して設定していくことができたらなと思いました。

ルール

cdk-nag の基本的な使い方はわかったので、スキャンのもととなるルールについても確認しておきたいと思います。

どのようなルールが用意されているのでしょうか?
cdk-nag リポジトリに説明があったので見てみました。

https://github.com/cdklabs/cdk-nag/blob/main/RULES.md#rules

内容を見てみる

AWS Solutions

AWS によって審査された、多くの技術的およびビジネス上の問題に対するクラウドベースのソリューションのコレクションを提供します。審査プロセスの一部として使用される多くのチェックが含まれています。

AWS Solutions の内容としては AWS ソリューションライブラリを基としており、規範的な構成をルールとして定義したもの、と理解しています。

HIPAA Security

HIPAA セキュリティ基準のサブセットへの準拠を可能にする AWS Config Rules のコレクションを提供します。

AWS Config の Conformance Pack[4]で「Operational Best Practice for HIPPA Security」を選択し、デプロイすることで適用します。

rule-config-hipaa

NIST 800-53 800-53 rev 4/5

NIST 800-53 規格のサブセットへの準拠を可能にする AWS Config Rules のコレクションを提供します

同様に、AWS Config の Conformance Pack で 「Operational Best Practice for NIST 800 53 rev 4/5」を選択し、デプロイすることで適用します。
rev 4 は米国連邦政府情報システムおよび連邦組織のためのセキュリティ管理ガイドラインとして存在していましたが、rev 5 は公共・民間両部門での使用に適し、一般的な情報システムおよび組織のためのガイドラインとなっているようです。

PCI DSS 3.2.1

PCI DSS 基準のサブセットへの準拠を可能にする AWS Config Rules のコレクションを提供します。

  • PCI DSS: Payment Card Industry Data Security Standard
  • クレジットカード会員データを安全に取り扱うセキュリティ基準のことです。
  • PCI DSS の説明について、富士通サイトのリンクを貼っておきます https://www.fujitsu.com/jp/solutions/business-technology/security/secure/pci-dss/about/

    PCI DSS(Payment Card Industry Data Security Standard)とは、カード会員情報の保護を目的として、国際ペイメントブランド 5 社(アメリカンエキスプレス、Discover、JCB、マスターカード、VISA)が共同で策定したカード情報セキュリティの国際統一基準です。

  • AWS Payment Card Industry Data Security Standard (PCI DSS) の運用のベストプラクティス https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/pci-standard.html

同様に、AWS Config の Conformance Pack で「Operational Best Practice for PCI DSS」を選択し、デプロイすることで適用します。
また、Security Hub 標準に用意されているものを有効にして、セキュリティチェックを開始することがベストプラクティスに示されています。
rule-sechub-pci

それぞれを見て

基本的には AWS Solutions の指定で問題なく、ルールに応じて AWS Config や Security Hub で必要な設定を行うことがわかりました。
機密情報を扱うシステムに関しては、特に慎重になる必要があると思いますが、AWS 側で設定と評価ができる状態が用意されているものに対して、開発サイドにもチェックできる体制が整えられていることは大変素敵だと感じました[5]。cdk-nag を開発された方々には感謝と敬意で胸がいっぱいです。

AWS Solutions に関して言えば、生成するアプリケーション構成の指標にすることはもちろん、お客様からのシステム評価項目と照合する、ということにも活用できそうです。
また、スキャン結果の CSV は、AWS が記述している「コンプライアンスレポートシステムを提供します」にあたると考えているのですが(違ったらご指摘ください)、システム評価結果として活用することを検討していくことができたらと思います。

まとめ

  • cdk-nag はルールに基づいて、セキュリティ観点から構成をチェックしてくれるスキャンツール
  • プロジェクトに応じて、適用するルールの一部を抑制することもできる
  • スキャンした結果を CSV で出力し、レポートとして活用できる
  • 抑制したルールを CloudFormation テンプレート上で確認することができる

今回は cdk-nag の概要について知ることができました。独自のルールセットを定義することもできるようなので[6]、今後使っていくなかで試していきたいと思います。

cdk-nag について調べていく中で、自分自身も何か貢献していきたいな、と思った今回でした。

おしまい

CDK ともう少しお近づきになりたい。

脚注
  1. クラスメソッドさんのブログ記事「[レポート]_Governance and security with infrastructure as code #reinvent」https://dev.classmethod.jp/articles/governance-and-security-with-infrastructure-as-code/ ↩︎

  2. 意外と最近なんだなと思いました。「事前のお知らせ: 2023 年 4 月より、Amazon S3 で、すべての新しいバケットに対して自動的に S3 パブリックアクセスブロックが有効化、アクセスコントロールリストが無効化」 https://aws.amazon.com/jp/about-aws/whats-new/2022/12/amazon-s3-automatically-enable-block-public-access-disable-access-control-lists-buckets-april-2023/ ↩︎

  3. 日本語が必要な時もあるので、とてもありがたやと思います。https://github.com/cdklabs/cdk-nag/pull/1000 ↩︎

  4. AWS Config ルールと修復アクションの集まりであり、アカウントやリージョン、または AWS Organizations の組織全体に 1 つのエンティティとして簡単にデプロイできるもの。https://docs.aws.amazon.com/ja_jp/config/latest/developerguide/conformance-packs.html ↩︎

  5. (もっと良い言い回しをしたかったのですがこれが精一杯でした。) ↩︎

  6. NRI Netcom さんの記事が詳しそうでした。参照させていただきます。https://tech.nri-net.com/entry/cdk_nag_myrule ↩︎

NCDCエンジニアブログ

Discussion