Dynamic References to Specify Secret Manager Values in AWS Cloudformation - amazon-web-services

Is there anyway we can pass dynamic references to Secret Manager to AWS Launch Config User Data?
Here is the code snippet I tried:
"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"
]
]
}
}
}
}
Seems error in getting the useradd: invalid user name '{{resolve:secretsmanager:Credentials:SecretString:userName}}'
How can I pass Secret Manager secret value to cloudformation user data ?

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 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.

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.

The proper way to do it is to call secret manager to get your data, here is how I succeded doing it :
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'

I have a cfn template similar to below working
Parameters:
ASecret:
Type: String
Default: '{{resolve:secretsmanager:ASecret}}'
-
-
-
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
echo ${ASecret}

AWS CloudFormation enhances the existing dynamic referencing of AWS Systems Manager Parameter Store parameters in CloudFormation templates. You can now reference the latest Systems Manager parameter values in CloudFormation templates without specifying parameter versions.
See more
https://aws.amazon.com/about-aws/whats-new/2021/04/now-reference-latest-aws-systems-manager-parameter-values-in-aws-cloudformation-templates-without-specifying-parameter-versions/

Related

AWS Cloudformation: How to split a string on 'NewLine' character

I have a set of strings in Cloudformation, this is how they appear on the command line:
arn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app0/yyy
arn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app4/yyy
arn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app1/yyy
arn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app2/yyy
arn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app3/yyy
I need to split on '\n' (the new line character)
The strings were generated by CF's CommandRunner:
GetElbv2Arns:
Type: AWSUtility::CloudFormation::CommandRunner
Properties:
Role: InfrastructureManagement
LogGroup: !Ref LogGroup
SubnetId: !Ref subnetId1
Command:
!Sub
- |
aws elbv2 describe-load-balancers \
--region ${AWS::Region} \
--query "LoadBalancers[].[LoadBalancerArn]" \
--output text \
> /command-output.txt
- dummy: ""
Turns out that event though the value started with '\n' as new line, it is actually just '\n' as text by the time CF gets the value. This is what is displayed in CF's Output pane:
arn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app0/yyy\narn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app4/yyy\narn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app1/yyy\narn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app2/yyy\narn:aws:elasticloadbalancing:us-east-1:xxx:loadbalancer/app/app3/yyy
The way to parse the values is to escape the '\':
Outputs:
ARNs1:
Description: "elbv2 Arns-0"
Value:
!Select [0, !Split [ "\\n", !GetAtt GetElbv2Arns.Output] ]

IAM based ssh to EC2 instance using CloudFormation template

