在 CloudFormation 模板中引用 AWS Parameter Store 的安全字符串

Posted

技术标签:

【中文标题】在 CloudFormation 模板中引用 AWS Parameter Store 的安全字符串【英文标题】:Referencing AWS Parameter Store's Secure String in CloudFormation template 【发布时间】:2019-11-15 09:17:06 【问题描述】:

坚持使用 AWS Parameter Store 中的 SecureString。我试图将数据库密码称为:

DatabasePassword:
    Type: AWS::SSM::Parameter::Value<SecureString>
    NoEcho: 'true'
    Default: /environment/default/database_password
    Description: The database admin account password

这会引发错误:

调用CreateStack操作时发生错误(ValidationError):模板格式错误:无法识别的参数类型:SecureString

但是,如果我将此参数称为String 而不是SecureString,则会引发不同的错误:

调用 CreateStack 操作时发生错误(ValidationError):模板引用的参数 [/environment/default/database_password] 具有 CloudFormation 不支持的类型。

我确实尝试过使用'resolve:ssm-secure:parameter-name:version',它适用于数据库配置:

MasterUsername: !Ref DatabaseUsername
MasterUserPassword: 'resolve:ssm-secure:/environment/default/database_password:1'

但是,我使用的是 AWS Fargate docker 容器,我将这些值作为环境变量提供:

Environment:
  - Name: DATABASE_HOSTNAME
    Value: !Ref DatabaseHostname
  - Name: DATABASE_USERNAME
     Value: !Ref DatabaseUsername
  - Name: DATABASE_PASSWORD
    Value: 'resolve:ssm-secure:/environment/default/database_password:1'

这会引发错误:

调用 CreateStack 操作时发生错误 (ValidationError):[AWS::ECS::TaskDefinition/Properties/ContainerDefinitions/Environment] 中不支持 SSM 安全引用

无法在我的实现中使用安全字符串。这个问题有什么解决方法吗? AWS 去年宣布支持SecureString,但找不到文档。我发现的只是使用resolve,它只在某些情况下有效。

参考资料:

1

2

【问题讨论】:

【参考方案1】:

CloudFormation 不支持 SecureString 作为模板参数类型。您可以在下面的文档中确认,让我引用它。

另外,AWS CloudFormation 不支持定义模板 参数作为 SecureString Systems Manager 参数类型。然而, 您可以将安全字符串指定为某些参数值 使用动态参数模式来获取资源。

参考:https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html#aws-ssm-parameter-types

正如您提到的,您“可以”使用dynamic parameter patterns 解决它,但只有有限的资源支持它。 ECSFargate 没有。

参考:https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html

也许您可以使用Secrets Manager 解决它,而不是将密码设置为容器的环境变量,您的应用程序在运行时从Secrets Manager 获取密码,这也提高了您的安全性,密码不会很清楚容器内的文本。

您可以在下面看到此解决方案的一个示例,它不适用于容器,但使用环境变量和Secrets Manager 的“工作方式”相同。

参考:https://aws.amazon.com/blogs/security/how-to-securely-provide-database-credentials-to-lambda-functions-by-using-aws-secrets-manager/

【讨论】:

我确实考虑过使用 Secrets Manager 的这个选项。如果我在我的应用程序中进行更改以从 Secrets Manager 中引用这些变量,那么如果我直接从 docker 容器内的 Secrets Manager 中提取凭据,它将起作用。但是,对于我的本地开发,我不想依赖 AWS,我指的是我的本地数据库实例而不是 AWS RDS 实例。 为什么 AWS 只为 String 而不是 SecureString 发布此功能?支持这个有什么问题?他们使使用 CloudFormation 进行开发变得非常困难。 :-(【参考方案2】:

AWS Secrets Manager 可用于获取 CloudFormation 模板的秘密,即使它们不是数据库密码之类的东西。

这里是文档的链接:https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/dynamic-references.html#dynamic-references-secretsmanager

Secrets Manager 密钥包含 3 个部分:

秘密名称,例如PROD_DB_PASSWORD 秘密的钥匙,例如DB_PASSWORD 以及实际的秘密值

然后,您将使用以下方法在 CloudFormation 模板中解析上述机密:

'resolve:secretsmanager:PROD_DB_PASSWORD:SecretString:DB_PASSWORD'

【讨论】:

【参考方案3】:

我知道这篇文章已经很老了,但我遇到了需要使用 SecureString 的情况,并找到了这篇文章和描述解决方法的博客文章。我认为这可以帮助某些人。

Original Post Here

基本上,您可以像这样在.ebextensions 文件夹中创建一个.conf 文件:

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

然后您可以像在 CloudFormation 模板中那样使用它(或者实际上任何您想要的方式,我将它与 Terraform 一起使用)。请注意,有一个额外的-env 后缀来与原生解析器区分开来。

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

【讨论】:

以上是关于在 CloudFormation 模板中引用 AWS Parameter Store 的安全字符串的主要内容,如果未能解决你的问题,请参考以下文章

cloudformation 验证返回:无效的模板资源属性

在 AWS CloudFormation 模板中引用 !Ref DynamoDB 表名

在 CloudFormation 模板中引用 AWS Parameter Store 的安全字符串

在 Cloudformation 模板中,我如何在 IoT Rule 中引用动态生成的 Lambda 函数 ARN?

在 AWS CloudFormation 模板中,如何使用自己的 Id 标记 EC2 实例而不会出现循环引用错误?

从 VPC 内的另一个 cloudformation 模板引用 SecurityGroup