Динамические ссылки для указания значений 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 без указания версий параметров.
Узнать больше
У меня есть шаблон cfn, аналогичный приведенному ниже.
Parameters:
ASecret:
Type: String
Default: '{{resolve:secretsmanager:ASecret}}'
-
-
-
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
echo ${ASecret}