I am using an AWS CloudFormation template for IAM role-based access to an EC2 instance.
I getting permission denied error while running the template, and I am not able to access the EC2 machine with a username without a pem file.
Instance:
Type: 'AWS::EC2::Instance'
Metadata:
'AWS::CloudFormation::Init':
config:
files:
/opt/authorized_keys_command.sh:
content: >
#!/bin/bash -e
if [ -z "$1" ]; then
exit 1
fi
SaveUserName="$1"
SaveUserName=${SaveUserName//"+"/".plus."}
SaveUserName=${SaveUserName//"="/".equal."}
SaveUserName=${SaveUserName//","/".comma."}
SaveUserName=${SaveUserName//"#"/".at."}
aws iam list-ssh-public-keys --user-name "$SaveUserName" --query
"SSHPublicKeys[?Status == 'Active'].[SSHPublicKeyId]" --output
text | while read KeyId; do
aws iam get-ssh-public-key --user-name "$SaveUserName" --ssh-public-key-id "$KeyId" --encoding SSH --query "SSHPublicKey.SSHPublicKeyBody" --output text
done
mode: '000755'
owner: root
group: root
/opt/import_users.sh:
content: >
#!/bin/bash
aws iam list-users --query "Users[].[UserName]" --output text |
while read User; do
SaveUserName="$User"
SaveUserName=${SaveUserName//"+"/".plus."}
SaveUserName=${SaveUserName//"="/".equal."}
SaveUserName=${SaveUserName//","/".comma."}
SaveUserName=${SaveUserName//"#"/".at."}
if id -u "$SaveUserName" >/dev/null 2>&1; then
echo "$SaveUserName exists"
else
#sudo will read each file in /etc/sudoers.d, skipping file names that end in ?~? or contain a ?.? character to avoid causing problems with package manager or editor temporary/backup files.
SaveUserFileName=$(echo "$SaveUserName" | tr "." " ")
/usr/sbin/adduser "$SaveUserName"
echo "$SaveUserName ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/$SaveUserFileName"
fi
done
mode: '000755' owner: root group: root
/etc/cron.d/import_users:
content: |
*/10 * * * * root /opt/import_users.sh
mode: '000644' owner: root
group: root
/etc/cfn/cfn-hup.conf:
content: !Sub |
[main]
stack=${AWS::StackId}
region=${AWS::Region}
interval=1
mode: '000400' owner: root group: root
/etc/cfn/hooks.d/cfn-auto-reloader.conf:
content: !Sub >
[cfn-auto-reloader-hook]
triggers=post.update
path=Resources.Instance.Metadata.AWS::CloudFormation::Init
action=/opt/aws/bin/cfn-init --verbose
--stack=${AWS::StackName} --region=${AWS::Region}
--resource=Instance
runas=root
commands:
a_configure_sshd_command:
command: >-
sed -i 's:#AuthorizedKeysCommand none:AuthorizedKeysCommand
/opt/authorized_keys_command.sh:g' /etc/ssh/sshd_config
b_configure_sshd_commanduser:
command: >-
sed -i 's:#AuthorizedKeysCommandUser
nobody:AuthorizedKeysCommandUser nobody:g' /etc/ssh/sshd_config
c_import_users:
command: ./import_users.sh
cwd: /opt
services:
sysvinit:
cfn-hup:
enabled: true
ensureRunning: true
files:
- /etc/cfn/cfn-hup.conf
- /etc/cfn/hooks.d/cfn-auto-reloader.conf
sshd:
enabled: true
ensureRunning: true
commands:
- a_configure_sshd_command
- b_configure_sshd_commanduser
'AWS::CloudFormation::Designer':
id: 85ddeee0-0623-4f50-8872-1872897c812f
Properties:
ImageId: !FindInMap
- RegionMap
- !Ref 'AWS::Region'
- AMI
IamInstanceProfile: !Ref InstanceProfile
InstanceType: t2.micro
UserData:
'Fn::Base64': !Sub >
#!/bin/bash -x
/opt/aws/bin/cfn-init --verbose --stack=${AWS::StackName}
--region=${AWS::Region} --resource=Instance
/opt/aws/bin/cfn-signal --exit-code=$? --stack=${AWS::StackName}
--region=${AWS::Region} --resource=Instance
This User Data script will configure a Linux instance to use password authentication.
While the password here is hard-coded, you could obtain it in other ways and set it to the appropriate value.
#!
echo 'secret-password' | passwd ec2-user --stdin
sed -i 's|[#]*PasswordAuthentication no|PasswordAuthentication yes|g' /etc/ssh/sshd_config
systemctl restart sshd.service

Aws cloudformation get private IP address of one ec2 instance to another ec2 instance

I created two Amazon EC2 instances in AWS CloudFormation using a YAML template. I want to take private IP address of one EC2 instance to the other EC2 instance which has a public IP address. As per AWS documentation we can do that using !GetAtt JMeterServer1Instance.PrivateIp
I want to know under which section of the public EC2 instance I should add that in the template. (Please consider this is a YAML template.)
How do I check that we have received it?
It appears that your requirement is:
Create two instances in a CloudFormation template
In the User Data for Instance-A, refer to Instance-B
This is quite simple. First, define that Instance-B DependsOn Instance-A to ensure the creation of Instance-A before Instance-B.
Then, in the User Data for Instance-B, refer to Instance-A:
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash
echo "${InstanceA.PrivateIp}" >foo
A 'better' method would be to use DNS names with a Hosted Zone for VPC in Route 53. This would create a DNS zone for the VPC, then define a DNS name that can be resolved locally. Link it to Instance-B and then Instance-A could refer to Instance-B by DNS name rather than IP address. This allows the DNS name to point to a different instance in future if desired, and creates less dependencies between Instance-A and Instance-B. (But, admittedly, more setup.)
As per the AWS document
Fn::GetAtt
will do the trick here.
My case is:
EC2Instance001 needs to be created 1st
EC2Instance002 needs to use IP of EC2Instance001.
At EC2Instance002 instance is created with two specific settings:
"DependsOn": [ "EC2Instance001"] as I want EC2Instance001 to be created first.
Under Userdata (or metadata) use { "Fn::GetAtt" : [ "EC2Instance001", "PrivateIp" ] } for getting IP of 1st Instance (EC2Instance001)
Here is how I achieved it (EC2Instance002):
---
EC2Instance002:
Type: AWS::EC2::Instance
Metadata:
AWS::CloudFormation::Init:
configSets:
InstallAndRun:
- Install
- Configure
Install:
packages:
yum:
git: []
files:
"/tmp/bootstrap.sh":
content:
Fn::Join:
- ''
- - "#!/bin/bash\n"
- 'set -x
'
- 'echo "============================"
'
- 'sudo hostname >> /tmp/EC2Instance.txt
'
- MASTERIP=
- Fn::GetAtt:
- EC2Instance001
- PrivateIp
- "\n"
- "echo $MASTERIP > masterIP.txt \n"
mode: '755'
owner: ec2-user
group: ec2-user
Configure:
commands:
runBootstrapScript:
command: "./bootstrap.sh"
cwd: "/tmp"
DependsOn:
- EC2Instance001
Properties:
InstanceType:
Ref: InstanceType
SecurityGroups:
- Ref: InstanceSecurityGroup
KeyName:
Ref: KeyName
UserData:
Fn::Base64:
Fn::Join:
- ''
- - "#!/bin/bash -xe\n"
- 'yum install -y aws-cfn-bootstrap
'
- "# Install the files and packages from the metadata\n"
- "/opt/aws/bin/cfn-init -v"
- " --stack "
- Ref: AWS::StackName
- " --resource EC2Instance002 "
- " --configsets InstallAndRun "
- " --region "
- Ref: AWS::Region
- "\n"
ImageId:
Fn::FindInMap:
- AWSRegionArch2AMI
- Ref: AWS::Region
- Fn::FindInMap:
- AWSInstanceType2Arch
- Ref: InstanceType
- Arch
You can see that under Metadata I am capturing IP of EC2Instance001 instance in variable $MASTERIP.
NOTE: Same line in JSON will be written as:
"MASTERIP=",{ "Fn::GetAtt" : [ "EC2Instance001", "PrivateIp" ] }, "\n",
It depends on what you'd like to with the private IP on the other machine.
If you'd like to use it in a script on the other VM, pass it down in the user data script like in this example: UserData script with Resource Attribute CloudFormation
The example on the link is showing the attribute of a NetworkInterface instead of an instance attribute, but it's the same with !GetAtt JMeterServer1Instance.PrivateIp

Cloudformation: Outputs - Can you return a value from a file

Cloudformation appears to have an "Outputs" section where you can have a value referenced for other stacks, or to display back to the user, etc.
The limited doc is here.
Is it possible to use this to make the contents of a file available?
e.g. I've got a Jenkins install where the initial admin password is stored within:
/var/lib/jenkins/secrets/initialAdminPassword
I'd love to have that value available after deploying our Jenkins Cloudformation stack without having to then SSH into the server.
Is this possible with the outputs section, or any other way with cloudformation templates?
The Outputs section Cloud Formation template are meant to help you find your resource easily.
For any resource you create, you can output the properties defined in Fb::GetAtt Documentation.
For example, to get the connection string for the RDS Instance which was created using Cloud formation template, you can use the following
"Outputs" : {
"JDBCConnectionString": {
"Description" : "JDBC connection string for the master database",
"Value" : { "Fn::Join": [ "",
[ "jdbc:mysql://",
{ "Fn::GetAtt": [ "MyDatabase", "Endpoint.Address" ] },
":",
{ "Fn::GetAtt": [ "MyDatabase", "Endpoint.Port" ] },
"/",
{ "Ref": "MyDBName" }]
]}
}
}
It is not possible to output contents from a file. Moreover, outputs are visible to all the users having access to your AWS account. So, having password as an output is not recommended.
I would suggest you to upload your secrets to a private S3 bucket after the cloud formation create stack operation is successful and download the secrets whenever required.
Hope this helps.
I know this question has been answered but I wanted to offer another solution.
I found myself wanting to do exactly what you (the, OP) were trying to do: use Cloudformation to install Jenkins on an EC2 instance and then print the initial admin password to the Cloudformation outputs.
I ended up working around trying to read the file with the password and instead used the Jenkins CLI from the UserData section to update the admin user with a password that I specified.
Here’s what I did (showing snippets from the template in YAML):
Added a parameter to the template inputs to get the password:
Parameters:
KeyName:
ConstraintDescription: Must be the name of an existing EC2 KeyPair.
Description: Name of an existing EC2 KeyPair for SSH access
Type: AWS::EC2::KeyPair::KeyName
PassWord:
AllowedPattern: '[-_a-zA-Z0-9]*'
ConstraintDescription: A complex password at least eight chars long with alphanumeric characters, dashes and underscores.
Description: Password for the admin account
MaxLength: 64
MinLength: 8
NoEcho: true
Type: String
In the UserData section, I used the PassWord parameter in a call to the jenkins-cli to update the admin account :
UserData: !Base64
Fn::Join:
- ''
- - "#!/bin/bash -x\n"
- "exec > /tmp/user-data.log 2>&1\nunset UCF_FORCE_CONFFOLD\n"
- "export UCF_FORCE_CONFFNEW=YES\n"
- "ucf --purge /boot/grub/menu.lst\n"
- "export DEBIAN_FRONTEND=noninteractive\n"
- "echo \"deb http://pkg.jenkins-ci.org/debian binary/\" > /etc/apt/sources.list.d/jenkins.list\n"
- "wget -q -O jenkins-ci.org.key http://pkg.jenkins-ci.org/debian-stable/jenkins-ci.org.key\n\
apt-key add jenkins-ci.org.key\n"
- "apt-get update\n"
- "apt-get -o Dpkg::Options::=\"--force-confnew\" --force-yes -fuy upgrade\n"
- "apt-get install -y python-pip\n"
- "pip install https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz\n"
- "apt-get install -y nginx\n"
- "apt-get install -y openjdk-8-jdk\n"
- "apt-get install -y jenkins\n"
- "# Wait for Jenkins to Set Up\n
- until [ $(curl -o /dev/null --silent\
\ --head --write-out '%{http_code}\n' http://localhost:8080) -eq 403\
\ ]; do sleep 1; done\nsleep 10\n# Change the password for the admin\
\ account\necho 'jenkins.model.Jenkins.instance.securityRealm.createAccount(\"\
admin\", \""
- !Ref 'PassWord'
- "\")' | java -jar /var/cache/jenkins/war/WEB-INF/jenkins-cli.jar -s\
\ \"http://localhost:8080/\" -auth \"admin:$(cat /var/lib/jenkins/secrets/initialAdminPassword)\"\
\ groovy =\n/usr/local/bin/cfn-init --resource=Instance --region="
- !Ref 'AWS::Region'
- ' --stack='
- !Ref 'AWS::StackName'
- "\n"
- "unlink /etc/nginx/sites-enabled/default\nsystemctl reload nginx\n"
- /usr/local/bin/cfn-signal -e $? --resource=Instance --region=
- !Ref 'AWS::Region'
- ' --stack='
- !Ref 'AWS::StackName'
- "\n"
Using this method, when Jenkins starts up, I don’t get the “enter the initial admin password” screen but instead I get a screen where i can just log in as admin with the password used in the parameters.
In terms of adding something to the outputs from a file on the system, I think there is a way to do it using WaitCondition and and passing data back using a cfn-signal command. But once I figured out that all I needed to do was set the password I didn’t pursue the WaitCondition method.
Again, I know you have your answer, but I wanted to share in case anyone else happens to be searching for a way to do this. This way worked for me! :D

Fn::Sub with a Mapping in a literal block to specify the user data script

Ok so reading the documentation on the The intrinsic function Fn::Sub. I can use a literal block to specify the user data script.
UserData:
"Fn::Base64":
!Sub |
#!/bin/bash -xe
yum update -y aws-cfn-bootstrap
/opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource LaunchConfig --configsets wordpress_install --region ${AWS::Region}
/opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource WebServerGroup --region ${AWS::Region}
and I can uses a mapping to substitute the ${Domain} variable with the resulting value from the Ref function.
Name: !Sub
- www.${Domain}
- { Domain: !Ref RootDomainName }
But what if I need to use a mapping substitute inside a literal block? Like for example:
"Fn::Base64": !Sub |
<powershell>
Write-host "My Domain is www.${Domain},{ Domain: !Ref RootDomainName }"
</powershell>
This example does not work, and I haven't been unable to find a method that does. Any ideas? The first example makes userdata scripts much easier to write and looks cleaner, but without being able to to use !Ref or !Findinmap it's usefulness is reduced dramatically.
Anyone got any ideas?
Since I arrived to this page through Google, and then found the solution through a different wording here(literally):
How to use !FindInMap in !Sub | userdata section
I'll just add it to save some frustrated searching to others.
Essentially, you have to write your example using the 2nd syntax but a bit more verbosely:
Fn::Base64:
Fn::Sub:
- |+
<powershell>
Write-host "My Domain is www.${Domain}"
</powershell>
- Domain:
Fn::Ref: RootDomainName
You may be able to shorten it a bit, but as the original poster said, mind your commas, quoting and usage of short forms.
P.S.: If the first solution has already served your purpose, you should mark it thus.
In that case you would write it simply as ${RootDomainName}. Local resouces in the same stack can just be mapped by using their resource name.