パイプラインに組み込むCloudFormation Guard

CloudFormation Guard(cfn-guard)とは?

CloudFormation(CFn)のテンプレートが組織のルールに準拠しているかチェックすることができるCLIツールです(オープンソースとしてGitHubで公開)。例えば、EBSボリュームは暗号化設定されていないとNGというルールがあれば、CFnテンプレート内のEBSボリュームで暗号化設定が有効になっているかチェック、なっていなければ、どの設定がどうあるべきなのかを表示します。2020年6月にプレビューとして公開されましたが、10/1にGAとなりました。
aws.amazon.com
github.com

他のCFnテンプレートのチェックツールとの関係は、利用者が定義した正しさ(各組織のルールとして正しいとされていること)に従ってチェックできるところと理解しています。

f:id:imiky:20201006234955p:plain

なぜCFnテンプレートをチェックしたいか

当然、統制・セキュリティの観点でルールに沿っていないリソースを作らせないようにするためという観点はあります。デプロイしてから準拠していないことに気づいて作り直しとかになると非常に面倒なので、なるべく早期に気づけた方が開発者としてもうれしいと思います。また、何らかルールに沿わないリソースを作ったせいでインシデントが起こったときに、トリプルチェックで丹精込めて手作りしろ!のような嘘みたいな地獄に陥らないために...
アプリケーションコードは静的テストをする。インフラ構築もパイプライン化することが一般的になった今日このごろ、じゃあパイプラインの中で同じようにインフラ設定もチェックしたらええやんというのが、今回お話したいcfn-guardの活用方法です。

cfn-guardの利用イメージ

チェックされるCFnテンプレート(ebs_volume_template.yml ※一部抜粋)が、以下だとします。100 GBのEBSボリュームです。

AWSTemplateFormatVersion: '2010-09-09'
Resources:
  NewVolume:
    Type: AWS::EC2::Volume
    Properties:
      Size: 100
      Encrypted: false
      AvailabilityZone: us-east-1a

こうあるべきという設定をルールセット(ebs_volume_template.ruleset)として定義します。上の2行は変数定義です。ルールを2つ記載しています。この組織では、EBSボリュームは東京リージョンのAZ-aかAZ-cに作ること、EBSボリュームは暗号化をすることというルールがあるようです。

let encryption_flag = true
let allowed_azs = [ap-northeast-1a, ap-northeast-1c]

AWS::EC2::Volume AvailabilityZone IN %allowed_azs
AWS::EC2::Volume Encrypted == %encryption_flag

ルールは独自記法ですが、CFnテンプレートから [Type] [Property] operator [Value] と書けばいいだけなので難しくないと思います。operatorはcfn-guardのREADMEにまとめられています。

これらをcfn-guardに入力してあげると、テンプレートがルールに沿っているか、そっていなければ何がダメで、どのような設定であるべきかを表示します。

$> cfn-guard -t ebs_volume_template.yml -r ebs_volume_template.ruleset
 "[NewVolume] failed because [Encrypted] is [false] and the permitted value is [true]"
 "[NewVolume] failed because [us-east-1a] is not in [ap-northeast-1a, ap-northeast-1c] for [AvailabilityZone]"
 Number of failures: 2

暗号化設定がfalseになってるけど、正しいのはtrueやで。AZはus-east-1aはあかん、ap-northeast-1a, cやないとあかんでと教えてくれています。


機能をざっくり言うと以上です。

パイプラインに組み込んでみる

やりたいことはCFnテンプレートをCodePipelineでデプロイしている前提で、パイプライン内にcfn-guardの関所を設けます。
f:id:imiky:20201007002728p:plain
ソースリポジトリから流れてきたテンプレートをcfn-guardでチェックし、cfn-guardがNGを返したら(テンプレートがルールに沿っていない場合)パイプラインを中断、OKなら(テンプレートがルールに準拠している場合)後続のステージに進むという仕掛けをします。この仕掛け+必ずデプロイパイプラインからしかリソースをデプロイできないように作っておけば、必ず、cfn-guardのルールセットにしたがったリソースしか作成されない状態を維持できます。

今回はcfn-guardに同梱されているLambda版cfn-guard「CloudFormation Guard Lambda(cfn-guard-lambda)」をパイプラインに組み込んでみたいと思います。

パイプラインに組み込むにあたって、cfn-guardの特徴を押さえておきます。

  1. 1回の呼び出しでは、テンプレート1つとルールセット1つを入力として呼び出す(複数のテンプレートを一気にチェックはできない)
  2. CFn テンプレートをネストしている場合、よしなに紐解いてチェックはしてくれない

このような特徴があるため、ネストされていて複数のテンプレートがある場合でも、全てのテンプレートがチェックされるようにする必要があります。
そこで、Step Functionsを活用して全てのテンプレートがチェックされるように構成してみます。
※2020年5月にCodePipelineからStep Functionsが呼び出せるようになりました(これを試してみたかった感もある)
aws.amazon.com

Step Functionsのステートマシンを以下のように組みます。
f:id:imiky:20201007005325p:plain
 先頭のLambdaが、ソースリポジトリから流れてきた全てのCFnテンプレートをチェック対象としてリスト化して返します。ここでStep Functionsの便利機能、動的並列処理を活用します。動的並列処理では、渡されたリストの要素分だけ、その後のLambdaを増殖させ、リストの要素を並列処理させることができます。つまり、従来、Lambda→SQS→Lambdaで実装していたような構成をマネージドに作ってくれます。動的並列処理の中で1 Lambda、1 テンプレートでcfn-guard-lambdaを実行、後続のLambdaでOK/NGを確認し、Step Functionsを異常終了させる(=テンプレートに違反があった)かどうかを判定します。これにより、Step Functionsが異常終了するかどうかでcfn-guardがチェックOKだったかどうかが分かります。

最後に、パイプラインでこのStep Functionsを呼び出すステージを追加してあげれば、やりたいことの実現です!

おわりに

とりあえず使ってみたレベルの雑記でしたが、cfn-guardの使い所のヒントになれば幸いです。