Dynamic references in CloudFormation to secure strings are very handy, providing a simple way to keep secrets (such as passwords) secure. However, SSM Secure String Parameters are only supported in a limited set of places and Elastic Beanstalk environment variables are not one of them (feature request for adding support). Therefore, if you want to use SSM Secure Strings with a Beanstalk application some extra work is necessary.
There are a few ways to access SSM Secure Strings from a Beanstalk application. One approach is to modify the application to get the secure string itself. But that can be quite a bit of extra work, or even impossible if the application can’t be modified. Another approach is to use an ebextension to pre-process the environment variable values provided in CloudFormation to resolve SSM secure string dynamic references. That way, the application continues to use environment variables removing the need to modify it.
To implement this approach of adding support for SSM Secure String dynamic references in Beanstalk environment variables values, first add this ebextension to your Elastic Beanstalk deployment archive:
---
packages:
yum:
bash: []
curl: []
jq: []
perl: []
files:
/opt/elasticbeanstalk/hooks/restartappserver/pre/00_resolve_ssm_environment_variables.sh:
mode: "000700"
owner: root
group: root
content: |
#!/usr/bin/env bash
/usr/local/bin/resolve_ssm_environment_variables.sh
/opt/elasticbeanstalk/hooks/appdeploy/pre/00_resolve_ssm_environment_variables.sh:
mode: "000700"
owner: root
group: root
content: |
#!/usr/bin/env bash
/usr/local/bin/resolve_ssm_environment_variables.sh
/opt/elasticbeanstalk/hooks/configdeploy/pre/00_resolve_ssm_environment_variables.sh:
mode: "000700"
owner: root
group: root
content: |
#!/usr/bin/env bash
/usr/local/bin/resolve_ssm_environment_variables.sh
/usr/local/bin/resolve_ssm_environment_variables.sh:
mode: "000700"
owner: root
group: root
content: |
#!/usr/bin/env bash
set -Eeuo pipefail
# Resolve SSM parameter references in the elasticbeanstalk option_settings environment variables.
# SSM parameter references must take the same form used in CloudFormation, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#dynamic-references-ssm-secure-strings
# supported forms are:
# {{resolve:ssm-secure-env:path:version}}
# {{resolve:ssm-secure-env:path}}
# {{resolve:ssm-env:path:version}}
# {{resolve:ssm-env:path}}
# where "path" is the SSM parameter path and "version" is the parameter version.
if [[ -z "${AWS_DEFAULT_REGION:-}" ]]; then
# not set so get from configuration
AWS_DEFAULT_REGION="$(aws configure get region)" || :
fi
if [[ -z "${AWS_DEFAULT_REGION:-}" ]]; then
# not set so get from metadata
AWS_DEFAULT_REGION="$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)" || :
fi
if [[ -z "${AWS_DEFAULT_REGION:-}" ]]; then
echo "Could not determine region." 1>&2
exit 1
fi
export AWS_DEFAULT_REGION
readonly CONTAINER_CONFIG_FILE="${1:-/opt/elasticbeanstalk/deploy/configuration/containerconfiguration}"
readonly TEMP_CONTAINER_CONFIG_FILE="$(mktemp)"
i=0
for envvar in $(jq -r ".optionsettings[\"aws:elasticbeanstalk:application:environment\"][]" "${CONTAINER_CONFIG_FILE}"); do
envvar="$(echo "${envvar}" | perl -p \
-e 's|{{resolve:ssm(?:-secure)-env:([a-zA-Z0-9_.-/]+?):(\d+?)}}|qx(aws ssm get-parameter-history --name "$1" --with-decryption --query Parameters[?Version==\\\x60$2\\\x60].Value --output text) or die("Failed to get SSM parameter named \"$1\" with version \"$2\"")|eg;' \
-e 's|{{resolve:ssm(?:-secure)-env:([a-zA-Z0-9_.-/]+?)}}|qx(aws ssm get-parameter --name "$1" --with-decryption --query Parameter.Value --output text) or die("Failed to get SSM parameter named \"$1\"")|eg;')"
export envvar
jq ".optionsettings[\"aws:elasticbeanstalk:application:environment\"][${i}]=env.envvar" < "${CONTAINER_CONFIG_FILE}" > "${TEMP_CONTAINER_CONFIG_FILE}"
cp "${TEMP_CONTAINER_CONFIG_FILE}" "${CONTAINER_CONFIG_FILE}"
rm "${TEMP_CONTAINER_CONFIG_FILE}"
((i++)) || :
done
Then use dynamic references in your Beanstalk environment variables in one of these forms:
{{resolve:ssm-secure-env:
path
:
version
}}
{{resolve:ssm-secure-env:
path
}}
{{resolve:ssm-env:path:
version
}}
{{resolve:ssm-env:
path
}}
For example, a fragment of the CloudFormation (in yaml) might look like this:
---
AWSTemplateFormatVersion: '2010-09-09'
Resoures:
BeanstalkEnvironment:
Type: AWS::ElasticBeanstalk::Environment
Properties:
OptionSettings:
-
Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: SPRING_DATASOURCE_PASSWORD
Value: !Sub "{{resolve:ssm-secure-env:/my/parameter:42}}"
Using Dynamic References to AWS Systems Manager Parameter Store Secure Strings with Elastic Beanstalk by Craig Andrews is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
Hello,
ive tried adding your yaml into my .ebextensions aswell as the dynamic reference in my beanstalk environment settings, but it does not seem to work for me under NodeJS 10.16.3. Is there any log i can check if the script even gets executed or if it produces any errors? My application only receives the placeholder string as the environment var.
Thanks in advance
I’ve only tried this with Docker and Java Beanstalk environments. I believe it should work with Node ones… if it doesn’t, and you determine how to make it work, please do share.
Thanks Craig, this was very helpful. I got it working with a few modifications to your script:
/usr/local/bin/resolve_ssm_environment_variables.sh:
mode: “000700”
owner: root
group: root
content: |
#!/usr/bin/env bash
set -Eeuo pipefail
# Resolve SSM parameter references in the elasticbeanstalk option_settings environment variables.
# SSM parameter references must take the same form used in CloudFormation, see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#dynamic-references-ssm-secure-strings
# supported forms are:
# {{resolve:ssm-secure-env:path:version}}
# {{resolve:ssm-secure-env:path}}
# {{resolve:ssm-env:path:version}}
# {{resolve:ssm-env:path}}
# where “path” is the SSM parameter path and “version” is the parameter version.
if [[ -z “${AWS_DEFAULT_REGION:-}” ]]; then
# not set so get from configuration
AWS_DEFAULT_REGION=”$(aws configure get region)” || :
fi
if [[ -z “${AWS_DEFAULT_REGION:-}” ]]; then
# not set so get from metadata
AWS_DEFAULT_REGION=”$(curl -s http://169.254.169.254/latest/dynamic/instance-identity/document | jq -r .region)” || :
fi
if [[ -z “${AWS_DEFAULT_REGION:-}” ]]; then
echo “Could not determine region.” 1>&2
exit 1
fi
export AWS_DEFAULT_REGION
readonly CONTAINER_CONFIG_FILE=”${1:-/opt/elasticbeanstalk/deploy/configuration/containerconfiguration}”
readonly TEMP_CONTAINER_CONFIG_FILE=”$(mktemp)”
cp “${CONTAINER_CONFIG_FILE}” “${TEMP_CONTAINER_CONFIG_FILE}”
i=0
# extract {{ssm(-secure)-env}} placeholders out of elastic beanstalk config and replace them with values from SSM
for optionSetting in $(jq -c ‘.optionsettings[“aws:elasticbeanstalk:application:environment”][]?’ “$CONTAINER_CONFIG_FILE”); do
setting=”$(echo “${optionSetting}” | perl -p \
-e ‘s|\{\{resolve:ssm(-secure)?-env:([a-zA-Z0-9_.\-\/]+?):(\d+?)\}\}|qx
(aws ssm get-parameter-history –name “$2” –with-decryption –query Parameters[?Version==\\\x60$3\\\x60].Value –output text) or die(“Failed to get SSM parameter named \”$2\” with version \”$3\””)|eg;’ \
\
-e ‘s|\{\{resolve:ssm(-secure)?-env:([a-zA-Z0-9_.\-\/]+?)\}\}|qx
(aws ssm get-parameter –name “$2” –with-decryption –query Parameter.Value –output text) or die(“Failed to get SSM parameter named \”$2\””)|eg;’ \
\
-e ‘s/(^”)|([^ -~])|(“$)//g’ \
)”
export $setting
# save the fetched values back to the config file so next time they’re ready and waiting for us
echo $(jq “.optionsettings[\”aws:elasticbeanstalk:application:environment\”][${i}]=\”${setting}\”” “${TEMP_CONTAINER_CONFIG_FILE}”) > “${TEMP_CONTAINER_CONFIG_FILE}”
((i++)) || :
done
cp “${TEMP_CONTAINER_CONFIG_FILE}” “${CONTAINER_CONFIG_FILE}”
rm “${TEMP_CONTAINER_CONFIG_FILE}”
First: many thanks for this great solution !
I am using it since a few years : but now time has come to migrate to Amazon Linux 2.
Sadly the container config file “/opt/elasticbeanstalk/deploy/configuration/containerconfiguration” no longer exist on AL2 and I was not able to identify a replacement candidate.
Does someone has any clue ?
Thanks