How do I pass construct objects for a cross stack reference in a single CDK project?

7 minute read
4

I want to pass construct objects for a cross stack reference in a single AWS Cloud Development Kit (AWS CDK) project.

Short description

To do a cross stack reference, use one of the following methods:

  • (AWS CloudFormation native method) Create stacks and cross stack reference
  • (Parameter Store method) Create stacks and cross stack reference to avoid AWS CloudFormation errors

Resolution

(AWS CloudFormation native method) Create stacks and cross stack reference

Note: The following steps create two example stacks that are named VpcStack and SecurityGroupStack. The VpcStack is a producer stack and the SecurityGroupStack is a consumer stack. In the SecurityGroupStack, a security group is created to reference the Amazon VPC ID in the VpcStack. When creating stacks, you can customize the names.

1.    Create a project and invoke cdk init in an empty directory:

mkdir my-project
cd my-project
cdk init --language typescript

2.    In the lib/my-project-stack.ts file, import AWS CDK modules:

import as 'aws-cdk-lib/aws-ec2';
import * as cdk from 'aws-cdk-lib';

3.    Define a stack and set a property for the Amazon VPC (for this example VpcStack):

export class VpcStack extends cdk.Stack {
  public readonly vpc: ec2.IVpc;

  constructor(scope: cdk.App, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    this.vpc = new ec2.Vpc(this, 'Cross-Ref-Vpc', {
      maxAzs: 2,
      natGateways: 1,
    });
  }
}

4.    Define an interface to specify the props that you want to receive from the target stack:

interface VpcStackProps extends cdk.StackProps {
  vpc: ec2.IVpc;
}

5.    Create another stack that consumes the ExportValue from VpcStack:

  • SecurityGroupStack uses VpcStackProps in props.
  • Add vpc: props.vpc to cross reference in the security group properties.
export class SecurityGroupStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string, props: VpcStackProps) {
    super(scope, id, props);
    const securityGroupName = "BastionHostSg";
    const SecurityGroup = new ec2.SecurityGroup(this, 'securityGroupName', {
      vpc: props.vpc,
      allowAllOutbound: true,
      securityGroupName: securityGroupName,
    });
  }
}

6.    In the bin/my-project/ts file, add the following:

#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import {VpcStack, SecurityGroupStack} from '../lib/my-project-stack';
const app = new cdk.App();
const vpc_stack = new VpcStack(app, 'vpc-stack', {});
const sg_stack = new SecurityGroupStack(app, 'sg-stack', {
    vpc: vpc_stack.vpc,
});

7.    Deploy the AWS CDK application by running the following commands:

npm update
``````plaintext
cdk deploy --all

Note: When the AWS CDK application is deployed, the ExportValue from vpc-stack is imported by sg-stack.

(Parameter Store method) Create stacks and cross stack reference to avoid CloudFormation errors

Create an AWS Certificate Manager (ACM) stack and an Application Load Balancer (ALB) stack

If you receive the AWS CloudFormation error Export EXPORT_NAME cannot be updated as it is in use by STACK_NAME, then complete the following steps:

1.    Create a project and invoke cdk init in an empty directory:

mkdir my-project
cd my-project
cdk init --language typescript

2.    Rename lib/my-project-stack.ts to lib/acm-stack.ts. Then, import the following AWS CDK modules:

import \* as cdk from 'aws-cdk-lib';
import \* as acm from "aws-cdk-lib/aws-certificatemanager";
import \* as ssm from "aws-cdk-lib/aws-ssm";
import {Construct} from 'constructs';

3.    Define and export an interface acmProps in lib/acm-stack.ts:

export interface acmProps extends cdk.StackProps {
 readonly acmName: string;
 readonly acmArnExportPath: string;
}

4.    Add the following to the file acm-stack.ts:

  • Define a stack named acmStack
  • Create an ACM certificate with your specified domainName and your specified validation set.
  • Create an SSM Parameter Store to add the certificate ARN as a value.    

Important: Make sure to review all AWS Command Line Interface (AWS CLI) commands and replace all instances of example strings with your values. For example, replace example_domainName with your specified domain name.

export class acmStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props: acmProps) {
    super(scope, id, props); 
    const cert = new acm.Certificate(this, 'Certificate', {
        domainName: 'example_domainName.com',
        validation: acm.CertificateValidation.fromDns(),
    });
    const parameter = new ssm.StringParameter(this, 'acmArnParameter', {
        parameterName: props.acmArnExportPath,
        stringValue: cert.certificateArn,
    });
    }
}

Note: For the validation: attribute, you can modify the validation method for your specific requirements. For more information, see validation methods.

5.    Create a file named albStack in the /lib directory. Then, import the following AWS CDK modules:

