CloudFormation 2024/11の更新 : hookの拡張 解説と操作例

# hookの拡張
11/20-21 にCloudFormation のフック(hook)の機能拡張が4件発表されました。

 https://aws.amazon.com/about-aws/whats-new/2024/11/author-aws-cloudformation-hooks-cloudformation-guard-domain-specific-language/

 https://aws.amazon.com/about-aws/whats-new/2024/11/aws-cloudformation-hooks-custom-aws-lambda-functions/

 https://aws.amazon.com/about-aws/whats-new/2024/11/aws-cloudformation-hooks-stack-change-set-target-points/


 https://aws.amazon.com/about-aws/whats-new/2024/11/aws-cloudformation-hooks-cloud-control-api-configurations-evaluation/


機能拡張の整理と、実際にGuardルールからhookを作成する手順をメモしておきます。

## hook とは
CloudFormation フック(hook)は、リソースを作成・更新・削除する前にテンプレートの内容を確認（評価）する機能です。
基準に満たない場合はリソースの作成・更新・削除を拒否できます。

下記はフックでリソース DirectoryBucket の作成を拒否した例です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/66cd429f-65a1-4aa5-dc5e-b11175878f6b.png) （ターゲット：スタック）

フックには失敗モード（FailureMode）設定があり、失敗(Fail)と警告(Warn)から選択します。
通常は失敗(Fail)モードに設定します。
警告(Warn)の場合はイベント履歴に警告通知を表示するのみで、リソース操作はそのまま行われます。

:::note info
[AWS CloudFormation 詳解](https://book.mynavi.jp/ec/products/detail/id=143664)
2章 CloudFormationの機能とコンソール画面
2-19 拡張機能とレジストリ
2-19-3 フック
:::


## ３つの機能拡張
「**1.フックのターゲット(hook targets) 設定の新設**」と「**2.Guard ルールやLambda関数を使った簡易なhook実装方法を提供**」が主な機能拡張です。
併せて「3. Cloud Control API (CCAPI) でhookが利用可能」も提供されました。

### 1. フックのターゲット(hook targets) 設定の新設
 **hookの実行タイミング**が追加され、**フックのターゲット**（**hook targets**）で指定可能になりました。

 フックのターゲットは **STACK** / **Resources** / **CHANGE_SET** / **CLOUD_CONTROL** が指定可能です。
 設定画面の選択肢
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/5c3977bd-a2a5-4b65-8fca-beef433ecbeb.png)


#### 〇フックのターゲット: スタック(STACK)
スタック操作（作成・更新・削除）開始時、リソース操作前にテンプレートを確認します。
既存のhook実行タイミングと同じです。
評価がNRの場合、スタックでエラーとなりリソース操作は行われません。（hookのモードが失敗(Fail)の場合）

<details><summary>フロー図</summary>

```mermaid
graph TB
  Start([スタック作成開始])-->hook
  
  IFH{hook対象 有無?}-->|hookなし| CONT
  IFH-->|hookあり| IFA{テンプレートを評価}
  IFA-->|評価 OK| CONT(通常スタック処理開始
                       リソース作成等)
  IFA-->|評価 NG| IFNG{"hook モード: 警告/失敗"}
  IFNG-->|警告モード|CONT
  IFNG-->|失敗モード|NG[/スタック作成で
                     エラーを発生\]
  NG-->ENDERR([スタックエラー処理])-->EEND([...])
  CONT-->END2([スタック作成終了])
  
  subgraph hook["hook_target=STACK"]
   IFH
   IFA
   IFNG
  end
```

</details>

#### 〇フックのターゲット: リソース(Resources)
リソース操作（作成・更新・削除）の最初にテンプレートを確認します。
評価がNGの場合、リソースの操作（作成・更新・削除）エラーとなります。（hookのモードが失敗(Fail)の場合）

<details><summary>フロー図</summary>

```mermaid 
graph TB
  Start([スタック作成開始])-->2ND(通常スタック処理開始)
  2ND-->A@{shape: processes,label: "リソース作成開始"}-->hook
  IFA{hook対象?}
  IFA-->|hookなし| CONTA
  IFA-->|hookあり| IFAH{テンプレートを評価}
  IFAH-->|評価 OK| CONTA@{shape: processes,label: 通常リソース処理 作成等}
  IFAH-->|評価 NG| IFNG{"hook モード: 警告/失敗"}
  IFNG-->|警告モード|CONTA
  IFNG-->|失敗モード| NG[/リソース作成エラー\]
  NG-->CONTAERR([リソース作成エラー処理])-->ERREND([スタックエラー処理])-->EEND([...])
  CONTA-->END2([スタック作成終了])
  subgraph hook["hook_target=Resources"]
   IFA
   IFAH
   IFNG
  end
```

