Learning how to execute a Bash script from CloudFormation

Brad Simonin
4 min readMar 4, 2020

For those learning AWS/AWS CLI, CloudFormation is a tool for building infrastructure with AWS.

Here is a very simple document on how to use CloudFormation to build an AWS EC2 Linux instance and execute a bash script from CloudFormation against the newly created Linux instance. This is a counterpart document to Learning how to execute a Bash script from Terraform.

Executing a bash script from CloudFormation can be used to configure the newly created server or for any other purpose.

The example below will show how to execute a script named setup-lnxcfg-user; this bash script will prepare the newly created Linux instance for Ansible playbook execution from an Ansible controller server. The script creates a new login id named lnxcfg, sets up ssh-keys, and sudo access on the new instance. This instance will be an Ansible client of an Ansible controller server; using Ansible is beyond the scope of this document, however, learning how to execute a bash script from CloudFormation will be very useful.

The setup-lnxcfg-user bash script is as follows:

setup-lnxcfg-user:

#!/bin/bash
# setup-lnxcfg-user
# create lnxcfg user for Ansible automation
# and configuration management.
# create lnxcfg user
getentUser=$(/usr/bin/getent passwd lnxcfg)
if [ -z "$getentUser" ]
then
echo "User lnxcfg does not exist. Will Add..."
/usr/sbin/groupadd -g 2002 lnxcfg
/usr/sbin/useradd -u 2002 -g 2002 -c "Ansible Automation Account" -s /bin/bash -m -d /home/lnxcfg lnxcfg
echo "lnxcfg:<PUT IN YOUR OWN lnxcfg PASSWORD FROM ANSIBLE SERVER>" | /usr/sbin/chpasswd mkdir -p /home/lnxcfg/.ssh
fi
# setup ssh authorization keys for Ansible access
echo "setting up ssh authorization keys..."
cat << 'EOF' >> /home/lnxcfg/.ssh/authorized_keys
<PUT IN YOUR OWN lnxcfg .ssh/id_rsa.pub SSH RSA KEY FROM ANSIBLE SERVER>
EOF
chown -R lnxcfg:lnxcfg /home/lnxcfg/.ssh
chmod 700 /home/lnxcfg/.ssh
# setup sudo access for Ansible
if [ ! -s /etc/sudoers.d/lnxcfg ]
then
echo "User lnxcfg sudoers does not exist. Will Add..."
cat << 'EOF' > /etc/sudoers.d/lnxcfg
User_Alias ANSIBLE_AUTOMATION = lnxcfg
Defaults:ANSIBLE_AUTOMATION !requiretty
ANSIBLE_AUTOMATION ALL=(ALL) NOPASSWD: ALL
EOF
chmod 400 /etc/sudoers.d/lnxcfg
fi
# end of script

The CloudFormation configuration file is below as example.yml.

A CloudFormation template can have arguments passed to the template via the ParameterKey and ParameterValue arguments. Within the Parameters section of the template, default values can be assigned for each parameter. Arguments not passed to the template will become the default value if a default value has been defined. Those arguments that are passed to the template override any default value.

Within the CloudFormation code below there is a parameter named UserData. The UserData can be used to pass the bash script which needs to be encoded as base64 data to CloudFormation. We can do this by passing a parameter:

ParameterKey=UserData,ParameterValue=$(base64 -w0 setup-lnxcfg-user)

base64 above is a Linux command which will encode/decode data and print to the Linux standard output. The -w option is to wrap encoded lines after the cols character (default 76). Using zero (as in -w0) will disable line wrapping of the setup-lnxcfg-user bash script. Using the syntax from above the parameter UserData will now have the value of the bash script which has been encoded as base64.

After the server is finished provisioning and the bash script has been executed an Ansible controller server will be able connect to the new Linux instance using the lnxcfg userid and configure the new instance.

example.yml CloudFormation:

---
AWSTemplateFormatVersion: "2010-09-09"
Description: "Example of executing bash script"
Parameters:
UserData:
Type: String
Default: ""
SecurityGroups:
Type: CommaDelimitedList
Default: sg-<PUT IN YOUR VPC SECURITY GROUP>
ImageId:
Type: String
Default: ami-0b898040803850657
# ami-0b898040803850657 is the free Amazon Linux 2 AMI
# for the us-east-1.
AvailabilityZone:
Type: String
Default: us-east-1a
SubnetId:
Type: String
Default: subnet-<PUT IN YOUR VPC SUBNET>
InstanceType:
Type: String
Default: t2.micro
KeyName:
Type: String
Default: <PUT IN NAME OF YOUR AWS PEM KEY>
Resources:
MyEC2Instance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: !Ref ImageId
KeyName: !Ref KeyName
AvailabilityZone: !Ref AvailabilityZone
SubnetId: !Ref SubnetId
SecurityGroupIds: !Ref SecurityGroups
Tags:
- Key: Name
Value: !Ref "AWS::StackName"
InstanceType: !Ref InstanceType
# lets add an ebs device when we create the instance
BlockDeviceMappings:
- DeviceName: /dev/xvdb
Ebs:
VolumeSize: 8
VolumeType: "gp2"
DeleteOnTermination: "true"
UserData: !Ref UserData
Outputs:
MyEC2InstancePublicIP:
Value: !GetAtt MyEC2Instance.PublicIp
MyEC2InstancePrivateIP:
Value: !GetAtt MyEC2Instance.PrivateIp
MyEC2InstanceID:
Value: !Ref MyEC2Instance

To execute the CloudFormation from a Linux command line with the AWS CLI installed execute:

aws cloudformation create-stack --stack-name example --template-body file://example.yml --parameters ParameterKey=UserData,ParameterValue=$(base64 -w0 setup-lnxcfg-user)

To execute the CloudFormation from a Linux command line and override all the parameters along with the UserData parameter execute:

aws cloudformation create-stack --stack-name example --template-body file://example.yml --parameters
ParameterKey=KeyName,ParameterValue=<yourKeyName>
ParameterKey=SecurityGroups,ParameterValue=sg-<yourSecurityGroups> ParameterKey=AvailabilityZone,ParameterValue=us-east-1a ParameterKey=SubnetId,ParameterValue=subnet-<yourSubnetId> ParameterKey=InstanceType,ParameterValue=t2.micro ParameterKey=ImageId,ParameterValue=ami-0b898040803850657
ParameterKey=UserData,ParameterValue=$(base64 -w0 setup-lnxcfg-user)

Executing a bash script from CloudFormation can be a very easy and useful.

--

--