Динамические ссылки для указания значений Secret Manager в облачной информации AWS

Можно ли в любом случае передать динамические ссылки на Secret Manager в пользовательские данные AWS Launch Config?

Вот фрагмент кода, который я пробовал:

"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",
                                "useradd -p <<pwd>>{{resolve:secretsmanager:Credentials:SecretString:userName}}\n",
                                "\n"
                            ]
                        ]
                    }
                }
        }
    }

Кажется, ошибка при получении useradd: неверное имя пользователя '{{resol:secretsmanager:Credentials:SecretString:userName}}'

Как передать секретное значение Secret Manager в пользовательские данные облачной информации?

7 ответов

Кажется, что {{resolve:...}} динамические ссылки раскрываются только в определенных контекстах в шаблоне.

В документации AWS нет точной информации о том, где именно в шаблоне вы можете использовать эти ссылки. Текущая формулировка в отношении {{resolve:secretsmanager:...}} говорит:

"Динамическую ссылку на секретный менеджер можно использовать во всех свойствах ресурса"

Однако это противоречит вашему примеру, и я также заметил, что динамические ссылки не могут быть разрешены внутри данных CloudFormation::Init.

У меня есть активное дело поддержки, открытое в AWS по этому поводу, они согласились с тем, что поведение динамических ссылок недостаточно документировано. Я обновлю этот ответ, когда узнаю больше.

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

Я не уверен, почему это не правильно для вас. Однако вы, вероятно, не хотите, чтобы CFN раскрывал ваш секрет в пользовательских данных, потому что пароль был бы встроен в скрипт пользовательских данных в кодировке base64, который отображается в консоли EC2.

Вместо этого вам следует воспользоваться тем, что у вас есть скрипт, который выполняется на хосте и диспетчере секретов вызовов во время выполнения скрипта (предупреждение не проверено):

"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"
                        ]
                    ]
                }
            }
    }
}

Это не идеально, так как он расширяет пароль в командной строке. Его можно сделать более безопасным, сначала поместив пароль в файл, а затем прочитав его, а затем уничтожив файл.

Я могу подтвердить, что ответ @JoeB "Предупреждение не проверено" работает с оговоркой, что рассматриваемая машина должна иметь разрешение на чтение секрета. Вам понадобится что-то вроде

  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-??????"

Обратите внимание на пару вещей:

В отличие от ковшей S3, вы не можете сделать arn:aws:secretsmanager:::secret..., Если вы не хотите явно указывать регион и учетную запись, вам нужно использовать подстановочный знак. Похоронен в нижней части Использование политик на основе идентификаторов (IAM Policies) для Secrets Manager

Если вас не волнует регион или учетная запись, владеющая секретом, вы должны указать подстановочный знак * (не пустое поле) для полей региона и номера идентификатора учетной записи ARN.

Возможно, менее важно, и менее вероятно, приведет к неожиданным сбоям, но все же стоит отметить:

С помощью '??????' в качестве подстановочного знака, соответствующего 6 случайным символам, которые назначены Secrets Manager, позволяет избежать проблемы, которая возникает, если вместо этого использовать подстановочный знак '*'. Если вы используете синтаксис "another_secret_name-*", он сопоставляет не только предполагаемый секрет с 6 случайными символами, но также совпадает с "another_secret_name-a1b2c3". С использованием '??????' Синтаксис позволяет безопасно предоставлять разрешения для секрета, который еще не существует.

Правильный способ сделать это - вызвать секретного менеджера, чтобы получить ваши данные, вот как мне это удалось:

SftpCredsUserPasswordSecret:
    Type: 'AWS::SecretsManager::Secret'
    Properties:
      Name: 'sftp-creds-user-password-secret'
      Description: DB Credentials
      GenerateSecretString:
        SecretStringTemplate: '{"username":"sftpuser"}'
        GenerateStringKey: "password"
        PasswordLength: 30
        ExcludePunctuation: true

  Ec2SftpRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument: # Tells that Ec2 can assume this role
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Principal:
              Service:
                - ec2.amazonaws.com
      Policies: # Tells that you can call for the secret value
        - PolicyName: "root"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action: "secretsmanager:GetSecretValue"
                Resource: !Ref SftpCredsUserPasswordSecret
      RoleName: 'role-ec2-sftp'

  Ec2SftpIamInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      InstanceProfileName:  'instance-profile-ec2-sftp'
      Roles:
        - !Ref Ec2SftpRole

  Ec2Sftp:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: "ami-06ce3edf0cff21f07"
      InstanceType: t2.micro
      SecurityGroupIds:
        - !ImportValue SgBridgeId
        - !ImportValue SgSftpId
      SubnetId: !ImportValue PublicSubnetAZbId
      KeyName: !Ref KeyName
      UserData:
        Fn::Base64: !Sub
        - |
          #!/bin/bash

          # Get Variables
          secret=`aws --region ${AWS::Region} secretsmanager get-secret-value --secret-id ${secretRef} --query SecretString --output text`
          sftpuser=`echo "$secret" | sed -n 's/.*"username":["]*\([^(",})]*\)[",}].*/\1/p'`
          sftppassword=`echo "$secret" | sed -n 's/.*"password":["]*\([^(",})]*\)[",}].*/\1/p'`

          # Create Sftp User
          adduser $sftpuser
          echo "$sftpuser:$sftppassword" | chpasswd

          # Configure sftp connection
          echo "" >> /etc/ssh/sshd_config
          echo "Match User $sftpuser" >> /etc/ssh/sshd_config
          echo "    PasswordAuthentication yes" >> /etc/ssh/sshd_config
          echo "    ForceCommand /usr/libexec/openssh/sftp-server" >> /etc/ssh/sshd_config

          # Restart the service
          systemctl restart sshd
        -
          secretRef: !Ref SftpCredsUserPasswordSecret
      IamInstanceProfile: !Ref Ec2SftpIamInstanceProfile
      Tags:
        - Key: Name
          Value: 'ec2-sftp'

Вариант ответа @JoeB:

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 в JSON в наши дни тяжело смотреть.

Я также добавил метод разделения логики UserData в собственный файл журнала, иначе он будет помещен в cloud-init.log, который также трудно читать.

AWS CloudFormation улучшает существующие динамические ссылки на параметры хранилища параметров AWS Systems Manager в шаблонах CloudFormation. Теперь вы можете ссылаться на последние значения параметров System Manager в шаблонах CloudFormation без указания версий параметров.

Узнать больше

https://aws.amazon.com/about-aws/whats-new/2021/04/now-reference-latest-aws-systems-manager-parameter-values-in-aws-cloudformation-templates-without-specifying- параметры-версии /

У меня есть шаблон cfn, аналогичный приведенному ниже.

      Parameters:
  ASecret:
    Type: String
    Default: '{{resolve:secretsmanager:ASecret}}'
-
-
-
UserData: 
        Fn::Base64: !Sub |
            #!/bin/bash -xe

            echo ${ASecret}
Другие вопросы по тегам