</details>

#### 〇フックのターゲット: 変更セット(CHANGE_SET)
スタックの変更セット作成時にテンプレートを確認します。
評価がNGの場合、変更セットはエラーとなります。（hookのモードが失敗(Fail)の場合）

CloudFormationでスタックを直接更新する操作は可能です。
但しスタック更新時の「変更セットのプレビュー」はエラー表示となります。

#### 〇フックのターゲット: Cloud Control API(CLOUD_CONTROL)
Cloud Control API実行時に設定内容を確認するようです。動作未調査。
CloudFormationでのスタック操作・変更セットの操作には影響しません。
以下の[3. Cloud Control API (CCAPI) でhookが利用可能](#3-cloud-control-api-ccapi-%E3%81%A7hook%E3%81%8C%E5%88%A9%E7%94%A8%E5%8F%AF%E8%83%BD)の実装です。

### 2. Guard ルールやLambda関数を使った簡易なhook実装方法を提供
簡易にhook作成が可能になりました。
 
 **CloudFormation Guard ルール** または **Lambda関数** を利用して作成できます。
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/62193550-1523-98e9-2e2b-d2b900e13d65.png)

GuardルールとLambda関数でのhook実装は、それぞれ<b>Private::Guard::</b>xxxx と<b>Private::Lambda::</b>XXXX と名前付けされます。
これはAWS::Hooks::GuardHookフックとAWS::Hooks::LambdaHookフックのエイリアスで実装されています。

新メニューフック の一覧。「元のフックタイプ」にエイリアス元のフックが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/6879a55f-750e-6521-d6a4-125930563c64.png)


#### * Guard/Guard ルール とは
GuardはAWS CloudFormation Guardの略で、CloudFormationテンプレートファイルが独自の制限事項（ルール）に従っているか、を評価するのに利用します。
Guard ルールファイル（ルールセットファイル）へ、テンプレートファイルの制限事項を記載します。

下記ルールの例では、AWS::EC2::Instance に ImageId が存在しなければエラーとなります。
~~~ sampleEC2.guard 
# sampleEC2.guard

let ec2_instances = Resources.*[Type == 'AWS::EC2::Instance']

rule IMAGEID_NOT_FOUND_ERROR
  when %ec2_instances !empty {
    %ec2_instances.Properties.ImageId exists
}
~~~
次の例では、S3Express の必須属性 DataRedundancyとLocationNameをチェックする３つのルールを記載しています。
~~~ sampleS3Express.guard
# sampleS3Express.guard

let s3express   = Resources.*[Type == 'AWS::S3Express::DirectoryBucket']
let s3expressLN = Resources.*[Type == 'AWS::S3Express::DirectoryBucket' Properties.LocationName is_string ]

rule S3EXPRESS_DATAREDUNDANCY_VALUE_ERR
  when %s3express !empty {
    %s3express.Properties.DataRedundancy == 'SingleAvailabilityZone'
  }

rule S3EXPRESS_LOCATIONNAME_MUST_EXIST
  when %s3express !empty {
    %s3express.Properties.LocationName exists
  }

rule S3EXPRESS_LOCATIONNAME_IS_WRONG
  when %s3expressLN !empty {
    %s3expressLN.Properties.LocationName in
      ['use1-az4','use1-az5','use1-az6','use2-az1','use2-az2','usw2-az1','usw2-az3','usw2-az4','aps1-az1','aps1-az3','apne1-az1','apne1-az4','euw1-az1','euw1-az3','eun1-az1','eun1-az2','eun1-az3']
      << check latest LocationName list at https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-express-Endpoints.html >>
  }
~~~
:::note info
[AWS CloudFormation 詳解](https://book.mynavi.jp/ec/products/detail/id=143664)
6章 CloudFormation用ツール
6-5 AWS CloudFormation Guard(cfn-guard)
6-5-3 ルールセットファイル（ガードファイル）の文法
:::

今回の拡張により、Guard ルールで直接hookの評価が実装できるようになりました。
利用時はGuard ルールファイルをS3へ保存しS3 URLで指定します。

### 3. Cloud Control API (CCAPI) でhookが利用可能
CCAPI はCloudFormationと、それ以外のIaC用に共通で提供されているAPIです。
CloudFormation以外でCCAPIを利用した操作でもhookが利用可能になりました。

## Guard ルールでhookを作成して新機能を確認

### Web管理コンソール 

Web管理コンソール左のメニューに「**フック**」(**hooks**) が追加されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/5f6aa3bf-ddc2-cace-c9ad-62a5fb57fc44.png)

