aws-cdk
aws-cdk copied to clipboard
(core): `crossRegionReferences` generates unnecessarily long SSM parameter names leading to deployment failures
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 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
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<string> | 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
Thanks @colifran for reference I don't use nested stacks, and my stack prefixes are only 4 letters long, and I was still running into this issue due do the number of regions AWS has available.