[アップデート] CloudFormationがデプロイ前にリソースの無効なプロパティ値とプロパティ名を検証するようになりました

リトライサイクルの高速化に役立ちそう
2024.03.15

え! 他のリソースは作り終わっているのにここからロールバック!?!

こんにちは、のんピ(@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で指定する必要があるので役立ちそうです。

なお、BucketNamEBucketNameとすると、以下のようにIAMロールの作成が完了してから、S3バケットの作成が開始されます。

IAMロールを作ってからS3バケットを作成することを確認

プロパティの値が許可されたものでない場合

続いて、プロパティの値が許可されたものでない場合です。

VersioningConfigurationStatusEnabledSuspendedしか取り得ません。ここに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がデプロイ前にリソースの無効なプロパティ値とプロパティ名を検証するようになったアップデートを紹介しました。

検出してくれるのはあくまで静的にスキャンで明らかになるようなもののようですね。リトライサイクルを早めたり、虚無タイムを減らすことができそうです。

なお、現時点ではリソースによって対応状況は異なるようです。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!