AWS CloudFormationテストツール全部入りDockerfileの作成

# アドベントカレンダーについて
APN Ambassador によるアドベントカレンダー「[Japan APN Ambassador Advent Calendar 2020](https://qiita.com/advent-calendar/2020/japan-apn-ambassador)」での22日目の記事となります。
APN Ambassador の詳細については、下記の記事をご覧ください。

- [APN Ambassadorってなんだ？](https://qiita.com/keizoa/items/343af854447f53f7570b)
- [APN Ambassador Program は世界標準！](https://qiita.com/obayk/items/46d9e04d24489d0c6c43)

なお、投稿内容は私個人の意見であり、所属企業を代表するものではありませんので、ご了承ください。

# はじめに
AWSのCloudFormationをYAMLやJSONで書くときに、文法やパラメータの型などを、テンプレートの中身を目視してレビューするのは大変です。
そこで、CloudFormationのテストを楽にするための様々なツールが用意されています。
本記事ではこれらのツールを紹介し、紹介したツールの導入をDockerfileで定義して使い回すことを目指します。

# ツール紹介
ツールはAWS CLIに付属しているものから、オープンソースで公開されているものまで様々に及びます。
1つ1つ紹介していきます。

## ① aws cloudformation validate-template
* `aws cloudformation validate-template` コマンドで、指定したテンプレートが有効なJSONやYAMLか否かをチェックできます。
* 但し、このコマンドでは、値の妥当性をチェックせず、あくまで正しい文法で書かれているかだけをチェックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/6bcc21f6-732a-5572-bb37-c10df469a400.png)

https://awscli.amazonaws.com/v2/documentation/api/latest/reference/cloudformation/validate-template.html

### 実行例

```bash
bash-4.2# aws cloudformation validate-template --template-body file://./vpcsample-template.yaml 

An error occurred (ValidationError) when calling the ValidateTemplate operation: Template format error: YAML not well-formed. (line 26, column 17)
```

## ② cfn-lint
* 正式名称は 「AWS CloudFormation Linter」
* CloudFormationのYAMLとJSONのテンプレートを、リソースの仕様および追加のチェック項目に即して検証します。
* プロパティの有効な値、さらにベストプラクティスの確認も含みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/da703120-ec1f-9d78-e2d8-35a08e201347.png)

https://github.com/aws-cloudformation/cfn-python-lint

### 実行例

```bash
bash-4.2# cfn-lint vpcsample-template.yaml
W3010 Don't hardcode ap-northeast-1a for AvailabilityZones
vpcsample-template.yaml:18:13
```

## ③ taskcat
* 複数のAWSリージョンで並列にスタックを作成してデプロイの可否をテストします。
* 単一テンプレートやスタックのテストでは分からない問題を検知できます。
    * 例えば、特定のリージョンでサポートされないサービスが含まれていないかを調べたりできます。
* 各リージョンでの成功/失敗のレポートを生成できます。
* CloudFormation StackSetsを利用する際の確認に効果を発揮します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/c128eda0-2535-6c50-81b9-ce1c7be1d310.png)

https://github.com/aws-quickstart/taskcat

### .taskcat.ymlのサンプル

```yaml
project:
   name: taskcat-example
   regions:
      - ap-northeast-1
      - us-east-1
   tests:
      vpcsample:
         template: ./vpcsample-template.yaml
```

### 実行例

```bash
bash-4.2# taskcat test run 

version 0.9.20
[INFO   ] : Linting passed for file: /workspaces/awsdev/work/vpcsample-template.yaml
[S3: -> ] s3://tcat-taskcat-example-XXXXXXXXXXXXXX/taskcat-example/vpcsample-template.yaml
[INFO   ] : ┏ stack Ⓜ tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2                                        
[INFO   ] : ┣ region: ap-northeast-1                                                                                         
[INFO   ] : ┗ status: CREATE_COMPLETE                                                                                        
[INFO   ] : ┏ stack Ⓜ tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2                                        
[INFO   ] : ┣ region: us-east-1                                                                                              
[INFO   ] : ┗ status: CREATE_COMPLETE                                                                                        
[INFO   ] : Reporting on arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2/7e9705c0-2747-11eb-861d-0aff14bc0982
[INFO   ] : Reporting on arn:aws:cloudformation:us-east-1:XXXXXXXXXXXX:stack/tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2/7fc94430-2747-11eb-bfb5-1246411399d1
[INFO   ] : Deleting stack: arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2/7e9705c0-2747-11eb-861d-0aff14bc0982
[INFO   ] : Deleting stack: arn:aws:cloudformation:us-east-1:XXXXXXXXXXXX:stack/tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2/7fc94430-2747-11eb-bfb5-1246411399d1
[INFO   ] : ┏ stack Ⓜ tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2                                        
[INFO   ] : ┣ region: ap-northeast-1                                                                                         
[INFO   ] : ┗ status: DELETE_COMPLETE                                                                                        
[INFO   ] : ┏ stack Ⓜ tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2                                        
[INFO   ] : ┣ region: us-east-1                                                                                              
[INFO   ] : ┗ status: DELETE_COMPLETE
```

