Custom Resource

The CLI commands in this module assume you are in the multi-region-s3-crr-kms-cmk-source directory (root of the project)

Add the Custom Resources import and resource to the construct

Make the following updates to the lib/index.ts file:

Import the Custom Resources construct with the other imports

import * as cdk from '@aws-cdk/core';
import * as cr from '@aws-cdk/custom-resources';
import * as kms from '@aws-cdk/aws-kms';
import * as s3 from '@aws-cdk/aws-s3';

export interface MultiRegionS3CrrKmsCmkSourceProps {
  ...
}

export class MultiRegionS3CrrKmsCmkSource extends cdk.Construct {
  constructor(scope: cdk.Construct, id: string, props: MultiRegionS3CrrKmsCmkSourceProps) {
    super(scope, id);
    ...
  }
}

Add the Custom Resource to the constructor:

...

export class MultiRegionS3CrrKmsCmkSource extends cdk.Construct {
  constructor(scope: cdk.Construct, id: string, props: MultiRegionS3CrrKmsCmkSourceProps) {
    super(scope, id);

    ...

    const stack = cdk.Stack.of(this);
    const parameterArn = stack.formatArn({
      account: stack.account,
      region: props.targetRegion,
      resource: 'parameter',
      resourceName: props.targetKeyIdSsmParameterName,
      service: 'ssm'
    });
    
    const targetKeyLookupCR = new cr.AwsCustomResource(this, 'TargetKeyLookup', {
      onUpdate: {   // will also be called for a CREATE event
        service: 'SSM',
        action: 'getParameter',
        parameters: {
          Name: props.targetKeyIdSsmParameterName
        },
        region: props.targetRegion,
        physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString())
      },
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: [parameterArn]})
    });
  }
}

We are leveraging an AWS CDK AwsCustomResource construct here to provide an abstraction layer here allowing us to make simple AWS API calls. We don’t have to write the Lambda function or manage the IAM policies, that is handled by the construct.

CDK has built in functionality to import SSM parameters via the ssm.StringParameter.valueFromLookup() method, but it only works if you are accessing parameters in the same environment (account/region).

For more info on using custom resources in the CDK, please see the overview of custom resources here

Summary

Our lib/index.ts should now look as follows:

import * as cdk from '@aws-cdk/core';
import * as cr from '@aws-cdk/custom-resources';
import * as kms from '@aws-cdk/aws-kms';
import * as s3 from '@aws-cdk/aws-s3';

export interface MultiRegionS3CrrKmsCmkSourceProps {
  readonly targetBucket: s3.Bucket,
  readonly targetKeyIdSsmParameterName: string,
  readonly targetRegion: string
}

export class MultiRegionS3CrrKmsCmkSource extends cdk.Construct {
  constructor(scope: cdk.Construct, id: string, props: MultiRegionS3CrrKmsCmkSourceProps) {
    super(scope, id);

    const sourceKmsKey = new kms.Key(this, 'MySourceKey', {
      trustAccountIdentities: true  // delegate key permissions to IAM
    });

    const stack = cdk.Stack.of(this);
    const parameterArn = stack.formatArn({
      account: stack.account,
      region: props.targetRegion,
      resource: 'parameter',
      resourceName: props.targetKeyIdSsmParameterName,
      service: 'ssm'
    });
    
    const targetKeyLookupCR = new cr.AwsCustomResource(this, 'TargetKeyLookup', {
      onUpdate: {   // will also be called for a CREATE event
        service: 'SSM',
        action: 'getParameter',
        parameters: {
          Name: props.targetKeyIdSsmParameterName
        },
        region: props.targetRegion,
        physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString())
      },
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({resources: [parameterArn]})
    });
  }
}