[アップデート] CloudFormationがデプロイ前にリソースの無効なプロパティ値とプロパティ名を検証するようになりました
え! 他のリソースは作り終わっているのにここからロールバック!?!
こんにちは、のんピ(@non____97)です。
皆さんはCloudFormationを触っていて「え! 他のリソースは作り終わっているのにここからロールバック!?!」と絶望したことはありますか? 私はあります。
このような時はおおよそリソースのプロパティの値に無効なものを設定している場合です。
デフォルトではCloudFormationのStackの作成または更新が失敗すると、実行前の状態にロールバックします。RDSやFSx for ONTAPなど作成に時間がかかるデプロイし終わった後にロールバックが発生すると精神的にくるものがあります。
今回、CloudFormationがデプロイ前にリソースの無効なプロパティ値とプロパティ名を検証するようになりました。
アップデート前はリソースにエラーが発生するような構文があったとしても、実際にエラーが発生するまで気づくことができませんでした。
今回のアップデートではデプロイ開始後にリソースの設定を検証し、もし不具合があれば事前にそのタイミングで失敗してくれます。早いタイミングでデプロイに失敗するので検証のサイクルが早まりますね。
実際に何パターンか試してみたので紹介します。
なお、以下記事で紹介されているようにスタックオプションで正常にプロビジョニングされたリソースの保持
を有効にすることでロールバックを防ぐことも可能です。
いきなりまとめ
- 以下についてはデプロイ開始のタイミングで検出することが可能
- 存在しないプロパティ名を指定した場合
- プロパティの値が許可されたものでない場合
- プロパティの値が正規表現に当てはまらない場合
- 以下についてはデプロイ開始のタイミングで検出することはできない
- ポリシードキュメントの構文が正しくない場合
- プロパティの値で指定したリソースが存在しない場合
- 重複した名前のリソースがすでに存在している場合
- リソースによっては対応していない場合もある
やってみた
存在しないプロパティ名を指定した場合
まずは存在しないプロパティを指定した場合です。
以下のようにBucketName
とすべきところをBucketNamE
とした場合の挙動を確認します。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
S3Bucket:
Type: AWS::S3::Bucket
DependsOn: LambdaExecutionRole
Properties:
BucketNamE: non-97-test-bucket1
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
また、S3バケットはIAMロールが作成後に作成開始をするようにDependsOn
を設定しています。もし、スタックのデプロイ開始のタイミングに検証をしてくれるのであればIAMロールの作成開始前に失敗するはずです。
こちらでデプロイするとスタックの開始をしてすぐにVALIDATION_FAILED
でエラーになりました。IAMロールの作成開始よりも早いです。
エラーメッセージにもProperties validation failed for resource S3Bucket with message: [#: extraneous key [BucketNamE] is not permitted]
と「そんなプロパティは許可されていない」と教えてくれました。トラブルシューティングにはありがたいです。
AWS CDKの場合でもaddPropertyOverride()
を使用する際はプロパティ名をStringで指定する必要があるので役立ちそうです。
なお、BucketNamE
をBucketName
とすると、以下のようにIAMロールの作成が完了してから、S3バケットの作成が開始されます。
プロパティの値が許可されたものでない場合
続いて、プロパティの値が許可されたものでない場合です。
VersioningConfiguration
のStatus
はEnabled
かSuspended
しか取り得ません。ここにYes
と指定してみます。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
S3Bucket:
Type: AWS::S3::Bucket
DependsOn: LambdaExecutionRole
Properties:
BucketName: non-97-test-bucket1
VersioningConfiguration:
Status: Yes
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
この状態でデプロイするとProperties validation failed for resource S3Bucket with message: [#/VersioningConfiguration/Status: true is not a valid enum value]
と早期にエラーを吐いてくれました。間違った値を教えてくれるのは助かります。
AWS CDKにおいても値の型が単純なStringの場合は気づきにくかったですが、これならエラーを早期に検出できそうです。
プロパティの値が正規表現に当てはまらない場合
続いて、プロパティの値が正規表現に当てはまらない場合です。
BucketName
で指定できる値は^[a-z0-9][a-z0-9//.//-]*[a-z0-9]$
と正規表現で指定されています。ここで、正規表現に含まれない_
をBucketName
に含めてみます。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
S3Bucket:
Type: AWS::S3::Bucket
DependsOn: LambdaExecutionRole
Properties:
BucketName: non-97-test-bucket_1
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
この状態でデプロイするとProperties validation failed for resource S3Bucket with message: [#/BucketName: string [non-97-test-bucket_1] does not match pattern ^[a-z0-9][a-z0-9//.//-]*[a-z0-9]$]
と早期にエラーを吐いてくれました。なかなか良いです。
プロパティのポリシードキュメントの構文が正しくない場合
次に、プロパティのポリシードキュメントの構文が正しくない場合を確認します。
PolicyDocument
はJSONで定義する必要があります。ポリシーバージョンを以下のように2024-3-15
と存在しないものを指定してみます。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
S3Bucket:
Type: AWS::S3::Bucket
DependsOn: LambdaExecutionRole
Properties:
BucketName: non-97-test-bucket1
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
SampleBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Version: 2024-3-15
Statement:
- Action:
- "s3:GetObject"
Effect: Allow
Resource: !Join
- ""
- - "arn:aws:s3:::"
- !Ref S3Bucket
- /*
Principal: "*"
この状態でデプロイするとバケットポリシーを作成するタイミングでResource handler returned message: "The policy must contain a valid version string (Service: S3, Status Code: 400, Request ID: リクエストID, Extended Request ID: 拡張リクエストID)" (RequestToken: リクエストトークン, HandlerErrorCode: GeneralServiceException)
とエラーになりました。流石にJSON内の解析はしてくれなさそうです。
プロパティの値で指定したリソースが存在しない場合
次に、プロパティの値で指定したリソースが存在しない場合を確認します。
バケットポリシーのアタッチ先に存在しないバケット名を指定します。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
S3Bucket:
Type: AWS::S3::Bucket
DependsOn: LambdaExecutionRole
Properties:
BucketName: non-97-test-bucket1
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
SampleBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: non-97-test-bucket-1
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- "s3:GetObject"
Effect: Allow
Resource: !Join
- ""
- - "arn:aws:s3:::"
- !Ref S3Bucket
- /*
Principal: "*"
この状態でデプロイするとバケットポリシーを作成するタイミングでResource handler returned message: "The specified bucket does not exist (Service: S3, Status Code: 404, Request ID: リクエストID, Extended Request ID: 拡張リクエストID)" (RequestToken: リクエストトークン, HandlerErrorCode: NotFound)
とエラーになりました。なんとなく難しい気はしていましたが、やはりできませんでした。
重複した名前のリソースがすでに存在している場合
おそらく無理だとは思うのですが、重複した名前のリソースがすでに存在している場合についても確認しておきます。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
S3Bucket:
Type: AWS::S3::Bucket
DependsOn: LambdaExecutionRole
Properties:
BucketName: non-97-enable-object-lock-on-existing-bucket
VersioningConfiguration:
Status: Enabled
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
この状態でデプロイするとS3バケットを作成するタイミングでResource handler returned message: "non-97-enable-object-lock-on-existing-bucket already exists (Service: S3, Status Code: 0, Request ID: null)" (RequestToken: リクエストトークン, HandlerErrorCode: AlreadyExists)
とエラーになりました。やはりできませんでした。
ちなみに、以下のようにテンプレート内で重複するパターンでも、1つのS3バケット作成後にもう一つのS3バケットを作成するタイミングでエラーになりました。
AWSTemplateFormatVersion: "2010-09-09"
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
S3Bucket1:
Type: AWS::S3::Bucket
DependsOn: LambdaExecutionRole
Properties:
BucketName: non-97-enable-object-lock-on-existing-bucket
S3Bucket2:
Type: AWS::S3::Bucket
DependsOn: LambdaExecutionRole
Properties:
BucketName: non-97-enable-object-lock-on-existing-bucket
リトライサイクルの高速化に役立ちそう
CloudFormationがデプロイ前にリソースの無効なプロパティ値とプロパティ名を検証するようになったアップデートを紹介しました。
検出してくれるのはあくまで静的にスキャンで明らかになるようなもののようですね。リトライサイクルを早めたり、虚無タイムを減らすことができそうです。
なお、現時点ではリソースによって対応状況は異なるようです。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">詳細な検証ありがとうございます!この辺は(今のところ)リソースごとに違うはずなので実例助かります?
そしてこれ、あるあるすぎてw
> え! 他のリソースは作り終わっているのにここからロールバック!?! https://t.co/Fd5CQQ6Dxn</p>— Kenji Kono (@konokenj) March 15, 2024</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">お、プロパティの値まで検証される場合もあるのか
DynamoDBテーブルで試したときは値を検証してくれなかったから、リソースごとに対応状況が異なるぽいな https://t.co/MOJUfADh2b</p>— Masashi Tomooka (@tmokmss) March 15, 2024</blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!