はじめに
UL Systems Advent Calendar 2020 の18日目です。
CloudFormationテンプレートでEC2を作成した際、うっかりEBSの暗号化を忘れてしまい作り直しになった経験はあるのではないでしょうか。
暗号化以外にもプロジェクトルールで決まっている設定が漏れていた。なんてこともあるのではないでしょうか。
レビューでこれらの漏れを防ぐことも重要ですが、できれば自動でチェックする仕組みが欲しいですよね?
そんな時に使えるコマンドラインツールに、AWS CloudFormation Guardがあります。
AWSによる紹介ブログはこちら
どんなものなのか、実際に確かめてみました。
実行環境構築
ローカルマシンで実行するだけでは面白くないため、CodeCommit + CodeBuildで実行できる環境にしました。
ソースプロバイダにCodeCommitを、buildspec.ymlに以下の内容を指定していしたCodeBuildプロジェクトを作成。
version: 0.2
phases:
install:
commands:
- wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/1.0.0/cfn-guard-linux-1.0.0.tar.gz
- tar -xvf cfn-guard-linux-1.0.0.tar.gz
build:
commands:
- ./cfn-guard-linux/cfn-guard --version
- ./cfn-guard-linux/cfn-guard check -t ebs_volume_template.yml -r ebs_volume_template.ruleset
How it worksにあるサンプルコードではjsonですが、yamlに置き換えたebs_volume_template.ymlとebs_volume_template.rulesetをCodeCommitにpush。
ビルドを実行し、3件のエラーが発生することを確認。
AWS::EC2::Instanceで試してみる
CFnテンプレートの作成
サンプルではEBSだが、実運用ではEC2 Instanceで作成することが多い。同様のチェックをEC2 Instanceでも試してみる。
適当なEC2を作成するCFnテンプレートを作成。
Resources:
NewInstance1:
Type: AWS::EC2::Instance
Properties:
DisableApiTermination: true
SubnetId: subnet-XXXXXXXXX
ImageId: ami-00f045aed21a55240
InstanceInitiatedShutdownBehavior: stop
InstanceType: t2.micro
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeSize: 500
VolumeType: gp2
Encrypted: false
- DeviceName: /dev/sda2
Ebs:
VolumeSize: 50
VolumeType: gp2
Encrypted: false
- DeviceName: /dev/sda3
Ebs:
VolumeSize: 10
VolumeType: gp2
KeyName: sample-key
Monitoring: false
SecurityGroupIds:
- sg-XXXXXXXXXXXXXXXXX
ルールセットの作成
1からルールセットを記載するのは面倒なので、Rulegenコマンドでの自動生成を試してみた。
> cfn-guard rulegen sample-project/ec2_template.yml
AWS::EC2::Instance BlockDeviceMappings == [{"DeviceName":"/dev/sda1","Ebs":{"Encrypted":false,"VolumeSize":500,"VolumeType":"gp2"}},{"DeviceName":"/dev/sda2","Ebs":{"Encrypted":false,"VolumeSize":50,"VolumeType":"gp2"}},{"DeviceName":"/dev/sda3","Ebs":{"VolumeSize":10,"VolumeType":"gp2"}}]
AWS::EC2::Instance DisableApiTermination == true
AWS::EC2::Instance ImageId == ami-00f045aed21a55240
AWS::EC2::Instance InstanceInitiatedShutdownBehavior == stop
AWS::EC2::Instance InstanceType == t2.micro
AWS::EC2::Instance KeyName == sample-key
AWS::EC2::Instance Monitoring == false
AWS::EC2::Instance SecurityGroupIds == ["sg-XXXXXXXXXXXXXXXXX"]
AWS::EC2::Instance SubnetId == subnet-XXXXXXXXX
ImageIdなど単純な比較となる項目は自動生成でも問題ないが、BlockDeviceMappingsなどの多階層の項目の値をチェックには使えそうにないですね。
リスト、かつ他階層の項目のため、ワイルドカードを利用し、以下のようなルールセットを作成
let encryption_flag = true
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted == %encryption_flag
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100
実行結果の確認
チェック対象のテンプレート、ルールセットが作成できたため、CodeCommitのリポジトリへpush。
チェック対象のテンプレートが変更になったため、buildspec.ymlの一部を修正。
version: 0.2
phases:
install:
commands:
- wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/1.0.0/cfn-guard-linux-1.0.0.tar.gz
- tar -xvf cfn-guard-linux-1.0.0.tar.gz
build:
commands:
- ./cfn-guard-linux/cfn-guard --version
- ./cfn-guard-linux/cfn-guard check -t ec2_template.yml -r ec2_template.ruleset
実行したところ、暗号化チェックでエラーが発生していない。
(failed because [Encrypted]~のメッセージがなく、失敗ケースが1件になっている。)
原因調査、対応
ドキュメントをもう一度確認してみたところ、Wildcard Semanticsに比較時の注意点書いてますね。
先ほどの記載ではいづれかがtrueならば結果OKとなってしまうため、全てfalseではないという条件へ修正。
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted != false
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100
実行し、Encryptedのエラーが発生することを確認。
Encryptedが指定されていないデバイスもエラーにしたい
「DeviceName: /dev/sda3」は「Encrypted」が指定されていないが、先ほどの結果ではOKと見なされている。
暗号化されていないディスクが作成されてしまうため、未指定も結果NGとなることが望ましい。
項目がない場合結果NGとなるよう--strict-checksオプションを指定するようbuildspec.ymlを修正。
version: 0.2
phases:
install:
commands:
- wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/1.0.0/cfn-guard-linux-1.0.0.tar.gz
- tar -xvf cfn-guard-linux-1.0.0.tar.gz
build:
commands:
- ./cfn-guard-linux/cfn-guard --version
- ./cfn-guard-linux/cfn-guard check -t ec2_template.yml -r ec2_template.ruleset --strict-checks
まとめ
実際に使ってみないとわからないことが多いので、とりあえず試すことは重要ですね。
また、現状ではサンプルでは1テンプレート、1rulesetになってますが、実運用を考慮した際、テンプレートごとにrulesetを作成することは現実的ではありません。
どの単位でCFnテンプレートを作成するのかにもよるが、Strict Checks用のrulesetとそれ以外のrulesetの2つで運用するのがベターなのかな?
ここら辺は運用しながら自分なりの答えを見つけたいと思います。
最後に今回作成したテンプレート、ルールセット、buildspec.ymlは以下となります。
Resources:
NewInstance1:
Type: AWS::EC2::Instance
Properties:
DisableApiTermination: true
SubnetId: subnet-XXXXXXXXX
ImageId: ami-00f045aed21a55240
InstanceInitiatedShutdownBehavior: stop
InstanceType: t2.micro
BlockDeviceMappings:
- DeviceName: /dev/sda1
Ebs:
VolumeSize: 500
VolumeType: gp2
Encrypted: false
- DeviceName: /dev/sda2
Ebs:
VolumeSize: 50
VolumeType: gp2
Encrypted: false
- DeviceName: /dev/sda3
Ebs:
VolumeSize: 10
VolumeType: gp2
KeyName: sample-key
Monitoring: false
SecurityGroupIds:
- sg-XXXXXXXXXXXXXXXXX
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.Encrypted != false
AWS::EC2::Instance BlockDeviceMappings.*.Ebs.VolumeSize <= 100
version: 0.2
phases:
install:
commands:
- wget https://github.com/aws-cloudformation/cloudformation-guard/releases/download/1.0.0/cfn-guard-linux-1.0.0.tar.gz
- tar -xvf cfn-guard-linux-1.0.0.tar.gz
build:
commands:
- ./cfn-guard-linux/cfn-guard --version
- ./cfn-guard-linux/cfn-guard check -t ec2_template.yml -r ec2_template.ruleset --strict-checks