Amazon Web Services ブログ

cfn-lint を使った AWS CloudFormation テンプレートの Git pre-commit バリデーション

AWS CloudFormation のツールはいまや黄金期をむかえています。 cfn_nagtaskcat といったツールによって、1 つのリソースをアカウントにデプロイする前にテストと検証を実行することで、コードとしてのインフラストラクチャの取り扱いが容易になりました。このブログ記事では、linter を使って CloudFormation テンプレートを検証する方法について解説していきます。

 

linter とは、コードを精査して、そのコードを実行したときにエラーを発生させる可能性のある構文エラーやバグがないかを探すプログラムのことです。スタンドアロンのツールとして実行することも可能ですが、ビルド自動化やオーサリングなどのツールに組み込まれていることが多いです。

これまで CloudFormation による構文チェックは、サービス API の ValidateTemplate アクションに限られていました。このアクションを実行すると、テンプレートが正しい形式の JSON または YAML で書かれているかどうかがわかりますが、自分で定義した実際のリソースが検証されているわけではありません。数か月前、私はより良い選択肢がないか探しました。Stelligent の cfn_nag のようなツールは、テンプレートのリソースに追加の検証を実行できますが、これはセキュリティやベストプラクティスの観点から行われるものです。さらに探しているうちに、CloudFormation のサービスチームが、リソースやプロパティに有効な構文について説明したリソース仕様を公開していることを発見しました。

AWS CloudFormation linter の紹介

同じころ、Amazon の同僚である Kevin Dejong が、Python で自ら記述した CloudFormation linter のオープンソース化を準備していました。これは、私が求めていた必須項目のほとんどをクリアしていました。

  • 組み込み関数の解析 (条件文を含む)
  • 拡張可能なルールベースのアーキテクチャ
  • 公開済みのリソース仕様に照らした検証
  • Python で記述されている (ええ、私は偏ってます)

私はこの linter の、とりわけ拡張性に強く引かれました。コミュニティのメンバーは、許容値や二者択一のリソースプロパティなど、リソース仕様の外部に存在する事柄について、新しい構文規則を追加することが可能になるからです。

当社は昨春、この linter を cfn-lint として公開して以来、その向上に取り組んでいます。これまでに、バッチファイル検証、SAM テンプレートサポート、制限チェック、そして、AWS CodePipeline や IAM ポリシーを使って作成されたパイプラインのようなリソース用のより詳細な検証などを追加してきました。linter 規則のフルリストは、GitHub のドキュメントでご覧いただけます。

linter を理解するには、とにかく自分でやってみるのが一番です。

linter のインストール

この linter は Python で記述されているので、pip を使うのが最も簡単なインストール方法です (みなさんのマシンに Python がすでにインストールされているものと仮定しています)。

pip3 install cfn-lint

linter をインストールし、オペレーティングシステムパスに組み込んだら、コマンドラインから実行できるようになります。基本の構文は以下のようになります。

cfn-lint --template simple-vpc.template.yml --region us-east-1 --ignore-checks W

上記には、linter がシンプルな VPC テンプレートを us-east-1 リージョンのリソース仕様に照らして、すべての警告ルールを無視して検証すると記されています。

ただ、この linter は、コマンドラインのみならず数多くのユースケースをサポートしています。

  1. Git リポジトリの pre-commit hook として
  2. Atom、Visual Studio Code、Sublime、IntelliJ、さらには VIM の IDE プラグインとして
  3. CI/CD パイプラインの構築ステップ、検証ステップとして

このブログでは、cfn-lint を Git の pre-commit hook としてセットアップする方法を解説します。他のユースケースについては今後の記事で取りあげる予定です。

Git リポジトリのセットアップ

まず、テンプレートに Git リポジトリが必要です。リポジトリがない場合は、以下の私の demo repo をコピーしてください。

git clone https://github.com/cmmeyer/cfnlintdemo.git

空のリポジトリを作成することもできます。

mkdir cfnlintdemo
cd cfnlintdemo
git init

cfn-lint を pre-commit フックとして設定