import * as cdk from 'aws-cdk-lib';
import * as ec2 from "aws-cdk-lib/aws-ec2";
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as ssm from "aws-cdk-lib/aws-ssm";
import * as acm from "aws-cdk-lib/aws-certificatemanager";
import {Construct} from 'constructs';

6.    Define an interface:

export interface albProps extends cdk.StackProps {
    readonly acmArnExportPath: string;
}

7.    Add the following code to your lib/alb-stack.ts file in your AWS CDK application to do the following:

  • Create an Amazon VPC with one natGateway to reduce cost.
  • Define an acmArn to retrieve the value from the SSM Parameter Store.
  • Define a certificate that convertsg the acmArn (Type: String) to Type: IListenerCertificate.
  • Create an ALB.
  • Add a Listener and sslCertificateArn that references the value in certificates (type: IListenerCertificate).
export class albStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props: albProps) {
    super(scope, id, props);
    const vpc = new ec2.Vpc(this, "VPC", { natGateways:1 });
    const acmArn = ssm.StringParameter.valueForStringParameter(this, props.acmArnExportPath);
    const certificate = acm.Certificate.fromCertificateArn(this, 'acm', acmArn);
    const alb = new elbv2.ApplicationLoadBalancer(this, 'ALB', {
        vpc,
        internetFacing: true,
        });
        alb.addRedirect();
    const listener = alb.addListener ('Listener',{
        port: 443,
        certificates: [certificate],
        });
        listener.addTargets('Instance', {port: 80});
    }
}

8.    Add the following code to your bin/my-project.ts file to do the following:

  • Define the env variable.
  • Define certificateArnSsmPath.
  • Define an AWS CDK application.
  • Define an ACM stack with the AWS CDK stack name cdk-ssm-acm-stack.
  • Define an ALB stack with the AWS CDK stack name cdk-ssm-alb-stack.
  • Add a dependency for the ACM and ALB stacks to make sure that the ACM stack is created before the ALB stack.
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import {acmStack, acmProps} from '../lib/acm-stack';
import {albStack, albProps} from '../lib/alb-stack';
import {addDependency} from 'aws-cdk-lib/core/lib/deps';

const env = {account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION}
const certificateArnSsmPath = "/cdk/acm/cross-stacks-reference/certArn";
const app = new cdk.App();
const acm_stack = new acmStack(app, "cdk-ssm-acm-stack", {
    env: env,
    acmName: "ssm-acm",
    acmArnExportPath: certificateArnSsmPath,
});
const alb_stack = new albStack(app, "cdk-ssm-alb-stack", {
    env: env,
    acmArnExportPath: certificateArnSsmPath,
});
alb_stack.addDependency(acm_stack)

9.    Deploy the AWS CDK application using the following commands:

npm update
``````plaintext
cdk deploy --all

Renew the ACM certificate

To renew the ACM certificate before expiration and make sure that the CloudFormation doesn't become stuck in the UPDATE_COMPLETE_CLEANUP_IN_PROGRESS state, complete the following steps:

1.    Add a new certificate in the lib/acm-stack.ts file and name it renew. Then, change the stringValue attribute in parameter to renew.certificateArn:

export class acmStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props: acmProps) {
    super(scope, id, props); 
    const cert = new acm.Certificate(this, 'Certificate', {
        domainName: 'example_domainName.com',
        validation: acm.CertificateValidation.fromDns(),
    });
    const renew = new acm.Certificate(this, 'renewCertificate', {
       domainName: 'example_domainName.com',
       validation: acm.CertificateValidation.fromDns(),
    });
    const parameter = new ssm.StringParameter(this, 'acmArnParameter', {
        parameterName: props.acmArnExportPath,
        stringValue: renew.certificateArn,
    });
    }
}

2.    Update the AWS CDK application:

cdk deploy --all

3.    When the stacks are updated, clean up the old certificate by doing the following:

  • Remove the certificate construct or comment it out by adding // in front of the certificate constructs.
  • Replace example_domainName with your specified domain name.
export class acmStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props: acmProps) {
    super(scope, id, props); 
    // const cert = new acm.Certificate(this, 'Certificate', {
    //     domainName: 'example_domainName.com',
    //     validation: acm.CertificateValidation.fromDns(),
    // });
    const renew = new acm.Certificate(this, 'renewCertificate', {
       domainName: 'example_domainName.com',
       validation: acm.CertificateValidation.fromDns(),
    });
    const parameter = new ssm.StringParameter(this, 'acmArnParameter', {
        parameterName: props.acmArnExportPath,
        stringValue: renew.certificateArn,
    });
    }
}

4.    Update the AWS CDK application to clean up the old certificate:

AWS OFFICIAL
AWS OFFICIALUpdated a year ago
1 Comment

For the love of god can I please see the finished code base for this example instead of a bunch of snippets. I can't find a good example of this beyond this guide and I can't figure out where am I supposed to be putting all of these snippets.

replied 3 months ago