はじめに
CDK アプリをセキュリティベストプラクティスに沿ってチェックしてくれる cdk-nag のメジャーバージョン v3 がリリースされました。
内部が大きく書き直された破壊的変更で、v2 でおなじみだった NagSuppressions(違反を例外扱いにする API)が丸ごと削除されています。
「もう例外は使えなくなったのか?」と心配になりますが、結論としては CDK 本体のネイティブ API に置き換わっただけで、例外化自体は引き続き可能でした。
本記事では同じスタックを v2 と v3 の両方でチェックにかけて、何がどう変わったのかを比較しながら確認します。
なお本記事の内容は README と MIGRATION.md をもとに、aws-cdk-lib 2.261.0 + cdk-nag 3.0.1(v2 側は 2.38.2)で動作確認しています。
v3 で何が変わったか
v2 までは cdk-nag が独自の仕組み(CDK の Aspect という、構成ツリーを巡回して各リソースを訪問する機構)でチェックを行い、違反を警告/エラーのメッセージとして出力していました。
レポートも CSV/JSON 形式で独自に出力していました。
v3 ではこの独自機構が捨てられ、CDK 本体に元々備わっている「ポリシー検証フレームワーク」のプラグインとして動くようになりました。
違反の報告形式も CDK 標準の validation-report.json に一本化され、CLI がそれを整形して表示します。
v2 と v3 で同じスタックをチェックしてみる
わざと違反だらけのスタック(ログなし S3 バケット + s3:* のワイルドカード権限を持つ IAM ユーザー)を用意し、AwsSolutions ルールパックでチェックします。
検証に使ったスタック定義(v2/v3 共通)
import { Stack, StackProps } from 'aws-cdk-lib';
import { Bucket } from 'aws-cdk-lib/aws-s3';
import { User, PolicyStatement } from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class DemoStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
// AwsSolutions-S1 (アクセスログ), S10 (SSL強制) に違反
new Bucket(this, 'DemoBucket');
// AwsSolutions-IAM5 に違反 (Action::s3:* と Resource::* の2件)
const user = new User(this, 'DemoUser');
user.addToPolicy(
new PolicyStatement({ actions: ['s3:*'], resources: ['*'] })
);
}
}
v2 の場合
v2 ではこんな感じで書いてました。
import { App, Aspects } from 'aws-cdk-lib';
import { AwsSolutionsChecks } from 'cdk-nag';
const app = new App();
new DemoStack(app, 'CdkNagV2Demo');
Aspects.of(app).add(new AwsSolutionsChecks());
synth すると違反が 4 件出て失敗します(抜粋)。
ERROR AwsSolutions-S1: The S3 Bucket has server access logs disabled.
CdkNagV2Demo/DemoBucket/Resource
ERROR AwsSolutions-IAM5[Action::s3:*]: The IAM entity contains wildcard permissions ...
ERROR AwsSolutions-IAM5[Resource::*]: The IAM entity contains wildcard permissions ...
Synthesis finished with errors
IAM5 のように 1 リソースから複数の違反が出るルールは、ルールID[検出ID] の形式で違反ごとに ID が振られます。
これを例外にしたい場合、v2 では NagSuppressions を使っていました。
import { NagSuppressions } from 'cdk-nag';
NagSuppressions.addResourceSuppressions(user, [
{
id: 'AwsSolutions-IAM5',
reason: 'Need s3:* for cross-account replication.',
appliesTo: ['Action::s3:*'], // 対象を絞る。省略すると IAM5 の違反が全部消える
},
], true);
ポイントは appliesTo を省略した場合の挙動で、そのリソースの IAM5 違反(Action::s3:* と Resource::* の 2 件)がまとめて全部消えます。
v3 の場合
同じスタックを v3 でチェックします。有効化の書き方が変わり、コンストラクタに app を渡します。
import { App, Validations } from 'aws-cdk-lib';
import { AwsSolutionsChecks } from 'cdk-nag';
const app = new App();
new DemoStack(app, 'CdkNagV3Demo');
Validations.of(app).addPlugins(new AwsSolutionsChecks(app));
検出される違反は v2 と同じ 4 件ですが、表示に「どの ID で acknowledge(例外化)すればよいか」のヒントが付くようになっています(抜粋)。
ERROR The S3 Bucket has server access logs disabled. (AwsSolutions)
CdkNagV3Demo/DemoBucket/Resource aws-cdk-lib.aws_s3.CfnBucket
Acknowledge with 'AwsSolutions::AwsSolutions-S1'
ERROR The IAM entity contains wildcard permissions ... (AwsSolutions)
CdkNagV3Demo/DemoUser/DefaultPolicy/Resource aws-cdk-lib.aws_iam.CfnPolicy
Acknowledge with 'AwsSolutions::AwsSolutions-IAM5[Action::s3:*]'
Validation failed. A copy of this report can be found in 'cdk.out/validation-report.json'
例外にする方法は cdk-nag 独自の NagSuppressions ではなく、CDK 本体の acknowledge() になりました。
import { Validations } from 'aws-cdk-lib';
// このバケットの S1 (アクセスログ) を例外にする
Validations.of(bucket).acknowledge({
id: 'AwsSolutions-S1',
reason: 'Logging not required for this bucket.',
});
// IAM5 のうち Action::s3:* だけ例外にする (Resource::* は違反のまま残る)
Validations.of(user).acknowledge({
id: 'AwsSolutions-IAM5[Action::s3:*]',
reason: 'Need s3:* for cross-account replication.',
});
書き方が変わっただけで「リソース単位で理由を添えて例外化する」という使い勝手は v2 と同じです。
スタック全体に効かせたい場合は Validations.of(stack).acknowledge(...) とスタックを渡します(v2 の addStackSuppressions() 相当)。
v2 からの移行
移行作業は機械的な置き換えが中心です。
詳細は MIGRATION.md にチェックリストがあるので、AI に読ませれば自動変換も可能な気がします。
| v2 | v3 |
|---|---|
aws-cdk-lib >= 2.176.0 |
aws-cdk-lib >= 2.257.0 |
Aspects.of(app).add(new Pack()) |
Validations.of(app).addPlugins(new Pack(app)) |
NagSuppressions.addResourceSuppressions(c, [{id, reason}]) |
Validations.of(c).acknowledge({id, reason}) |
NagSuppressions.addStackSuppressions(stack, [...]) |
Validations.of(stack).acknowledge({id, reason}) |
appliesTo: ['Action::s3:*'] |
id: 'AwsSolutions-IAM5[Action::s3:*]' |
appliesTo 省略による一括抑制 |
不可 (1 件ずつ acknowledge) |
CSV/JSON レポート (reports prop) |
validation-report.json (自動出力) |
suppressionIgnoreCondition / logIgnores
|
削除 (代替なし) |
まとめ
メジャーバージョンアップとしては「独自機構を捨てて本体側の標準に寄せる」方向の変更で、CDK のポリシー検証フレームワークに他のツールと同じ土俵で乗るようになりました。
違反の例外化(抑制)は v3 でも使えるが、API が CDK ネイティブの Validations.of().acknowledge() に置き換わり、複数の違反を出すルールは 1 件ずつの acknowledge が必要になった。