I was trying to configure a CloudFormation template to pull database passwords from SSM Parameter Store. The template used the AWS::SSM::Parameter::Value<String> parameter type, but it worked only for regular String parameters. When I tried a SecureString, CloudFormation failed with a cryptic error about unsupported parameter types. In this post, I’ll walk through exactly what causes this and how to work around it.

The Problem

CloudFormation template fails with error when using SSM SecureString parameters:

Parameter type AWS::SSM::Parameter::Value<SecureString> is not supported
Invalid parameter type AWS::SSM::Parameter::Value<SecureString>

When you define a template parameter like:

Parameters:
  DbPassword:
    Type: AWS::SSM::Parameter::Value<SecureString>
    Default: /prod/rds/password

CloudFormation rejects it immediately during validation.

Why Does This Happen?

CloudFormation parameter types AWS::SSM::Parameter::Value<String> are designed to fetch parameter values from SSM during template processing. However, this design limitation means they don’t support SecureString parameters—the decryption happens during deployment when the value is used, not during parameter resolution.

This is by design for security reasons: CloudFormation parameters show up in templates, stack descriptions, and console output. SecureString values should never be displayed, so CloudFormation doesn’t support them as parameter types.

However, dynamic references DO support SecureString. Dynamic references resolve at deployment time without revealing the actual value in templates or console output.

The Fix

Option 1: Use Dynamic References for SecureString Parameters

Replace the parameter type approach with dynamic references directly in your resource properties. This works with SecureString:

Resources:
  MyRDSDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: mydb
      Engine: postgres
      MasterUsername: dbadmin
      # Use dynamic reference with SecureString
      MasterUserPassword: "{{resolve:ssm-secure:/prod/rds/password:1}}"
      DBInstanceClass: db.t3.micro
      AllocatedStorage: 20

The syntax is:

{{resolve:ssm-secure:PARAMETER_NAME:VERSION_NUMBER}}

For example:

{{resolve:ssm-secure:/prod/rds/master-password:1}}
{{resolve:ssm-secure:/my/secret/api-key:2}}

The version number (:1) is required. Use version 1 for the latest, or specify exact versions.

Option 2: Use Dynamic References with Regular Strings

For non-sensitive parameters stored in SSM as regular Strings, you can also use dynamic references:

Resources:
  MyFunction:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.11
      Handler: index.handler
      Role: !GetAtt LambdaRole.Arn
      Environment:
        Variables:
          # Non-secret config from SSM Parameter Store
          ENVIRONMENT_NAME: "{{resolve:ssm:/config/environment-name:1}}"
          LOG_LEVEL: "{{resolve:ssm:/config/log-level:1}}"
          # Secrets from SSM SecureString
          API_KEY: "{{resolve:ssm-secure:/secrets/api-key:1}}"
      Code:
        ZipFile: |
          def handler(event, context):
              return {"statusCode": 200}

Option 3: Verify SSM Parameter Exists Before Deploying

Before deploying a template with dynamic references, verify the parameter exists and is accessible:

# Check if parameter exists
aws ssm get-parameter \
  --name /prod/rds/password \
  --query "Parameter.[Name,Type,Version]" \
  --output table

# For SecureString, get the parameter with decryption
aws ssm get-parameter \
  --name /prod/rds/password \
  --with-decryption \
  --query "Parameter.Value"

# List all parameters matching a pattern
aws ssm describe-parameters \
  --filter "Key=Name,Values=/prod" \
  --query "Parameters[].{Name:Name,Type:Type,Version:Version}"

Option 4: Handle Missing Parameters Gracefully

If a parameter doesn’t exist, CloudFormation deployment fails. Create the parameter first if it doesn’t exist:

# Create the SecureString parameter if not already present
aws ssm put-parameter \
  --name /prod/rds/password \
  --value "MySecurePassword123!" \
  --type SecureString \
  --key-id alias/aws/ssm \
  --overwrite

Or in your CloudFormation template, use a custom resource to fetch the value:

Resources:
  # Lambda to read SSM SecureString
  SSMParameterLambda:
    Type: AWS::Lambda::Function
    Properties:
      Runtime: python3.11
      Handler: index.handler
      Role: !GetAtt LambdaRole.Arn
      Code:
        ZipFile: |
          import boto3
          import cfnresponse

          ssm = boto3.client('ssm')

          def handler(event, context):
              try:
                  param_name = event['ResourceProperties']['ParameterName']
                  response = ssm.get_parameter(Name=param_name, WithDecryption=True)
                  cfnresponse.send(event, context, cfnresponse.SUCCESS,
                    {'Value': response['Parameter']['Value']}, param_name)
              except Exception as e:
                  cfnresponse.send(event, context, cfnresponse.FAILED, {})

  GetPasswordFunction:
    Type: AWS::CloudFormation::CustomResource
    Properties:
      ServiceToken: !GetAtt SSMParameterLambda.Arn
      ParameterName: /prod/rds/password

  MyRDSDatabase:
    Type: AWS::RDS::DBInstance
    Properties:
      MasterUserPassword: !GetAtt GetPasswordFunction.Value
      # ... other RDS properties

How to Run This

  1. Create the SSM SecureString parameter: aws ssm put-parameter --name /prod/rds/password --value "secret" --type SecureString
  2. In your CloudFormation template, use dynamic references: MasterUserPassword: "{{resolve:ssm-secure:/prod/rds/password:1}}"
  3. Deploy the stack: aws cloudformation deploy --template-file template.yaml --stack-name my-stack
  4. Verify the deployment: aws rds describe-db-instances --db-instance-identifier mydb --query "DBInstances[0].MasterUsername"

Is This Safe?

Yes. Dynamic references are the secure way to handle secrets in CloudFormation. The actual secret value is never displayed in templates, stack descriptions, or console output—it’s resolved only at deployment time by the CloudFormation service.

Key Takeaway

CloudFormation parameter types don’t support SecureString, but dynamic references do. Use {{resolve:ssm-secure:PARAMETER_NAME:VERSION}} directly in resource properties for secrets stored in SSM Parameter Store as SecureString. This keeps secrets out of templates and console logs.


Have questions or ran into a different CloudFormation issue? Connect with me on LinkedIn or X.