Dynamic References to Specify Secret Manager Values in AWS Cloudformation

ぃ、小莉子 提交于 2019-12-03 08:36:48

I am not sure why this is not expanded correctly for you. However, you probably do not want CFN to expand your secret in the user data because the password would be embedded in the base64 encoded user data script which is visible in the EC2 console.

Instead you should take advantage of the fact that you have a script that executes on the host and call secrets manager at script execution time (warning untested):

"SampleLaunchConfig": {
        "Type": "AWS::AutoScaling::LaunchConfiguration",
         "Properties": {
            "ImageId": {
                "Fn::FindInMap": [
                    "AWSRegionArch2AMI",
                    {
                        "Ref": "AWS::Region"
                    },
                    "AMI"
                ]
            },
            "UserData": {
                "Fn::Base64": {
                    "Fn::Join": [
                        "",
                        [
                            "#!/bin/bash -xe\n",
                            "yum update -y\n",
                            "yum install -y jq\n",
                            !Sub "useradd -p `aws --region ${AWS::Region} secretsmanager get-secret-value --secret-id Credentials --query SecretString --output text | jq -r .passwordKey` `aws --region ${AWS::Region} secretsmanager get-secret-value --secret-id Credentials --query SecretString --output text | jq -r .userName`\n",
                            "\n"
                        ]
                    ]
                }
            }
    }
}

This is not ideal since it expands the password on the command line. It might be made more secure by putting the password in a file first and reading it from there and then shredding the file.

It seems that {{resolve:...}} dynamic references are only expanded in certain contexts within a template.

There is no precise information in the AWS docs about exactly where in a template you can use these references. The current wording with regard to {{resolve:secretsmanager:...}} says:

"The secretsmanager dynamic reference can be used in all resource properties"

However this is contradicted by your example, and I've also observed dynamic references failing to resolve inside of CloudFormation::Init data.

I have an active Support case open with AWS about this, they have agreed that the behaviour of dynamic references is inadequately documented. I'll update this answer as I learn more.

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#dynamic-references-secretsmanager

I can confirm that @JoeB's "warning untested" answer works, with the caveat that the machine in question must have permission to read the secret. You'll need something like

  MyInstancePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: MyPolicy
      PolicyDocument:
        Version: 2012-10-17
        Statement:
          -
            Effect: Allow
            Action:
              - secretsmanager:GetSecretValue
            Resource: !Join
              - ''
              - - !Sub "arn:aws:secretsmanager:${AWS::Region}:"
                - !Sub "${AWS::AccountId}:secret:Credentials-??????"

Note a couple of things:

Unlike S3 buckets, you can't do arn:aws:secretsmanager:::secret.... If you don't want to explicitly declare the region and account, you need to use a wildcard. Buried at the bottom of Using Identity-based Policies (IAM Policies) for Secrets Manager

If you don't care about the region or account that owns a secret, you must specify a wildcard character * (not an empty field) for the region and account ID number fields of the ARN.

Perhaps less important, and less likely to cause unexpected failures, but still worth note:

Using '??????' as a wildcard to match the 6 random characters that are assigned by Secrets Manager avoids a problem that occurs if you use the '*' wildcard instead. If you use the syntax "another_secret_name-*", it matches not just the intended secret with the 6 random characters, but it also matches "another_secret_name-a1b2c3". Using the '??????' syntax enables you to securely grant permissions to a secret that doesn't yet exist.

Variant on @JoeB's answer:

Resources:
  SampleLaunchConfig:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      ImageId: !FindInMap [ AWSRegionArch2AMI, !Ref: 'AWS::Region', AMI ]
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe
          exec > >(tee /var/log/user-data.log | logger -t user-data) 2>&1

          yum update -y
          yum install -y jq

          username=$(aws secretsmanager get-secret-value --secret-id Credentials \
                                                         --query SecretString \
                                                         --region ${AWS::Region} --output text | jq -r .userName)
          password=$(aws secretsmanager get-secret-value --secret-id Credentials \
                                                         --query SecretString \
                                                         --region ${AWS::Region} --output text | jq -r .passwordKey)
          useradd -p "$password" $username

UserData in JSON is painful to watch these days.

I've also added a technique to split out the UserData logic to it's own log file, as otherwise it goes in cloud-init.log, which is also painful to read.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!