通过 for 循环创建多个 S3 存储桶的 CloudFormation FindInMap 模板错误

问题描述

我正在尝试通过执行此 shell 脚本在一个 for 循环中创建多达 50 个具有不同(但相似)存储桶策略的 S3 存储桶:

#!/bin/bash

set -e

aws cloudformation validate-template --template-body file://$1

for i in $BUCKETS
do
  aws cloudformation deploy --stack-name $i --template-file $1 --parameter-overrides \
        BucketName=$i \
        DefaultPrincipal=$DEFAULT_PRINCIPAL \
        --capabilities CAPABILITY_NAMED_IAM --no-fail-on-empty-changeset || exit 1
done

环境变量 BUCKETS 的值为:“bucket1 bucket2 bucket3 bucket4 bucket5 bucket6”。

对于每个存储桶,不同的用户应该具有访问权限。大多数存储桶只有 DefaultPrincipal,bucket1 有另外 4 个用户,bucket2 有另外 1 个用户(如下面的 UserMap 所示)。

我的第一个方法是这样的,这已经有点丑陋了:

AWstemplateFormatVersion: 2010-09-09
Description: Create S3 bucket
Parameters:
  BucketName:
    Type: String
    Description: Bucket name
  DefaultPrincipal:
    Description: Default principal ARN
    Type: String
Mappings:
  UserMap:
    bucket1:
      users:
        - 'user1-abc'
        - 'user2-xyz'
        - 'user3-qwe'
        - 'user4-asd'
    bucket3:
      users:
        - 'user5-yxc'
Conditions:
  FirstUserCondition: !Or
    - !Equals
      - !Ref BucketName
      - bucket1
    - !Equals
      - !Ref BucketName
      - bucket3
  SecondUserCondition: !Equals
    - !Ref BucketName
    - bucket1
  ThirdUserCondition: !Equals
    - !Ref BucketName
    - bucket1
  FourthUserCondition: !Equals
    - !Ref BucketName
    - bucket1
Resources:
  S3BucketTest:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref BucketName
  S3BucketPolicyTest:
    Type: 'AWS::S3::BucketPolicy'
    DependsOn: S3BucketTest
    Properties:
      Bucket: !Ref BucketName
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: TestPolicy
            Action:
              - 's3:PutObject'
              - 's3:Getobject'
              - 's3:AbortMultipartUpload'
              - 's3:List*'
            Effect: Allow
            Resource:
              - !Join
                - ':'
                - - 'arn:aws:s3::'
                  - !Ref BucketName
              - !Join
                - /
                - - !Join
                    - ':'
                    - - 'arn:aws:s3::'
                      - !Ref BucketName
                  - '*'
            Principal:
              AWS:
                - !ImportValue
                  DefaultPrincipal
                - !If
                  - FirstUserCondition
                  - !Sub
                    - >-
                      arn:aws:iam::${AccountId}:user/${UserName}
                    - AccountId: !Ref AWS::AccountId
                      UserName: !Select
                        - 0
                        - !FindInMap
                          - UserMap
                          - !Ref BucketName
                          - users
                  - !Ref 'AWS::Novalue'
                - !If
                  - SecondUserCondition
                  - !Sub
                    - >-
                      arn:aws:iam::${AccountId}:user/${UserName}
                    - AccountId: !Ref AWS::AccountId
                      UserName: !Select
                        - 1
                        - !FindInMap
                          - UserMap
                          - !Ref BucketName
                          - users
                  - !Ref 'AWS::Novalue'
                - !If
                  - ThirdUserCondition
                  - !Sub
                    - >-
                      arn:aws:iam::${AccountId}:user/${UserName}
                    - AccountId: !Ref AWS::AccountId
                      UserName: !Select
                        - 2
                        - !FindInMap
                          - UserMap
                          - !Ref BucketName
                          - users
                  - !Ref 'AWS::Novalue'
                - !If
                  - FourthUserCondition
                  - !Sub
                    - >-
                      arn:aws:iam::${AccountId}:user/${UserName}
                    - AccountId: !Ref AWS::AccountId
                      UserName: !Select
                        - 3
                        - !FindInMap
                          - UserMap
                          - !Ref BucketName
                          - users
                  - !Ref 'AWS::Novalue'

然而,这总是导致以下错误

