Using Dynamic References to AWS Systems Manager Parameter Store Secure Strings with Elastic Beanstalk

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

CC BY-SA 4.0 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.

4 thoughts on “Using Dynamic References to AWS Systems Manager Parameter Store Secure Strings with Elastic Beanstalk

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

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

  2. 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}”

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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.