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

7 minute read
5

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

Resolution

To create stacks and a cross stack reference, use AWS CloudFormation. Or, use Parameter Store, a capability of AWS Systems Manager, to avoid CloudFormation errors.

Use AWS CloudFormation

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 Virtual Private Cloud (Amazon VPC) ID in the VpcStack. When you create stacks, you can customize the names.

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. 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:

    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 uses the ExportValue from VpcStack. In the following command, 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. Run the following commands to deploy the AWS CDK application:

    npm update  
    cdk deploy --all

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

Use Parameter Store

Create an ACM stack and an Application Load Balancer stack

If you receive the following CloudFormation error, then create an AWS Certificate Manager (ACM) stack and an Application Load Balancer stack:

"Export EXPORT_NAME cannot be updated as it is in use by STACK_NAME"

To create both stacks, 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 code to the acm-stack.ts file that completes the following tasks:
    Defines a stack that's named acmStack.
    Creates an ACM certificate with your domainName and your validation set.
    Creates a Parameter Store to add the certificate ARN as a value.

    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: Modify the validation method for your requirements.

  5. Create a file that's 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 that completes the following tasks: 
    Creates an Amazon VPC with one natGateway to reduce cost.
    Defines an acmArn to retrieve the value from the Parameter Store.
    Defines a certificate that converts the acmArn (type: String) to type: IListenerCertificate.
    Creates an Application Load Balancer.
    Adds 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 that completes the following tasks: 
    Defines the env variable.
    Defines certificateArnSsmPath.
    Defines an AWS CDK application.
    Defines an ACM stack with the AWS CDK stack name cdk-ssm-acm-stack.
    Defines an ALB stack with the AWS CDK stack name cdk-ssm-alb-stack.
    Adds a dependency for the ACM and Application Load Balancer stacks so that the ACM stack is created before the Application Load Balancer 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. Run the following command to deploy the AWS CDK application:

    npm update  
    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. To clean up the old certificate, remove the certificate construct or add // in front of the certificate constructs to comment it out:

    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,
                });
                }
            }

    Note: Replace example_domainName with your domain name.

  4. Update the AWS CDK application.

AWS OFFICIAL
AWS OFFICIALUpdated 2 months ago
3 Comments

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 8 months ago

Thank you for your comment. We'll review and update the Knowledge Center article as needed.

profile pictureAWS
MODERATOR
replied 4 months ago

Hi @jsommerville,

I have an example of code in my GitHub repo for this re:Post, which you can refer to for the full code: https://github.com/aws-6w8hnx/cdk-workshop-cross-stack-reference

fanq10
replied 4 months ago