【AWS】CloudFormation Guardでよく使う評価ルールとハマりポイント

## はじめに

CloudFormation(以下CFn)使用時にCloudFormation Guardを使用していますか？
CFnのPolicy as Codeといえばこのツールと認知されている、
CFnにおけるガードレールの役割を担うツールがCloudFormation Guardです。

本記事は、CloudFormation Guardを利用したPJにおいて、
繰り返し利用したルールセットと一部ハマりポイントを記載した記事となります。
※CloudFormation Guardに関する説明・使い方他のブログで紹介されているので、
　本記事の記載範囲外となります。

## CloudFormation Guardとは・使い方
以下ブログにわかりやすくまとめられているため割愛します。

https://techblog.forgevision.com/entry/cloudformation-guard

## 紹介評価ルールの構造
基本的に上記まとめブログに記載されていますが、
本記事におけるルールの構造を記載します。

例)KMSの暗号化確認
```
let aws_kms_key_resources = Resources.*[ Type == 'AWS::KMS::Key' ]
rule aws_kms_key when %aws_kms_key_resources !empty {
  %aws_kms_key_resources.Properties.EnableKeyRotation == true
}
```
`let aws_kms_key_resources`：
　対象となるリソース情報を格納するための変数作成
`Resources.*[ Type == 'AWS::KMS::Key' ]`：
　Cfnテンプレート内記載のType「AWS::KMS::Key」を持つ全てのResource句を範囲指定し、本範囲を変数「aws_kms_key_resources」に代入
　※テンプレート内一意である論理IDを対象にすることも可能
`rule aws_kms_key`:
　ルールを格納するためのルールセット変数作成
`when %aws_kms_key_resources !empty`:
　これから記載するルール適用条件を記載。本記載は変数「aws_kms_key_resources」がテンプレートに存在する場合ルールが適用される
`%aws_kms_key_resources.Properties.EnableKeyRotation == true`:
　ルール内容。変数「aws_kms_key_resources」内のProperies句内の「EnableKeyRotation」がtrueであることの確認



## よく使う評価ルール
ここから複数システムにて共通的に使用したルールを記載します。

### ProjectタグとNameタグが付いているのか
この評価ルールではProject/Nameタグの付与確認とProjectタグ内容の確認になります。
Nameタグは各論理IDごとに設定しているため、汎用性を優先してタグ付与のみの確認としています。
※網羅的に確認する場合は論理ID単位でルールを作成する必要有

■タグ評価ルールファイル
```
let aws_s3_resources = Resources.*[ Type == 'AWS::S3::Bucket' ]
rule aws_s3 when %aws_s3_resources !empty {
  %aws_s3_resources.Properties {
    # CostTag check
    some Tags[*] {
      Key == "Project"
      Value == "test"
    }
    some Tags[*] {
      Key == "Name"
    }
  }
}
```

#### ハマりポイント
Key/Valueの組み合わせがバラバラでもルールに準拠していればOKとなってしまう。
Cfnテンプレート内におけるKey/Value形式のようにセットで評価するようにしたい。

##### 対応方法
`some`をProperties要素の前に付与することで、
Key/Valueの組み合わせをブロックとして評価ルールになる。
※以下ルールはこのKey/Valueの組み合わせでないとエラーにできる。
```
    some Tags[*] {
      Key == "Project"
      Value == "test"
    }
```

### Projectタグが付いているのか(Prameter句参照形式)
タグのValueをPrameter句に定義している場合以下記載のような記載となる。
※Prameter句の内容までGuardツールで検出できないため、
　Prameter句の正当性はレビューやデプロイ後のAWSConfig等で検知する必要あり。

■タグ評価ルールファイル(Prameter句参照形式)

```
let aws_s3_resources = Resources.*[ Type == 'AWS::S3::Bucket' ]
rule aws_s3 when %aws_s3_resources !empty {
  %aws_s3_resources.Properties {
    # CostTag check
    some Tags[*] {
      Key == "Project"
      Value == {"Fn::Sub":"${SystemName}-${Enviroment}"}
    }
  }
}
```

#### ハマりポイント
評価ルール内では短縮形の組み込み関数(!Ref)が利用できない。
また、組み込み関数を利用して設定される値を評価してくれないため、
組み込み関数の記述が行えているかの確認しかできない。