An error occurred (ValidationError) when calling the CreateChangeSet operation: Template error: Unable to get mapping for UserMap::bucket2::users
An error occurred (ValidationError) when calling the CreateChangeSet operation: Template error: Fn::Select  cannot select nonexistent value at index 1

这个怎么解决?尤其是第一个错误...

解决方法

由于这个错误,我不得不通过在 UserMap 中列出所有存储桶和用户字符串来做一个非常丑陋的解决方法:

AWSTemplateFormatVersion: 2010-09-09
Description: Create S3 bucket
Parameters:
  BucketName:
    Type: String
    Description: Bucket name
  DefaultPrincipal:
    Description: Default principal ARN
    Type: String
Mappings:
  UserMap:
    bucket1:
      users:
        - 'user1-abc'
        - 'user2-xyz'
        - 'user3-qwe'
        - 'user4-asd'
    bucket2:
      users:
        - ''
        - ''
        - ''
        - ''
    bucket3:
      users:
        - 'user5-yxc'
        - ''
        - ''
        - ''
    bucket4:
      users:
        - ''
        - ''
        - ''
        - ''
    bucket5:
      users:
        - ''
        - ''
        - ''
        - ''
    bucket6:
      users:
        - ''
        - ''
        - ''
        - ''
Conditions:
  FirstUserCondition: !Not
    - !Equals
      - ''
      - !Select
        - 0
        - !FindInMap
          - UserMap
          - !Ref BucketName
          - users
  SecondUserCondition: !Not
    - !Equals
      - ''
      - !Select
        - 1
        - !FindInMap
          - UserMap
          - !Ref BucketName
          - users
  ThirdUserCondition: !Not
    - !Equals
      - ''
      - !Select
        - 2
        - !FindInMap
          - UserMap
          - !Ref BucketName
          - users
  FourthUserCondition: !Not
    - !Equals
      - ''
      - !Select
        - 3
        - !FindInMap
          - UserMap
          - !Ref BucketName
          - users
Resources:
  S3BucketTest:
    Type: 'AWS::S3::Bucket'
    Properties:
      BucketName: !Ref BucketName
  S3BucketPolicyTest:
    Type: 'AWS::S3::BucketPolicy'
    DependsOn: S3BucketTest
    Properties:
      Bucket: !Ref BucketName
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          - Sid: TestPolicy
            Action:
              - 's3:PutObject'
              - 's3:GetObject'
              - 's3:AbortMultipartUpload'
              - 's3:List*'
            Effect: Allow
            Resource:
              - !Join
                - ':'
                - - 'arn:aws:s3::'
                  - !Ref BucketName
              - !Join
                - /
                - - !Join
                    - ':'
                    - - 'arn:aws:s3::'
                      - !Ref BucketName
                  - '*'
            Principal:
              AWS:
                - !ImportValue
                  DefaultPrincipal
                - !If
                  - FirstUserCondition
                  - !Sub
                    - >-
                      arn:aws:iam::${AccountId}:user/${UserName}
                    - AccountId: !Ref AWS::AccountId
                      UserName: !Select
                        - 0
                        - !FindInMap
                          - UserMap
                          - !Ref BucketName
                          - users
                  - !Ref 'AWS::NoValue'
                - !If
                  - SecondUserCondition
                  - !Sub
                    - >-
                      arn:aws:iam::${AccountId}:user/${UserName}
                    - AccountId: !Ref AWS::AccountId
                      UserName: !Select
                        - 1
                        - !FindInMap
                          - UserMap
                          - !Ref BucketName
                          - users
                  - !Ref 'AWS::NoValue'
                - !If
                  - ThirdUserCondition
                  - !Sub
                    - >-
                      arn:aws:iam::${AccountId}:user/${UserName}
                    - AccountId: !Ref AWS::AccountId
                      UserName: !Select
                        - 2
                        - !FindInMap
                          - UserMap
                          - !Ref BucketName
                          - users
                  - !Ref 'AWS::NoValue'
                - !If
                  - FourthUserCondition
                  - !Sub
                    - >-
                      arn:aws:iam::${AccountId}:user/${UserName}
                    - AccountId: !Ref AWS::AccountId
                      UserName: !Select
                        - 3
                        - !FindInMap
                          - UserMap
                          - !Ref BucketName
                          - users
                  - !Ref 'AWS::NoValue'

有没有更好的方法来做到这一点?