### レポートサンプル (テキスト)

```text
bash-4.2# cat tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2-ap-northeast-1-cfnlogs.txt
-----------------------------------------------------------------------------
Region: ap-northeast-1
StackName: tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2
*****************************************************************************
ResourceStatusReason:  
Stack launch was successful
*****************************************************************************
*****************************************************************************
Events:  
TimeStamp                         ResourceStatus      ResourceType                LogicalResourceId                                                ResourceStatusReason
--------------------------------  ------------------  --------------------------  ---------------------------------------------------------------  ---------------------------
2020-11-15 13:36:54.256000+00:00  CREATE_COMPLETE     AWS::CloudFormation::Stack  tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2
2020-11-15 13:36:52.656000+00:00  CREATE_COMPLETE     AWS::EC2::Subnet            TestSubnet01
2020-11-15 13:36:40.587000+00:00  CREATE_COMPLETE     AWS::EC2::Subnet            TestSubnet03
2020-11-15 13:36:40.280000+00:00  CREATE_COMPLETE     AWS::EC2::Subnet            TestSubnet02
2020-11-15 13:36:36.368000+00:00  CREATE_IN_PROGRESS  AWS::EC2::Subnet            TestSubnet01                                                     Resource creation Initiated
2020-11-15 13:36:35.712000+00:00  CREATE_IN_PROGRESS  AWS::EC2::Subnet            TestSubnet01
2020-11-15 13:36:24.163000+00:00  CREATE_IN_PROGRESS  AWS::EC2::Subnet            TestSubnet03                                                     Resource creation Initiated
2020-11-15 13:36:24.147000+00:00  CREATE_IN_PROGRESS  AWS::EC2::Subnet            TestSubnet02                                                     Resource creation Initiated
2020-11-15 13:36:23.594000+00:00  CREATE_IN_PROGRESS  AWS::EC2::Subnet            TestSubnet03
2020-11-15 13:36:23.442000+00:00  CREATE_IN_PROGRESS  AWS::EC2::Subnet            TestSubnet02
2020-11-15 13:36:21.092000+00:00  CREATE_COMPLETE     AWS::EC2::VPC               TestVPC
2020-11-15 13:36:04.224000+00:00  CREATE_IN_PROGRESS  AWS::EC2::VPC               TestVPC                                                          Resource creation Initiated
2020-11-15 13:36:03.612000+00:00  CREATE_IN_PROGRESS  AWS::EC2::VPC               TestVPC
2020-11-15 13:35:59.310000+00:00  CREATE_IN_PROGRESS  AWS::CloudFormation::Stack  tCaT-taskcat-example-vpcsample-2a58141fb6114aed895774ebfcac9ed2  User Initiated
*****************************************************************************
-----------------------------------------------------------------------------
Tested on: Sunday, 15. November 2020 01:37PM

```

### レポートサンプル (HTML)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/b3bb1add-5805-a790-0bb9-e03353b4f9ea.png)

## ④ cfn_nag
* 以下のようなセキュリティ上安全ではない設定を検査して表示するためのツールです。
    * 許可範囲が広すぎるIAMルール（ワイルドカード）
    * 許容範囲が広すぎるセキュリティグループルール（ワイルドカード）
    * 有効化されていないアクセスログ
    * 有効化されていない暗号化
    * パスワードの強度

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/32fff86d-caf5-2b04-20b4-4c68dbb87e48.png)

https://github.com/stelligent/cfn_nag

### 実行例

```bash
bash-4.2# cfn_nag_scan --input-path vpcsample-template.yaml 
------------------------------------------------------------
vpcsample-template.yaml
------------------------------------------------------------------------------------------------------------------------
| WARN W60
|
| Resources: ["TestVPC"]
| Line Numbers: [5]
|
| VPC should have a flow log attached

Failures count: 0
Warnings count: 1
```