##### 対応方法
CFnテンプレートファイル内で短縮形の組み込み関数を利用している場合は、
評価ルールの中で、完全関数名に戻してあげる必要がある。
以下のようなテンプレートファイルに対しては、
`Value == {"Fn::Sub":"${SystemName}-${Enviroment}"}`のような形のルールを作成
```
    some Tags[*] {
      Key == "Project"
      Value == {"Fn::Sub":"${SystemName}-${Enviroment}"}
    }
```
■評価対象テンプレートファイル
```
  S3:
    Type: AWS::S3::Bucket
    Properties:
      Tags:
        - Key: Project
          Value: !Sub "${SystemName}-${Enviroment}"
```


### S3バケットの暗号化が有効になっているのか
S3バケットの暗号化が有効になっていることを確認する評価ルール

■S3バケットの暗号化確認評価ルールファイル
```
let aws_s3_resources = Resources.*[ Type == 'AWS::S3::Bucket' ]
rule aws_s3 when %aws_s3_resources !empty {
  %aws_s3_resources.Properties {
    BucketEncryption.ServerSideEncryptionConfiguration[*] {
      BucketKeyEnabled == true
    }
  }
}
```

### CFnデフォルト値になっているのか
CFnデフォルト値を正として評価したい場合は「要素に値が設定されていないこと」を評価する`!exist`を使用する

■CFnデフォルト値を正とする確認評価ルールファイル

```
let aws_s3_resources = Resources.*[ Type == 'AWS::S3::Bucket' ]
rule aws_s3 when %aws_s3_resources !empty {
  %aws_s3_resources.Properties {
    BucketEncryption.ServerSideEncryptionConfiguration[*] {
      BucketKeyEnabled !exists
    }
  }
}
```

### 配列内の値がCFnデフォルト値になっているのか
CFnデフォルト値を正として評価したい場合は「要素に値が設定されていないこと」と「その配列を定義する要素」が存在しないことを確認する必要がある。

例えば、[SecurityHub 基礎セキュリティ[ECS.8]](https://docs.aws.amazon.com/ja_jp/securityhub/latest/userguide/ecs-controls.html#ecs-8)を題材として、
'AWS::ECS::TaskDefinition'に環境変数「AWS_ACCESS_KEY_ID」を渡していないことを確認します。
※ECSにおけるAWS_ACCESS_KEY_IDの設定は、
　以下テンプレート内のEnvironmentにて設定します。

■評価対象テンプレートファイル
```
AWSTemplateFormatVersion: 2010-09-09
Description: Create a task definition for a web server.
Resources:
  ECSTaskDefinition:
    Type: 'AWS::ECS::TaskDefinition'
    Properties:
      ContainerDefinitions:
        - Name: first-run-task
          Image: 'httpd:2.4'
          Essential: true
          PortMappings:
            - ContainerPort: 80
              Protocol: tcp
          Environment:
            - Name: AWS_ACCESS_KEY_ID
              Value: 'XXXXXXXXXXXXXXXX'
```

■配列内の値をCFnデフォルト値を正とする評価ルールファイル
```
let aws_ecs_taskdefinition_resources = Resources.*[ Type == 'AWS::ECS::TaskDefinition' ]
rule aws_ecs_taskdefinition when %aws_ecs_taskdefinition_resources !empty {
  %aws_ecs_taskdefinition_resources.Properties {
    # [ECS-2-8] Cloud inspection check sheet
    some ContainerDefinitions[*] {
      Environment[*].Name != "AWS_ACCESS_KEY_ID" or
      Environment[*].Name !exists
    }
  }
}
```
## 終わりに
テンプレートを用いてデプロイ後のリソースではなく、
テンプレートファイルの記述を評価するため組み込み関数が使いにくいなど、
惜しいところがあるツールだと感じました。

また、評価ルールの作成も独自の記述方法があるため、慣れるまで時間がかかりました。
組織やPJの規約に当てはまるルールを作成することができれば、
規約違反による手戻りを防げる協力なツールであると思います。

本記事がCloudForamtionGuard作成に悩まれている方の助けになれば幸いです。

### 参考元

https://docs.aws.amazon.com/ja_jp/cfn-guard/latest/ug/what-is-guard.html

https://techblog.forgevision.com/entry/cloudformation-guard

https://vbuiv.hatenablog.com/entry/2022/03/09/092332

https://docs.aws.amazon.com/ja_jp/cfn-guard/latest/ug/query-and-filtering.html