Mastering AWS CloudFormation
上QQ阅读APP看书,第一时间看更新

Dynamic references with Parameter Store and Secrets Manager

At the beginning of this chapter, we looked at passing parameters either in a JSON file or as a command-line argument.

Although it is a known practice in infrastructure-as-code to keep parameters in a version control system (VCS), it introduces additional complexity.

If we store parameters in a VCS, we need to take care of encrypting sensitive data, such as passwords or other credentials. If we pass them via command-line arguments, we have to make sure we don't make a mistake or a typo (which is a common occurrence in the life of IT engineers).

To solve these issues, we can store template parameters in the SSM Parameter Store or Secrets Manager (for passwords).

They both support versioning, so we can always revert the parameters to the previous version if there was a mistake and then redeploy our stack.

Another benefit of using the Parameter Store and Secrets Manager is that we can provide developers who are not responsible for CloudFormation development with the ability to apply slight changes to the stack, such as changing the instance type or increasing the size of the AutoScaling group.

What we are going to do now is append our core template with a few parameters for our application templates:

core.yaml

Resources:

# Some resources...

  WebAsgMinSize:

    Type: AWS::SSM::Parameter

    Properties:

      Type: String

      Value: "1"

  WebAsgMaxSize:

    Type: AWS::SSM::Parameter

    Properties:

      Type: String

      Value: "1"

  WebAsgDesiredSize:

    Type: AWS::SSM::Parameter

    Properties:

      Type: String

      Value: "1"

# The rest...

Then, we will add SSM parameters to the outputs:

Outputs:

# Original outputs...

  WebAsgMinSize:

    Value: !Ref WebAsgMinSize

    Export:

      Name: WebAsgMinSize

  WebAsgMaxSize:

    Value: !Ref WebAsgMaxSize

    Export:

      Name: WebAsgMaxSize

  WebAsgDesiredSize:

    Value: !Ref WebAsgDesiredSize

    Export:

      Name: WebAsgDesiredSize

We created three parameters for the web tier stack to use. Now, we need to refer to those parameters in the web tier template. There are two ways we can do this:

  • Refer to them from the parameters
  • Refer to them from the resource declaration

You will recall that we've used AWS-specific parameters in the parameter section when we were creating our WebTier template (look at the Using conditional elements section if you need a refresher).

While the use of this is relatively obvious and simple, this will require us first to create a core stack and then copy and paste the SSM Parameter Store parameter's name to the parameters section in the template.

To reduce the number of manual actions, it is wise to resolve the parameter right in the resource declaration.

This is how it works: once the parameter is created and you have its name, you can pass it to a resource property by using Jinja formatting: "{{resolve:service-name:reference-key}}".

We can use the SSM parameter name or ARN.

For our case, we export the parameter's ARN. Let's now resolve it in the web tier AutoScaling group resource:

webtier.yaml

Resources:

# WebTier resources...

  WebTierAsg:

    Type: AWS::AutoScaling::AutoScalingGroup

    Properties:

      # Properties of ASG...

      DesiredCapacity:

        Fn::Join:

          - ":"

          - - "{{resolve:ssm"

            - !ImportValueWebAsgDesiredSize

            -"1}}"

      MaxSize:

        Fn::Join:

          - ":"

          - - "{{resolve:ssm"

            - !ImportValueWebAsgMaxSize

            - "1}}"

      MinSize:

        Fn::Join:

          - ":"

          - - "{{resolve:ssm"

            - !ImportValueWebAsgMinSize

            - "1}}"

In the preceding code, our properties refer not to the template parameters, but to the values of SSM parameters.

SSM Parameter Store limitations

At the time of writing, it is not possible to refer to the latest version of the parameters in the SSM Parameter Store. If you make a change to the parameter, you will still have to change the version of it in the resolve block.

We can use the same method to randomly generate a DB password, so we don't have to hardcode and/or expose it:

database.yaml

Resources:

  DbPwSecret:

    Type: AWS::SecretsManager::Secret

    Properties:

      GenerateSecretString:

      GenerateStringKey: "DbPassword"

  RdsInstance:

    Type: AWS::RDS::DBInstance

    Properties:

      # Some RDS Instance properties...

      MasterUserPassword:

        Fn::Join:

          - ":"

          - - "{resolve:secretsmanager"

            - !RefDbPwSecret

            - "SecretString:DbPassword}}"

Note that, unlike SSM parameters, we do not specify the secret's version, but the actual key we want to refer to.