特定IPアドレス以外を拒否設定したS3バケットをCloudFormationで更新したい時に追加する条件

2021.02.01

いわさです。

IPアドレスでS3バケットへのアクセスを制限したいケースはあると思います。
その際にS3バケットのバケットポリシーへ対象IPアドレスを記述することで、対象IPアドレス以外からのアクセスを拒否することが出来ます。

しかし、CloudFormationで上記ポリシー設定を行ったバケットを作成した場合、作成後のCloudFormationからの更新や削除が出来なくなります。(対象IPアドレスであればマネジメントコンソールからの操作は出来ます。)
対処するための条件を試してみましたので記します。

IPアドレス制限を行うCloudFormationテンプレート

まず、シンプルにIPアドレス制限を行うS3バケットをCloudFormationテンプレートで作成します。

後ほどテンプレート更新でライフサイクルを変更したいので、ライフサイクルのみ設定しておきます。
設定した日数でオブジェクトを期限切れにします。

AWSTemplateFormatVersion: "2010-09-09"
Description: ""
Parameters: 
  bucketName:
    Description: bucket name (unique).
    Type: String
  bucketExpirationInDays:
    Description: Bucket object expiration date.
    Type: Number
    Default: 5
Resources:
    S3Bucket:
        Type: "AWS::S3::Bucket"
        Properties:
            BucketName: !Ref bucketName
            PublicAccessBlockConfiguration:
              BlockPublicAcls: true
              BlockPublicPolicy: true
              IgnorePublicAcls: true
              RestrictPublicBuckets: true
            LifecycleConfiguration: 
                Rules: 
                  - 
                    Id: "auto-delete"
                    Status: "Enabled"
                    ExpirationInDays: !Ref bucketExpirationInDays
    S3BucketPolicy:
        Type: "AWS::S3::BucketPolicy"
        Properties:
            Bucket: !Ref S3Bucket
            PolicyDocument: 
                Version: "2012-10-17"
                Statement: 
                  - 
                    Effect: "Deny"
                    Principal: "*"
                    Action: "s3:*"
                    Resource: 
                      - !Sub "arn:aws:s3:::${S3Bucket}"
                      - !Sub "arn:aws:s3:::${S3Bucket}/*"
                    Condition: 
                        NotIpAddress: 
                          "aws:SourceIp": 
                            - "111.222.333.444/32"

このテンプレートでCloudFormationから作成してみます。
ライフサイクルは5日を指定します。

作成されたS3バケットの設定を確認しています。
5日で設定されていますね。

対象IPアドレス以外からはアクセス出来ません。

CloudFormationからスタックの更新と削除をしてみる

では、CloudFormationからライフサイクルの日数を更新してみましょう。

更新に失敗しました。

UPDATE_FAILED
API: s3:PutLifecycleConfiguration Access Denied

削除も失敗します。

DELETE_FAILED
API: s3:DeleteBucketPolicy Access Denied
API: s3:DeleteBucket Access Denied

当然ですが、許可されているIPアドレスでマネジメントコンソールからであれば削除や更新が出来ます。

IPアドレスがクライアントIPアドレスではなく、CloudFormation独自のIPのアドレスを利用するそうで、IPアドレス制限を行うとアクセスできなくなります。

AWS CloudFormation provisions resources by using its own IP address, not the IP address of the originating request. For example, when you create a stack, AWS CloudFormation makes requests from its IP address to launch an EC2 instance or to create an S3 bucket, not from the IP address from the CreateStack call or the aws cloudformation create-stack command.

グローバル条件コンテキストキー

CalledViaを条件に組み合わせることでCloudFormationからの実行時はIPアドレスに関係なく操作を行うことが出来ます。

Condition句にaws:CalledViaでCloudFormationを指定します。

AWSTemplateFormatVersion: "2010-09-09"
Description: ""
Parameters: 
  bucketName:
    Description: bucket name (unique).
    Type: String
  bucketExpirationInDays:
    Description: Bucket object expiration date.
    Type: Number
    Default: 5
Resources:
    S3Bucket:
        Type: "AWS::S3::Bucket"
        Properties:
            BucketName: !Ref bucketName
            PublicAccessBlockConfiguration:
              BlockPublicAcls: true
              BlockPublicPolicy: true
              IgnorePublicAcls: true
              RestrictPublicBuckets: true
            LifecycleConfiguration: 
                Rules: 
                  - 
                    Id: "auto-delete"
                    Status: "Enabled"
                    ExpirationInDays: !Ref bucketExpirationInDays
    S3BucketPolicy:
        Type: "AWS::S3::BucketPolicy"
        Properties:
            Bucket: !Ref S3Bucket
            PolicyDocument: 
                Version: "2012-10-17"
                Statement: 
                  - 
                    Effect: "Deny"
                    Principal: "*"
                    Action: "s3:*"
                    Resource: 
                      - !Sub "arn:aws:s3:::${S3Bucket}"
                      - !Sub "arn:aws:s3:::${S3Bucket}/*"
                    Condition: 
                        StringNotEquals:
                          "aws:CalledVia":
                            - "cloudformation.amazonaws.com"
                        NotIpAddress: 
                          "aws:SourceIp": 
                            - "111.222.333.444/32"

スタック更新の動作確認

パラメータの変更を再度試してみます。

先程は失敗していた、更新に成功しました。

パラメータの更新結果も確認出来ました。

まとめ

S3バケットポリシーでIPアドレス制限を行う設定はよく見るのですが、例外を追加するパターンについてはあまり記載がなく今回検証しました。
同じ問題でエラーになった方の参考になれば幸いです。