いわさです。
CloudFormation の静的解析を行うことが出来るツールとして、CloudFormation Guard を使うことが出来ます。
CloudFormation Guard は CloudFormation テンプレートに限らず、Config を始め様々なポリシー評価ツールとして使うことが出来ます。
先日のアップデートで CloudFormation Guard のメジャーバージョンが更新され、これまでの 2.x.x 系から 3.x.x 系となりました。
json_parse()
をルール内で解析用に使用出来るようになるなどいくつか大きなアップデートが上記アナウンスで紹介されているのですが、cfn-guard-lambda
の新しいデプロイ方法が提供されるようにもなっていました。
cfn-guard-lambda
というのは CloudFormation Guard の機能を Lambda 経由で使えるようにする単純なラッパー関数です。
通常 CloudFormation Guard はインストールして使うツールですが事前に Lambda 関数としてデプロイしておくことで Guard の解析を Lambda 上で実行することが出来ます。
これまでセットアップ手順が少し面倒だったのですが、今回のアップデートで SAM CLI を使って一発でデプロイ出来るようになっていました。
そこで、良い機会なのでcfn-guard-lambda
の初使用を兼ねてデプロイから試してみることにしました。
デプロイ
前提として SAM CLI が導入済みである必要があります。
また、cfn-guard-lambda
は Rust 製なので SAM でビルドする場合は-use-container
オプションを使う必要があるため、Docker もセットアップ済みであることが条件です。
詳細な手順は以下に記載されています。
流れとしては、CloudFormation Guard のリポジトリをクローンするとguard-lambda
というディレクトリが含まれており、そこに SAM テンプレートが用意されているので、それをそのままデプロイするだけです。
ビルド
私の環境でビルドに 10 分程度かかりました。
# クローン
% git clone https://github.com/aws-cloudformation/cloudformation-guard.git
Cloning into 'cloudformation-guard'...
remote: Enumerating objects: 5310, done.
remote: Counting objects: 100% (1684/1684), done.
remote: Compressing objects: 100% (444/444), done.
remote: Total 5310 (delta 1440), reused 1340 (delta 1237), pack-reused 3626
Receiving objects: 100% (5310/5310), 8.59 MiB | 18.21 MiB/s, done.
Resolving deltas: 100% (3713/3713), done.
# ビルド
% cd cloudformation-guard/guard-lambda/
% sam build --use-container
Starting Build inside a container
Building codeuri: /Users/iwasa.takahito/work/hoge0704guard/cloudformation-guard runtime: provided.al2 metadata: {'BuildMethod': 'makefile'} architecture: x86_64 functions: CloudFormationGuardLambda
Fetching public.ecr.aws/sam/build-provided.al2:latest-x86_64 Docker container image.............................................................................................................................................................................................................................................................................................................................................................................................................................
Mounting /Users/iwasa.takahito/work/hoge0704guard/cloudformation-guard as /tmp/samcli/source:ro,delegated, inside runtime container
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Validate SAM template: sam validate
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {{stack-name}} --watch
[*] Deploy: sam deploy --guided
Running CustomMakeBuilder:CopySource
Running CustomMakeBuilder:MakeBuild
Current Artifacts Directory : /tmp/samcli/artifacts
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
stable-x86_64-unknown-linux-gnu installed - rustc 1.70.0 (90c541806 2023-05-31)
Rust is installed now. Great!
To get started you may need to restart your current shell.
This would reload your PATH environment variable to include
Cargo's bin directory ($HOME/.cargo/bin).
To configure your current shell, run:
source "$HOME/.cargo/env"
source /root/.cargo/env && rustup target add x86_64-unknown-linux-musl
source /root/.cargo/env && cd guard-lambda && cargo build --release --target x86_64-unknown-linux-musl
cp -r /tmp/samcli/scratch/target/x86_64-unknown-linux-musl/release/cfn-guard-lambda /tmp/samcli/artifacts/bootstrap
デプロイ
デプロイはすぐですね。
% sam deploy --guided
Configuring SAM deploy
======================
Looking for config file [samconfig.toml] : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]: hoge0704guard
AWS Region [ap-northeast-1]:
:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key CloudFormationGuardLambdaFunctionName
Description -
Value hoge0704guard-CloudFormationGuardLambda-NuOezGGmIrDS
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - hoge0704guard in ap-northeast-1
出力された関数名を使って Lambda の実行を行います。
使ってみる
今回の CloudFormation Guard 3.0 のアップデート前からcfn-guard-lambda
は使うことが出来ていましたが、使用方法を紹介したものがあまりなかったので、今回は使うところも試してみたいと思います。
次の記事を参考にサンプルルールを使って、NG のリソーステンプレートで静的解析を行い、エラーメッセージに従ってテンプレートを修正して OK の状態に治すところまで実施してみましょう。
実行方法も先ほどのデプロイ手順と同じ README に記載されています。
ポイントとしては Lambda へのペイロードで 2 つの必須パラメータを指定する点です。
ひとつめはdata
です。検査対象のテンプレートを指定します。
GitHub リポジトリのサンプルでは JSON テンプレートに対して検査していました。私は今回 YAML で試してみたのですが、どちらもサポートされています。
ふたつめはrules
です。ここでルールを指定します。
先程のブログ記事の内容にあわせて、次のような Lambda ペイロードを用意しました。
ルールは EBS の暗号化とサイズをチェックする内容になっています。
テンプレートはそのルールに反した EBS リソースを 2 つ作成するものとなっています。
hoge.json
{
"data": "Resources:\n NewVolume:\n Type: AWS::EC2::Volume\n Properties:\n Size: 500\n Encrypted: false\n AvailabilityZone: !Select\n - 0\n - Fn::GetAZs: !Ref AWS::Region\n NewVolume2:\n Type: AWS::EC2::Volume\n Properties:\n Size: 50\n Encrypted: false\n AvailabilityZone: !Select\n - 0\n - Fn::GetAZs: !Ref AWS::Region",
"rules": [
"let encryption_flag = true\n AWS::EC2::Volume Properties.Encrypted == %encryption_flag\n AWS::EC2::Volume Properties.Size <= 100"
]
}
上記ペイロードを Lambda 関数に引き渡してみます。
% aws lambda invoke --function-name hoge0704guard-CloudFormationGuardLambda-NuOezGGmIrDS --payload file://hoge.json --cli-binary-format raw-in-base64-out output.json
{
"StatusCode": 200,
"ExecutedVersion": "$LATEST"
}
出力内容を確認してみましょう。
ちょっと量が多いのでポイントのみ抜粋します。
{
"message": [
{
"context": "File(rules=1)",
"container": {
"FileCheck": {
"name": "lambda-payload",
"status": "FAIL",
"message": null
}
},
:
{
"context": "TypeBlock#AWS::EC2::Volume/0",
"container": {
"TypeBlock": "FAIL"
},
"children": [
{
"context": "GuardAccessClause#block Properties.Encrypted EQUALS %encryption_flag",
"container": {
"GuardClauseBlockCheck": {
"at_least_one_matches": false,
"status": "FAIL",
"message": null
}
},
"children": [
{
"context": " Properties.Encrypted EQUALS %encryption_flag",
"container": {
"ClauseValueCheck": {
"Comparison": {
"comparison": [
"Eq",
false
],
"from": {
"Resolved": {
"path": "/Resources/NewVolume/Properties/Encrypted",
"value": false
}
},
"to": {
"Resolved": {
"path": "",
"value": true
}
},
"message": null,
"custom_message": null,
"status": "FAIL"
}
}
},
"children": []
}
]
}
]
},
:
{
"context": "TypeBlock#AWS::EC2::Volume/0",
"container": {
"TypeBlock": "FAIL"
},
"children": [
{
"context": "GuardAccessClause#block Properties.Size LESS THAN EQUALS 100",
"container": {
"GuardClauseBlockCheck": {
"at_least_one_matches": false,
"status": "FAIL",
"message": null
}
},
"children": [
{
"context": " Properties.Size LESS THAN EQUALS 100",
"container": {
"ClauseValueCheck": {
"Comparison": {
"comparison": [
"Le",
false
],
"from": {
"Resolved": {
"path": "/Resources/NewVolume/Properties/Size",
"value": 500
}
},
"to": {
"Resolved": {
"path": "",
"value": 100
}
},
"message": null,
"custom_message": null,
"status": "FAIL"
}
}
},
"children": []
}
]
}
]
}
:
良いですね。
エラーが検出されています。
ではエラー内容に従ってテンプレートを修正してみます。
ストレージサイズを 500 GB から 50 GB に変更し、ディスク暗号化も有効にします。
hoge.json
{
"data": "Resources:\n NewVolume:\n Type: AWS::EC2::Volume\n Properties:\n Size: 50\n Encrypted: true\n AvailabilityZone: !Select\n - 0\n - Fn::GetAZs: !Ref AWS::Region\n NewVolume2:\n Type: AWS::EC2::Volume\n Properties:\n Size: 50\n Encrypted: true\n AvailabilityZone: !Select\n - 0\n - Fn::GetAZs: !Ref AWS::Region",
"rules": [
"let encryption_flag = true\n AWS::EC2::Volume Properties.Encrypted == %encryption_flag\n AWS::EC2::Volume Properties.Size <= 100"
]
}
もう一度実行してみます。
{
"message": [
{
"context": "File(rules=1)",
"container": {
"FileCheck": {
"name": "lambda-payload",
"status": "PASS",
"message": null
}
},
:
"children": [
{
"context": "Filter/Map#1",
"container": {
"Filter": "PASS"
},
"children": [
{
"context": "GuardAccessClause#block Type EQUALS \"AWS::EC2::Volume\"",
"container": {
"GuardClauseBlockCheck": {
"at_least_one_matches": false,
"status": "PASS",
"message": null
}
},
"children": [
{
"context": " Type EQUALS \"AWS::EC2::Volume\"",
"container": {
"ClauseValueCheck": "Success"
},
"children": []
}
]
}
]
},
:
{
"context": "TypeBlock#AWS::EC2::Volume/0",
"container": {
"TypeBlock": "PASS"
},
"children": [
{
"context": "GuardAccessClause#block Properties.Encrypted EQUALS %encryption_flag",
"container": {
"GuardClauseBlockCheck": {
"at_least_one_matches": false,
"status": "PASS",
"message": null
}
},
"children": [
{
"context": " Properties.Encrypted EQUALS %encryption_flag",
"container": {
"ClauseValueCheck": "Success"
},
"children": []
}
]
}
]
},
:
エラーが解消され、ステータスがPASS
になることが確認出来ました。
さいごに
本日は cfn-guard-lambda (AWS CloudFormation Guard as a Lambda) が SAM CLI で簡単にデプロイ出来るようになっていたので、初めて cfn-guard-lambda を使ってみました。
Lambda ペイロードとして色々とインプットする関係で、普通にユーザーが使う分にはツールで使ったほうがわかりやすい気がしますね。
ただ、API とかで実行したくて実行環境のセットアップが難しい場合は出番がありそうです。
導入も簡単になったので使ってみてください。