Using SSM Parameters with CloudFormation Templates and Terraform Projects
So let's talk about using SSM Parameters in your CloudFormation Templates and Terraform Projects.
"What?"
Sorry, I mean AWS Systems Manager Parameter Store. It was formerly known as SSM Parameter store, and likely to soon renounce its name all together and go by a symbol. Though depending upon who or what you talk to, it's still SSM. CLI and CloudFormation like SSM. Console and Marketing like AWS Systems Manager. Everything else is 50/50. And yes, all of this is ironic because SSM stands for "Simple Systems Manager" and as soon as they replaced "simple" with "AWS" it became the monster it feared. Oh well, at least they didn't rename it to complex systems manager.
ANYHOW. Though AWS Systems Manager has become an umbrella for a lot of different things, the service of interest to us is Parameter Store. In a nutshell, you can store strings in it.
The usefulness of this is the ability to have one place where all of your secrets, api keys, passwords, and random one-line jokes are stored. This means when you go to launch a stack, CloudFormation Template, or Terraform Project, you can reference the values with less human error. You can also encrypt the strings you store in SSM, which takes its usefulness to another level.
(FYI, I'm going to be calling it SSM and not AWS Systems Manager Parameter Store.)
And so, in this post we're going to take a look at how we can use SSM Parameters in both CloudFormation and Terraform with some examples. I'm doing both here because, if you're working in AWS at least, knowing both is a smart move. If you'd like to know more about that, then you can checkout my thoughts on Terraform vs. CloudFormation on AWS.
What You Should Already Know
Obviously, if I assume you know nothing, we'll be here all day, week, month. So because of that, I'm going to assume you've got some general comfort with both CloudFormation and Terraform. That being said, you by no means need to be a master to understand the rest of this article. But if you've never launched a thing with CloudFormation, you'll probably get lost.
I do think you need less Terraform experience to understand this article though. The reason being is that, if you know CloudFormation well enough, you'll be able to draw comparisons between the two. We'll keep the examples relatively simple, so seeing the similarities should be straightforward.
The CloudFormation Template, Pre SSM Parameters
So let's say we've got a CloudFormation template like so:
{
"Description": "Jerry's us-east-1 instance",
"Parameters": {
"ParamInstanceName": {
"Type": "String",
"Default": "jerrys-cfn-instance",
"Description": "The name of the EC2 instance"
}
},
"Resources": {
"ExampleInstance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-0b898040803850657",
"InstanceType": "t2.micro",
"Tags": [{
"Key": "Name",
"Value": { "Ref": "ParamInstanceName" }
}]
}
}
}
}
All it does is launch an EC2 instance and accepts a parameter that's used as the Name
tag on the instance. Like I said, super simple. To launch it, save it as a .json
file and head over to the CloudFormation console and upload it.
The Terraform Project, Pre SSM Parameters
Because Jerry was frisky, he went ahead and made another one in Terraform as well, despite everyone yapping at him to use the CDK. This is what it looks like:
provider "aws" {
region = "us-east-1"
}
# the analogous "Parameters" of Terraform
variable "instance_name" {
type = string
description = "The name of the EC2 instance"
default = "jerrys-tf-instance"
}
resource "aws_instance" "tf_instance" {
ami = "ami-0b898040803850657"
instance_type = "t2.micro"
tags = {
Name = var.instance_name
}
}
The exact same thing happens when using this project. Aside from having Terraform installed, you just need to have this file in its own directory and run terraform init
and then terraform apply
. Doing so will create the instance.
Using SSM Parameters
Instead of having the instance name input either through CloudFormation Parameters or Terraform Input Variables, Jerry would prefer to have one, unified place to keep his various variable strings. A place where, even if he creates a new project, he can still get the same values every time. And he's decided to use SSM making this paragraph completely pointless because what else would he use given the blog post's title.
(Yes, I know our example of using tags is a bit contrived, but otherwise we'll get too distracted from our main focus. Feel free to imagine this as pulling down an API key or database password to be used in a launch script or whatever.)
Making the First SSM Parameter for CloudFormation
To follow along:
Head over to the AWS Systems Manager in the AWS Console
Click on
Parameter Store
in the left navigationClick
Create parameter
Name the parameter
instance-name
Leave
Description
as-isLeave
Tier
as StandardFor
Type
select "String" (we'll revisit secure strings soon)For
Value
inputjerrys-cfn-instance
Click
Create parameter
See? Not too bad. And so here's where we get to have the conversation about why we aren't using Secure Strings. Well unfortunately, at the time of this writing, CloudFormation SSM Parameters do NOT support using Secure Strings. It's possible to use it in around 10ish services if you use Dynamic References, but you still can't use it like a normal parameter.
We'll revisit this shortly.
Making the Second, Secure SSM Parameter for Terraform
But don't worry, Terraform can use 'em! Thus outlining the peculiar situation whereby Terraform will often be ahead of CloudFormation in terms of AWS Services it supports. Though I'd love to give them all the credit for this, a lot of it is because they hook into the AWS CLI instead of CloudFormation to get their stuff done in the background.
To follow along making a secure parameter:
Head over to the AWS Systems Manager in the AWS Console
Click on
Parameter Store
in the left navigationClick
Create parameter
Name the parameter
secure-instance-name
Leave
Description
as-isLeave
Tier
as StandardFor
Type
select "Secure String"Leave
KMS key source
asMy current account
Leave
KMS Key ID
asalias/aws/ssm
(don't worry, we'll talk about it)For
Value
inputjerrys-secure-tf-instance
...and thus the secure string was created. How? Well, AWS has a service called the AWS Key Management Service. It's a lot of things, but the simplest way to think about it is this - its a service that has a bunch of encryption keys in it that can be used to encrypt and decrypt things across AWS. While you can create your own keys, we're just using one of the AWS managed keys. These are keys created and managed by AWS Services. In this case, SSM has a key named alias/aws/ssm
that we can use.
I know, I know. If you haven't done anything with KMS, it may seem a bit auto-magical. But again, there's just some default encryption keys in your account and we're using one of them here to encrypt our string. That's it.
Now that we have our two SSM parameters created, we can go forth and use them in our templates.
The CloudFormation Template, Post SSM Parameters
The nice thing about hooking into SSM parameters in CloudFormation is that they aren't that much more work. Much like how String
is a parameter type, AWS::SSM::Parameter::Value<String>
is also a type. More on the format of those types can be found here in the CloudFormation Parameters Documentation.
However, all that AWS::SSM::Parameter::Value<String>
is saying is, "Hey CloudFormation, this type is going to come from AWS SSM Parameter Store, and its value is going to be a string."
"Don't you mean AWS Systems Manager Parameter Store?"
"Whatever Bob."
And so, after swapping out our old parameter with the new parameter, we get this:
{
"Parameters": {
"ParamInstanceName": {
"Type": "AWS::SSM::Parameter::Value<String>",
"Default": "instance-name",
"Description": "The instance name SSM parameter"
}
},
"Resources": {
"ExampleInstance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId": "ami-0b898040803850657",
"InstanceType": "t2.micro",
"Tags": [{
"Key": "Name",
"Value": { "Ref": "ParamInstanceName" }
}]
}
}
}
}
The only difference, aside from the parameter type Type
, is that we've given it a default value that specifies the NAME of the SSM parameter. Since we named ours instance-name
in the console, that's what we'll use as our default. This also points out that you could NOT use a default and, upon launch, choose from any of the SSM parameters in your account.
And there ya have it. Now you can launch the template and it'll use the SSM parameter! Now, let's move on to Terraform.
The Terraform Template, Post SSM Parameters
In Terraform there are "resources" which are...exactly what you think they are. There's also "data sources" which, when defined, can either fetch data from a remote source or compute data down into a different format (among other things). The "data source" that we're interested in is the aws_ssm_parameter
data source. When Terraform sees this, it will know that it needs to head over to SSM in your account and grab whatever parameter you ask it for...
...and decrypting secure strings for use is just an optional one-liner:
provider "aws" {
region = "us-east-1"
}
data "aws_ssm_parameter" "instance_name" {
name = "secure-instance-name" # our SSM parameter's name
with_decryption = true # defaults to true, but just to be explicit...
}
resource "aws_instance" "tf_instance" {
ami = "ami-0b898040803850657"
instance_type = "t2.micro"
tags = {
Name = data.aws_ssm_parameter.instance_name.value
}
}
...and there we go. Not only will it go and fetch that SSM parameter for us, but it'll also decrypt it. Now you can do the usual terraform apply
and enjoy the show.
(Please note that this example is assuming that you've configured the AWS CLI with a profile that has enough permissions to pull parameters and decrypt them. If you've just configured the CLI with either your root AWS account or an IAM user that has admin privileges, then you do.)
On Using SSM Secure Parameters with CloudFormation
Well, we touched on the limitation already, and yes, it is surprising that its still a thing. But how do we take this lemon and make it into lemonade? Ehhhhh it depends. If you need to give an IAM user a password or RDS DB Cluster / Instance a password, then Dynamic References are your way forward.
However, if you need to do something like grab an API key from SSM and make it available to an EC2 instance, then you'll have to use the AWS CLI in combo with a user data script or cfn-init
and AWS::CloudFormation::Init
metadata. The command would look like this:
aws ssm get-parameters --names NAME_OF_YOUR_SECURE_PARAM \
--with-decryption \
--output text \
--query 'Parameters[0].Value' \
--region YOUR_REGION
The above command will fetch and decrypt the SSM secure string. HOWEVER. The instance (or container) using this command needs the IAM permissions to both GET the SSM parameter AND to decrypt it with the key that was used to encrypt the parameter originally. That IAM policy would look something like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"ssm:GetParameters"
],
"Resource": [
"arn:aws:ssm:us-east-1:123456789101:parameter/secure-instance-name"
],
"Effect": "Allow"
}
{
"Action": [
"kms:Decrypt"
],
"Resource": [
"arn:aws:kms:us-east-1:123456789101:key/ID_OF_ENCRYPTING_KEY"
],
"Effect": "Allow"
}
]
}
To get that ID_OF_ENCRYPTING_KEY
you'd...
- Go to Key Management Service Console
- Click on
AWS managed keys
- Grab the
Key ID
In our case we'd want the key aws/ssm
. I know that in SSM it's called alias/aws/ssm
, but we also call SSM "AWS Systems Manager Parameter Store" so I'm not sure what else I can say.
The Usefulness of SSM Parameters
Well, hopefully our simplistic example didn't cloud your eyes from all of the possibilities that SSM Parameters provide. As I mentioned, they can pull a great deal of human error out of passing parameters to your templates or projects. And of course they can also prevent goofy security breaches like storing sensitive data right in templates or projects. Thankfully, as we've seen, they're pretty darn easy to use as well.
That being said, I will mention one edge case that can be a game breaker. By default, you can only request 40 parameters per second. Obviously, depending upon your infrastructure and needs this limit may be a ceiling for your application. If you have a high number of parameters in the store or just a ton of things that call from it, you'll wind up with a nasty ThrottlingException
error. Yes, you can absolutely get the limit raised, as shown here, but I thought I'd let you know about it.
Finally, one thing to keep in mind, when you're using SSM parameters they will be refreshed every time you launch a template again. Meaning, if you go to update a CloudFormation Template, and you've changed the value in SSM, the template will pull and use the new value. This isn't that big of a deal, but it's something to watch out for.
And There We Go
We've helped Jerry explore SSM parameters in-depth using both CloudFormation Templates and Terraform. If you'd like to see the code in github, you can find that here.
Enjoy Posts Like These? Sign up to my mailing list!
J Cole Morrison
http://start.jcolemorrison.comDeveloper Advocate @HashiCorp, DevOps Enthusiast, Startup Lover, Teaching at awsdevops.io