フックのトップ画面。11/24現在、なぜか左メニューが英語になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/3e640a02-9937-4353-ec45-6da51d708304.png)

:::note info
「フックの概要(hooks overview)」も追加されています。
こちらは hook 機能の紹介です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/5d58cf7f-230d-6ddf-77da-c8a68bd0bcf3.png)
:::

### フックを作成（Guardを利用）
先に、Guard ルールファイル（sampleS3Express.guard）を同じリージョンのS3バケットへ保存しておきます。

「フックを作成」ボタンで「Guardを利用」を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/d0b8837d-ae18-836e-fa88-55fb7d297ce7.png)

ステップ１
ルールファイルのS3 URIを指定して、「次へ」ボタンで進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/186bfe97-be9c-52c1-c0a6-3b9f5323aacc.png)
＊「Guard出力レポートのS3バケット」を指定すればguardの評価結果などをログ出力できます。

ステップ２
フック名、フックのターゲット、アクション、フックのモード、実行ロールを指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/897f80a8-3024-4ed4-5b53-8222ba9d6c4d.png)

ステップ３
デフォルトままにしておきます。
なお、ステップ２でフックのターゲットに「スタック」を指定した場合、「リソースフィルター」は空欄にしてください。指定すると動作しない現象を確認しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/05986d7c-0c61-2736-a27f-f3afa0c470b1.png)

ステップ４
設定内容が表示されます。確認して「フックをアクティブ化」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/f5c5fc5f-9854-72d3-9264-94dc8462e86e.png)

作成完了画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/48bc54c5-0d39-aa8c-eea9-454a1b78b1be.png)


今回はフック「Private::Guard::S3ExpressCheckProperties」が作成されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/7d0edaeb-47c4-2210-bb0a-fb33ddb8b91c.png)

## スタック作成のテスト
不正なスタックを作成して動作を確認してみます。
下記 sampleS3ExpressError.yaml ファイルを使います。LocationNameが不正です。
 リソースタイプ [AWS::S3Express::DirectoryBucketについてはこちら](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-s3express-directorybucket.html) をご覧ください

~~~ sampleS3ExpressError.yaml
Resources:
  DirectoryBucket:
    Type: AWS::S3Express::DirectoryBucket
    Properties:
      DataRedundancy: SingleAvailabilityZone
      LocationName: ERRORapne1-az99
~~~
スタックメニューから、「スタックの作成」ボタンの「新しいリソースを使用（標準）」を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/731e81bf-56b0-0f3e-ae13-59b665775e7a.png)

ステップ１
「テンプレートファイルのアップロード」でsampleS3ExpressError.yaml ファイルをアップロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/20b4ae96-793f-b2fd-083d-8b5f32d18a29.png)

ステップ２
スタック名を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/a41abc46-8f21-a182-97d5-5209c5267661.png)

ステップ３
全てデフォルトままで「次へ」
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/c915c735-e68e-a1ce-af63-ad549a51875e.png)

ステップ４
設定を確認して「送信」ボタン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/8ff111d4-4cba-dab8-cef0-2c9d51f62ab2.png)

イベントにて、スタック作成が失敗しているのを確認します。
フックの呼び出しが２つ表示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/f87a6544-ad66-f760-22f5-135a759d64ed.png)

フックの呼び出しをクリックすると情報が確認できます。下記はフックが開始したメッセージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/76b4ee50-7ed8-a125-2d68-adca4152561e.png)

フック名 Private::Guard::S3ExpressCheckProperties がエラーを発生させています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/392b4083-facb-61b0-0253-ec6632123113.png)

理由には、Guardルールファイルで検知対象となったルール名が表示されます。
~~~
"フックのスタータス理由"
Hook failed with message: Template failed validation, the following rule(s) failed: 
S3EXPRESS_LOCATIONNAME_IS_WRONG.
~~~
「Guard出力レポートのS3バケット」を有効にした場合は、出力先も表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832079/d3eeccf1-0724-198e-d217-fbf56a0e7e08.png)

#### 終わりに
今回のhookの拡張には、組織統制の面だけでなく、テンプレート設定不備などの事前チェックとしての活用の可能性も感じます。

フックのターゲット STACK / Resources の違いと使い分けの説明、Lambdaでの作成例なども、時間ができれば書きたいのですが。。