## ⑤ CloudFormation Guard
* 予め定めておいたルール内でテンプレートが記述されているかどうかを検査します。
    * .ruleset ファイルに予めルールを書いておきます。
    * cfn-guard コマンドで YAML がそのルールに沿って書かれているかどうかが検査されます。
* プロジェクト毎に設けられる制約をルール化しておく、またはお客様と合意した設計値を書いておいて最終チェックに使うなど、プロジェクト固有または逸脱できない条件との合致を検査するために使用するのが良さそうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/211986/e3eeb088-0d33-a982-5496-4a73f0dfb269.png)

https://github.com/aws-cloudformation/cloudformation-guard

### 実行例

```bash
bash-4.2# cfn-guard check -t Examples/ebs_volume_template.json -r Examples/ebs_volume_template.ruleset

[NewVolume2] failed because [Encrypted] is [false] and the permitted value is [true]
[NewVolume] failed because [Encrypted] is [false] and the permitted value is [true]
[NewVolume] failed because [Size] is [500] and the permitted value is [<= 100]
Number of failures: 3
```

# テストツールをDockerfileに詰め込む
VSCode Remote Containersでも動かせるようにしてあります。

また、taskcatの実行には特権が必要になるため、下記リンク先を参考にして動かしましょう。
VSCode Remote Containersを冠とした記事ですが、VSCode Remote Containersを使わなければアタッチ手前までの手順でOKです。
[VSCode Remote Containers で --privileged と /sbin/init を渡したコンテナで開発する方法](https://qiita.com/nasuvitz/items/68ff25fe18674560d939)

```dockerfile
# AWS CLI v2 (Official)
FROM amazon/aws-cli:latest

# To install Visual Studio Code Server
# and modules
RUN yum -y update && \
    yum -y install tar gzip git

# To install pip
RUN curl -kL https://bootstrap.pypa.io/get-pip.py | python

# To install cfn-lint
RUN pip install cfn-lint

# To install taskcat
RUN yum -y install python3 && \
    pip3 install taskcat

# To install docker (for taskcat)
RUN amazon-linux-extras install -y docker

# To install cfn-nag & CloudFormation Guard
RUN yum -y install bzip2 gcc make openssl-devel && \
    git clone https://github.com/sstephenson/rbenv.git ~/.rbenv && \
    echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile && \
    echo 'eval "$(rbenv init -)"' >> ~/.bash_profile && \
    source ~/.bash_profile && \
    git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build && \
    ~/.rbenv/plugins/ruby-build/install.sh && \
    rbenv install 2.7.2 && \
    rbenv global 2.7.2 && \
    gem install cfn-nag && \
    yum -y install wget && \
    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 -C ~/

# To clean up
RUN yum clean all
```

## Dockerfile作成において判断に迷った点
どのコンテナイメージを`FROM`に据えるかという点はポイントかと思いますが、今回は`amazon/aws-cli:latest`を選択しました。
このイメージは、AWS公式のAWS CLI v2のDockerイメージとなります。
参考: [公式 AWS CLI バージョン 2 Docker イメージの使用](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-docker.html)

AWS CLIに関連したサポートを受けるためには、公式のイメージを使っておくほうが良いと考え、このイメージを選択しています。

ただ、その代わりにcfn-nagのインストールと実行に必要なRubyのインストールには、そこそこ面倒な手順を踏んでいます。
だからと言って、rubyのイメージにすれば、今度はAWS CLIのインストールが少々手間となるため、メリット・デメリットを勘案しても、判断に迷うところではありました。

# まとめと今後に向けて
* 機械的な検査はできる限り自動化してしまうことで、人の判断に依存せず、見落としのないテストが可能です。
* 複数のツールを組み合わせることで、異なる観点でのテストが可能です。
* ただし、要件とテンプレートの整合チェックとなると、まだまだ困難は残ります。
    * SIerが良く行うウォーターフォールモデルの開発時には、前工程の要件(基本設計)と、現工程のテンプレートの中身が整合しているかどうかがチェックされますが、この観点ではまだまだ検査が難しい側面があり、最近リリースされたCloudFormation Guardをどこまで活用できるかを含め、今後突き詰めていきたいポイントと言えます。

# 参考文献
* [YouTube: I'm in DevOps - Deep Dive: Infrastructure as Code on AWS (Level 300)](https://www.youtube.com/watch?v=YHdZYw3hXwE)
* [公式 AWS CLI バージョン 2 Docker イメージの使用](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-docker.html)