aws / aws/aws-cdk

(core): `crossRegionReferences` generates unnecessarily long SSM parameter names leading to deployment failures

Open
#30,119 5 comments 3 reactions 0 assignees View on GitHub
@aws-cdk/core bug p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
2d 3h
Merged PRs (30d)
83

Description

### Describe the bug

When using `crossRegionReferences` the producing stack creates SSM Parameters in the consuming region for each exported value. This is accomplished via this code https://github.com/aws/aws-cdk/blob/2779b5e7b4c962409f977eff2d28b96bc01c6c46/packages/aws-cdk-lib/core/lib/private/refs.ts#L248-L263. For users using longer stack names or nested stacks, the generated parameter name can become unnecessarily long. As a result, this can lead to deployment failures resulting from a response object that exceeds the [4096 byte](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-responses.html) limit when the export reader and writer custom resource providers are responding back to CloudFormation.

### Expected Behavior

`crossRegionReferences` doesn't generate unnecessarily long SSM parameter names.

### Current Behavior

`crossRegionReferences` generates SSM parameters with the name `/cdk/exports/{consumingStackName}/{export-name}`. For users using nested stacks or for users with longer stack names (or both), the parameter names can become unnecessarily long which can result in deployment failures.

### Reproduction Steps

```ts
import * as awscdk from 'aws-cdk-lib'

const ACCOUNT_ID = '111222333444'
const STACK_PREFIX = 'testing-cdk-cross-region-references-as-example-for-github-issue'
// 8 or less works, 9 or more fails with "Response object is too long"
const NUMBER_OF_KEYS_TO_CREATE = 9

function createRootStack (app: awscdk.App, region: string): awscdk.Stack {
const stackName = `${STACK_PREFIX}-rootstack-${region}`

const props = {
env: {
account: ACCOUNT_ID,
region: region
},
crossRegionReferences: true,
synthesizer: new awscdk.LegacyStackSynthesizer()
}

const stack = new awscdk.Stack(app, stackName, props)

return stack
}

function createPrimaryKeyStack (rootStack: awscdk.Stack, keyName: string): string {
const nestedStack = new awscdk.NestedStack(rootStack, `${STACK_PREFIX}-nestedstack-${keyName}`)

const keyProps = getKeyProps(keyName)
const kmsCfnKey = new awscdk.aws_kms.CfnKey(nestedStack, `${STACK_PREFIX}-key-${keyName}`, keyProps)

const aliasName = `alias/${STACK_PREFIX}-${keyName}`
const aliasProps = getAliasProps(aliasName, kmsCfnKey.attrKeyId)
new awscdk.aws_kms.CfnAlias(nestedStack, `${STACK_PREFIX}-alias-${keyName}`, aliasProps)

return kmsCfnKey.attrArn
}

function createReplicaKeyStack (rootStack: awscdk.Stack, keyName: string, primaryKeyArn: string) {
const nestedStack = new awscdk.NestedStack(rootStack, `${STACK_PREFIX}-nestedstack-${keyName}`)

const keyProps = getReplicaKeyProps(keyName, primaryKeyArn)
const kmsCfnKey = new awscdk.aws_kms.CfnReplicaKey(nestedStack, `${STACK_PREFIX}-key-${keyName}`, keyProps)

const aliasName = `alias/${STACK_PREFIX}-${keyName}`
const aliasProps = getAliasProps(aliasName, kmsCfnKey.attrKeyId)
new awscdk.aws_kms.CfnAlias(nestedStack, `${STACK_PREFIX}-alias-${keyName}`, aliasProps)
}

function getKeyPolicy (): awscdk.aws_iam.PolicyDocument {
const thisPrincipal = new awscdk.aws_iam.AccountPrincipal(ACCOUNT_ID)

const policyStatement = new awscdk.aws_iam.PolicyStatement({
sid: 'Allow all key access',
effect: awscdk.aws_iam.Effect.ALLOW,
actions: [
'kms:*'
],
resources: ['*'],
principals: [thisPrincipal]
})

const policyDocument = new awscdk.aws_iam.PolicyDocument({ statements: [policyStatement] })

return policyDocument
}

function getKeyProps (keyName: string): awscdk.aws_kms.CfnKeyProps {
const policyDocument = getKeyPolicy()

const keyProps: awscdk.aws_kms.CfnKeyProps = {
description: `Testing cross-region references in CDK - ${keyName}`,
keyPolicy: policyDocument,
multiRegion: true,
enableKeyRotation: false,
enabled: true,
keyUsage: 'ENCRYPT_DECRYPT',
pendingWindowInDays: 7
}

return keyProps
}

function getReplicaKeyProps (keyName: string, primaryKeyArn: string): awscdk.aws_kms.CfnReplicaKeyProps {
const policyDocument = getKeyPolicy()

const keyProps: awscdk.aws_kms.CfnReplicaKeyProps = {
description: `Testing cross-region references in CDK - ${keyName}`,
primaryKeyArn: primaryKeyArn,
keyPolicy: policyDocument,
enabled: true,
pendingWindowInDays: 7
}

return keyProps
}

function getAliasProps (aliasName:string, targetKeyId: string): awscdk.aws_kms.CfnAliasProps {
const aliasProps: awscdk.aws_kms.CfnAliasProps = {
aliasName: aliasName,
targetKeyId: targetKeyId
}

return aliasProps
}

function main (args: Array | undefined = undefined) {
const app = new awscdk.App()

const rootStackEast = createRootStack(app, 'us-east-1')
const rootStackWest = createRootStack(app, 'us-west-2')

for (let i = 1; i <= NUMBER_OF_KEYS_TO_CREATE; i++) {
const primaryKeyArn = createPrimaryKeyStack(rootStackEast, i.toString())
createReplicaKeyStack(rootStackWest, i.toString(), primaryKeyArn)
}
}

if (require.main === module) {
main()
}
```

### Possible Solution

`crossRegionReferences` is an experimental feature. We may want to consider updating the SSM parameter names being generated to be limited to a certain length. This would break existing customers, though. Ideally, we could find a way to give users a choice to optionally limit the parameter name length. While this doesn't help us get around the 4096 byte limitation for custom resource provider response objects, it _would_ reduce the chances of a deployment failure by limiting the response body size.

### Additional Information/Context

_No response_

### CDK CLI Version

2.136.0

### Framework Version

_No response_

### Node.js Version

20.11.0

### OS

MacOS

### Language

TypeScript

### Language Version

_No response_

### Other information

_No response_

Contributor guide

Open the contributing guide

Research direction

Start in packages/aws-cdk-lib/core/lib/private/refs.ts at the cross-region reference code around lines 248-263, then run the provided TypeScript reproduction with nested stacks and multiple keys. Trace how the consuming stack name and export name form SSM parameter names. Done means the generated names avoid the reported deployment failure while the compatibility impact and any required user choice are addressed.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
cloud, infrastructure
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.