Using parameters in the real world


What are we going to do?

This article will show you how to manage parameters across your Service Catalog Tools environment. It is a collection of real world examples of how to use parameters for organization unit, account and region level wide configurations.

Using parameters (basic usage)

When you specify a launch you can specify parameters. Here is an example where a vpc is provisioned into the default region of each account tagged as type:spoke:

launches:
  networking:
    portfolio: "networking"
    product: "vpc"
    version: "v1"
    parameters:
      cidr:
        default: "10.0.0.1/24"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  
  

You could have also retrieved the value from SSM:

launches:
  networking:
    portfolio: "networking"
    product: "vpc"
    version: "v1"
    parameters:
      cidr:
        ssm: 
          name: "/multi-account-config/networking/vpc/default/cidr"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  
  

This makes it dynamic but what happens if you want to have a different value for each region?

Using mappings for parameters

You can use a mapping to make this more configurable:

mappings:
  VPCs:
    us-east-1:
      "cidr": "10.0.0.1/24"
    us-west-1:
      "cidr": "192.168.0.1/26"

launches:
  networking:
    portfolio: "networking"
    product: "vpc"
    version: "v1"
    parameters:
      cidr:
        mapping: [VPCs, AWS::Region, cidr] 
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  
  

With the above configuration you are saying when provisioning vpc into us-east-1 use 10.0.0.1/24 and when provisioning into us-west-1 use 192.168.0.1/26. This allows you to have a different parameter value for each region but that value will be the same for every launch. To make it different per account you can use the following:

mappings:
  VPCs:
    0123456789010:
      "cidr": "10.0.0.1/24"
    0098765432110:
      "cidr": "192.168.0.1/26"

launches:
  networking:
    portfolio: "networking"
    product: "vpc"
    version: "v1"
    parameters:
      cidr:
        mapping: [VPCs, AWS::AccountId, cidr]
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  

With the above configuration you are saying when provisioning vpc into account 0123456789010 use 10.0.0.1/24 and when provisioning into account 0098765432110 use 192.168.0.1/26. This allows you to have a different parameter value for each account but you will have to update your manifest file each time you want to add an account.

Using intrinsic functions in ssm parameter names

You can use the account id and region name within the SSM parameter name value to use account and region specific ssm parameters:

launches:
  networking:
    portfolio: "networking"
    product: "vpc"
    version: "v1"
    parameters:
      cidr:
        ssm:
          name: "/vpcs/${AWS::AccountId}/${AWS::Region}/cidr"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  

Each time vpc is provisioned into a region of an account the region name and account id will be used to substitute values in the ssm name attribute. For example, when you provision into us-east-1 of account 012345678910 the ssm parameter used to get the value for the cidr parameter will be the one with the name "/vpcs/012345678910/us-east-1/cidr”.

Storing values in ssm using intrinsic functions

You can store the stack outputs for your products in SSM and use intrinsic functions to derive the name:

launches:
  networking:
    portfolio: "networking"
    product: "vpc"
    version: "v1"
    parameters:
      cidr:
        ssm:
          name: "/vpcs/${AWS::AccountId}/${AWS::Region}/cidr"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  
    outputs:
      ssm:
        - param_name: "/vpcs/${AWS::AccountId}/${AWS::Region}/id"
          stack_output: VPCId

When you provision into us-east-1 of account 012345678910 the ssm parameter used to store the stack output will have the name of "/vpcs/012345678910/us-east-1/id”

Customer provided parameters

If you have built a self-service / account vending mechanism you may want to allow the customers of your solution to set some parameters to be used later on - for example whether they require a connected account or not, if they want private subnets or public or even if they want to have networking at all or not.

If you are vending accounts by provisioning a product into your Service Catalog Tools account you have a very easy option. Include an SSM parameter into your account creation product. The name of the parameter should be derived from the account id of the newly created account:

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  NetworkType:
    Type: String
    AllowedValues" : ["connected", "private", "public", "none"]
    Description: Type of networking setup required
Resources:
  Account:
    Type: Custom::Resource
    Description: A custom resource representing an AWS Account
    Properties:
      ServiceToken: !Ref AccountCreatorLambdaArn
      Email: !Ref Email
      AccountName: !Ref AccountName
      IamUserAccessToBilling: !Ref IamUserAccessToBilling

  NetworkingRequired:
    Type: AWS::SSM::Parameter
    Properties:
      Name: !Sub "/networking/${Account.AccountId}/NetworkType"
      Value: !Ref NetworkType

Please note some of the parameters and resources have been omitted from the example above.

This will create an SSM parameter in your Service Catalog Tools account that can be used in your launches:

launches:
  networking:
    portfolio: "networking"
    product: "networks"
    version: "v1"
    parameters:
      NetworkType:
        ssm:
          name: "/networking/${AWS::AccountId}/NetworkType"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  

Within your product you can use conditions to provision the correct set of resources or you can use three launches (one for each network type) along with a condition on whether they should do anything or not:

launches:
  networking-connected:
    portfolio: "networking"
    product: "networks"
    version: "v1"
    parameters:
      NetworkType:
        ssm:
          name: "/networking/${AWS::AccountId}/NetworkType"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  
  networking-private:
    portfolio: "networking"
    product: "networks"
    version: "v1"
    parameters:
      NetworkType:
        ssm:
          name: "/networking/${AWS::AccountId}/NetworkType"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  
  networking-private:
    portfolio: "networking"
    product: "networks"
    version: "v1"
    parameters:
      NetworkType:
        ssm:
          name: "/networking/${AWS::AccountId}/NetworkType"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  

Example product template for private product:

AWSTemplateFormatVersion: 2010-09-09
Parameters:
  NetworkType:
    Description: Type of network to create
    Type: String
    AllowedValues" : ["connected", "private", "public", "none"]

Conditions:
  CreateNetwork: !Equals
    - !Ref NetworkType
    - private

Resources:
  Network:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: 'false'
      EnableDnsHostnames: 'false'

Using large numbers of SSM parameters

If you are using SSM parameters to store region or account specific configurations you can easily end up with a large number of SSM parameters.

For example, having the following parameters Network Type, VPC Cidr, VPC Id, Number of subnets for 7 regions of 300 accounts is over 8,000 parameters.

When using a large number of SSM parameters we recommend you prefix your SSM parameter with a common value which is distinct from other use cases and use the prefix option when specifying the parameter in the manifest file.

For example in the networking example above we recommend the following SSM parameter names:

  • /platform-protected/configurations/networking/${AWS::AccountId}/${AWS::Region}/NetworkType
  • /platform-protected/configurations/networking/${AWS::AccountId}/${AWS::Region}/VPC/Cidr
  • /platform-protected/configurations/networking/${AWS::AccountId}/${AWS::Region}/VPC/Id
  • /platform-protected/configurations/networking/${AWS::AccountId}/${AWS::Region}/Subnets/count

Having /platform-protected in the prefix makes it easier to write IAM policies for the resources, protecting it from write changes from non service roles.

Having /platform-protected/configurations/networking in the prefix makes it easier to write IAM policies by functional area - networking prefixes can be made read/write for members of the networking teams.

Having /platform-protected/configurations/networking in the prefix makes it easier to use paths:

launches:
  networking-vpc:
    portfolio: "networking"
    product: "networks"
    version: "v1"
    parameters:
      NetworkType:
        ssm:
          name: "/platform-protected/configurations/networking/${AWS::AccountId}/${AWS::Region}/NetworkType"
          path: "/platform-protected/configurations/networking"
    deploy_to:
      tags:
        - tag: "type:spoke"
          regions: "default_region"  

If you specify a path the solution will use AWS SSM get_parameters_by_path instead of get_parameter. This provides a significant performance improvement reducing the overall execution time of your workflow.

Using boto3 parameters

You can use the result of a boto3 call as a parameter.

Here we are saying use the ssm client in the spoke account for the region you are provisioning the stack into to call get_parameter and filter the result down to Parameter.Value:

stacks:
golden-ami-id-replicator:
name: "ssm-parameter"
version: "v2"
execution: "hub"
parameters:
Name:
default: "GoldenAMIId"
Value:
boto3:
account_id: "${AWS::AccountId}"
region: "${AWS::Region}"
client: "ssm"
call: "get_parameter"
use_paginator: false
arguments:
Name: "GoldenAMIId"
use_paginator: false
filter: "Parameter.Value"
deploy_to:
tags:
- tag: role:spoke
regions: regions_enabled

If you omit the region the framework will use the home region where you installed the framework and if you omit the account_id the framework will use the hub account where you installed the framework.

Using ${AWS::AccountId} and ${AWS::Region} evaluate to the account and region where the action is occurring.

Using s3 parameters

You can get a json encoded object from s3 and use that as a parameter. The objects must be in the bucket named: sc-puppet-parameters-. You should not create this bucket yourself, it will be created when you upgrade.

vpc:
  name: "vpc-stack"
  version: "v1"
  parameters:
    CIDRRange:
      s3:
        key: "${AWS::AccountId}/config"
        jmespath: "${AWS::Region}".networking.VPC.CIDR
  deploy_to:
    tags:
    - tag: "spoke-accounts"
      regions: "enabled_regions"

Uses the following JSON

{
  "eu-west-1": {
    "networking": {
      "VPC": {
        "CIDR": "10.0.0.1/24"
      }
    }
  }
}

You must specify a key and a jmespath. You can use @ for the jmespath if you want to use the complete value.

You can use ${AWS::AccountId}, ${AWS::Region} and ${AWS::PuppetAccountId} in the key or the jmespath.

If your jmespath contains a hyphen you must surround it with double quotes - like the example above.

Be careful with strings vs numbers in your json objects. If you are using strings for keys in the object and are relying on ${AWS::AccountId} (which is a number) you must use double quotes:

vpc:
  name: "vpc-stack"
  version: "v1"
  parameters:
    CIDRRange:
      s3:
        key: "config"
        jmespath: "${AWS::AccountId}"."${AWS::Region}".networking.VPC.CIDR
  deploy_to:
    tags:
    - tag: "spoke-accounts"
      regions: "enabled_regions"

Uses the following JSON

{
  "012345678910": {
    "eu-west-1": {
      "networking": {
        "VPC": {
          "CIDR": "10.0.0.1/24"
        }
      }
    }
  }
}

whereas

vpc:
  name: "vpc-stack"
  version: "v1"
  parameters:
    CIDRRange:
      s3:
       key: "config"
       jmespath: ${AWS::AccountId}."${AWS::Region}".networking.VPC.CIDR
  deploy_to:
    tags:
    - tag: "spoke-accounts"
      regions: "enabled_regions"

Uses the following JSON:

{
  123456789101: {
    "eu-west-1": {
      "networking": {
        "VPC": {
          "CIDR": "10.0.0.1/24"
        }
      }
    }
  }
}

You can also specify a default which is what is used when the value does not exist:

vpc:
  name: "vpc-stack"
  version: "v1"
  parameters:
    CIDRRange:
      s3:
       key: "config"
       jmespath: ${AWS::AccountId}."${AWS::Region}".networking.VPC.CIDR
       default: "10.0.0.1/24"
  deploy_to:
    tags:
    - tag: "spoke-accounts"
      regions: "enabled_regions"