次に、pre-commit をインストールして Git に関連付けます。pre-commit のフレームワークに慣れていない方は、同社のウェブサイトで詳細がご覧いただけます。pre-commit は、コードレビューのためにコードを提出する前にスクリプトを実行して、コード内の単純な問題を特定する方法を提供しています。ここでは、CloudFormation テンプレートの構文を検証します。

pre-commit は、Python の pip インストーラーからインストールすることもできます。
sudo pip install pre-commit

また、pre-commit がウェブサイトで提供している Python ベースのインストーラーも使用できます。
curl https://pre-commit.com/install-local.py | python -

次に、pre-commmitcfn-lint をフックとして使用するよう指示します。これを実行するには、リポジトリのルート内に .pre-commit-config.yaml ファイルを作成します。以下は、私の cfnlintdemo リポジトリ内にあるファイルです。

# .pre-commit-config.yaml
repos:
-   repo: https://github.com/awslabs/cfn-python-lint
    rev: v0.15.0 # The version of cfn-lint to use
    hooks:
    -   id: cfn-python-lint
        files: templates/.*\.(json|yml|yaml)$

上記は、pre-commit に対して、cfn-lint のバージョン 0.15.0 (本記事の執筆時点での最新版) をインストールし、git commit のアクションがあったときは常に templates ディレクトリ内のすべてのファイルに対してこれを実行するように指示しています。

最後のステップで、以下のコマンドをリポジトリのルートから実行して、リポジトリにフックをインストールします。

pre-commit のインストール

誤ったテンプレートを追加してみる

linter がインストールされたので、誤ったテンプレートをリポジトリにコミットさせて、これをテストしてみます。以下のサンプルのテンプレートには、スタックを更新または削除しようとしたときに障害となる可能性のある、サブネットのルートテーブルの誤った関連付けが含まれています。

# cfnlintdemo/templates/bad-routeable-association.yaml
---
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Bad SubnetRouteTableAssociation'

Parameters:
  PublicRouteTable:
    Type: String

  PrivateRouteTable:
    Type: String

  PublicSubnet01:
    Type: String

  PrivateSubnet01:
    Type: String


Resources:
  PublicSubnetRouteTableAssociation1:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: {Ref: PublicRouteTable}
        SubnetId: {Ref: PublicSubnet01}

  PrivateSubnetRouteTableAssociation1:
      Type: AWS::EC2::SubnetRouteTableAssociation
      Properties:
        RouteTableId: {Ref: PrivateRouteTable}
        SubnetId: {Ref: PublicSubnet01}

ファイルを保存して、これをリポジトリにコミットさせてみます。

git add /templates/bad-reoutetable-association.yaml
git commit -m "Bring the badness"

次のように表示されるはずです。

[INFO] Installing environment for https://github.com/awslabs/cfn-python-lint.
[INFO] Once installed this environment will be reused.
[INFO] This may take a few minutes...
AWSLabs CloudFormation Linter............................................Failed
hookid: cfn-python-lint

W2001 Parameter PrivateSubnet01 not used.
templates/bad-route-table-association.yaml:15:3

E3022 SubnetId in PublicSubnetRouteTableAssociation1 is also associated with PrivateSubnetRouteTableAssociation1
templates/bad-route-table-association.yaml:24:9

E3022 SubnetId in PrivateSubnetRouteTableAssociation1 is also associated with PublicSubnetRouteTableAssociation1
templates/bad-route-table-association.yaml:30:9<

まとめ

お疲れさまでした。 誤ったテンプレートが Git リポジトリにコミットされるのを防ぐことができました。これで、CloudFormation テンプレートを含むあらゆる Git リポジトリの pre-commit フックとして、cfn-lint を追加できるようになりました。


著者について

Chuck Meyer は、オハイオ州を拠点とする、AWS CloudFormation のシニアデベロッパーアドボケイトです。  社外および社内の開発チームと協力して、CloudFormation ユーザーの開発者体験を継続的に向上させています。  ライブミュージックを熱狂的に愛し、ベースの演奏やライブ鑑賞に多くの時間を費やしています。#cloudformation Slack チャンネルへの参加を希望される方は、Twitter (@chuckm) からご連絡ください。