こんにちは、上野です。
前回の続きです。前回は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, }) }, ], })
独自ルールの検証は以上です。
まとめ
独自のルール群と、ルールそのものも独自で作成してみました。ここまで自由度が高ければ、ワークロードごとの独自のルールや設計チェックに活用できそうです。独自ルールについては、あまり作りこみすぎても管理が大変なので、基本は用意されているルールを使えば良いかなと思いました。必要最低限のルールから始めて、導入すすめていきたいですね。