How to share information between stacks through SSM parameter store in CDK?

Zhiyuan Li
4 min readMar 30, 2021

--

Summary

In CDK, there are multiple ways to share information between stacks, using SSM parameter store is one of popular solutions, this article walks you through the process of how to utilize `aws-ssm` module to share information between stacks in CDK.

Resolution

To share information between CDK stacks and between CDK Apps, one can use the low level CFN features that exporting output values and then importing in other stacks. This solution has below limitations:

1. need to use low level constructs `CfnOutput` and `Fn.importValue`, which requires understanding on low level CloudFormation feature, thus not “CDK-native”.

2. the values of `CfnOutput` only available at deployment to CDK, rather than at synthesize, therefore, it cannot be used by some constructs’ properties that do not allow `token` as value.

3. same as in CloudFormation, exporting stack cannot be changed if there are importing stacks reference to its outputs, which lead to less flexibility.

To address above limitations, use SSM parameter store to store information that other stacks need to reference is the way to go. Using this solution, one only needs to create high level CDK constructs; and can use method of `aws-ssm` module to get SSM parameter at synthesis; and most importantly, it unties the relationship between exporting stack and importing stack.

To implement above solution in CDK, we need to:

1. Import `aws-ssm` module.

2. Define the resources in the stack.

3. Create `ssm.StringParameter` objects for any resources that need to be used by other stacks.

4. In other stacks that need to retrieve this output value, use `StringParameter.fromStringParameterName` or `StringParameter.valueFromLookup` to retrieve parameters stored in SSM. Specifically:

* fromStringParameterName: Returns a token that will resolve (during deployment).

* valueFromLookup: Reads the value of an SSM parameter during synthesis through an environmental context provider.

5. Once we have the parameters variable, we normally need to use static method `fromXXX` to import existing resources to the importing stacks.

For example, to import a security group, use `fromSecurityGroupId()`; to import an IAM role, use `fromRoleArn()`.

Example

Walk through a real world Typescript scenario, I created two stack: `IamSsmStack` that creates SSM parameters, and `Ec2SsmStack` that retrieves and uses this SSM parameter.

In `IamSsmStack`, I created a instance role and then create a SSM parameter to store the role ARN, once `IamSsmStack` got created, I created another stack `Ec2SsmStack` which retrieved the SSM parameter to get the role ARN and attached it to an Ec2 instance.

Below is the code of IamSsmStack (iam.ts):

import * as cdk from '@aws-cdk/core';
import * as iam from '@aws-cdk/aws-iam';
import * as ssm from '@aws-cdk/aws-ssm';
export class IamSsmStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// create an IAM Role for ec2 instance in another stack
const ec2Role = new iam.Role(this, "ec2Role", {
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
});
// create an SSM parameters which store export values
new ssm.StringParameter(this, 'ec2RoleArn', {
parameterName: `/iamVpcStack/instanceRoleArn`,
stringValue: ec2Role.roleArn
})
}
}

Above will be synthesized to below CFN template (snippet):

Resources:
ec2Role38AB65C0:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: ec2.amazonaws.com
Version: "2012-10-17"
Metadata:
aws:cdk:path: IamSsmStack/ec2Role/Resource
ec2RoleArn8128314D:
Type: AWS::SSM::Parameter
Properties:
Type: String
Value:
Fn::GetAtt:
- ec2Role38AB65C0
- Arn
Name: /iamVpcStack/instanceRoleArn
Metadata:
aws:cdk:path: IamSsmStack/ec2RoleArn/Resource
...

Below is the code of Ec2SsmStack (ec2.ts):

import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as iam from '@aws-cdk/aws-iam';
import * as ssm from '@aws-cdk/aws-ssm';
export class Ec2SsmStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// import the default vpc
const vpc = ec2.Vpc.fromLookup(this, 'vpc', {
isDefault: true,
});
// import the ec2 instance role from role arn in SSM parameter store
const ec2Role = iam.Role.fromRoleArn(
this, 'ec2Role',
ssm.StringParameter.fromStringParameterName(
this, 'ec2RoleArn', '/iamVpcStack/instanceRoleArn').stringValue
);
// use the importing role for an ec2 instance
new ec2.Instance(this, 'testInstance', {
machineImage: new ec2.AmazonLinuxImage(),
instanceType: ec2.InstanceType.of(
ec2.InstanceClass.T3,
ec2.InstanceSize.NANO
),
vpc,
role: ec2Role, // use the imported role
});
}
}

Above will be synthesized to below CFN template (snippet):

Parameters:
ec2RoleArnParameter:
Type: AWS::SSM::Parameter::Value<String>
Default: /iamVpcStack/instanceRoleArn
SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2
Resources:
testInstanceInstanceProfile70854DE1:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- Fn::Select:
- 1
- Fn::Split:
- /
- Fn::Select:
- 5
- Fn::Split:
- ":"
- Ref: ec2RoleArnParameter
testInstance843F827C:
Type: AWS::EC2::Instance
Properties:
AvailabilityZone: us-east-1a
IamInstanceProfile:
Ref: testInstanceInstanceProfile70854DE1
...

The CDK app entry point:

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { IamSsmStack } from '../lib/iam';
import { Ec2SsmStack } from '../lib/ec2'
const app = new cdk.App();new IamSsmStack(app, 'IamSsmStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
new Ec2SsmStack(app, 'Ec2SsmStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});

Reference

[1] core module

[2] aws-ssm module

[3] class StringParameter (construct)

[4] Get a value from the Systems Manager Parameter Store

--

--

Zhiyuan Li
Zhiyuan Li

Written by Zhiyuan Li

I blog about Cloud, DevOps, Cybersecurity. Opinions are my own.

No responses yet