Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
colifran opened this issue May 9, 2024 · 1 comment
Labels
@aws-cdk/core Related to core CDK functionality bug This issue is a bug. p2

Comments

@colifran
Copy link
Contributor

colifran commented May 9, 2024

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

function generateExportName(importStack: Stack, reference: Reference, id: string): string {
const referenceStack = Stack.of(reference.target);
const components = [
referenceStack.stackName ?? '',
referenceStack.region,
id,
];
const prefix = `${importStack.nestedStackParent?.stackName ?? importStack.stackName}/`;
const localPart = makeUniqueId(components);
// max name length for a system manager parameter is 1011 characters
// including the arn, i.e.
// arn:aws:ssm:us-east-2:111122223333:parameter/cdk/exports/${stackName}/${name}
const maxLength = 900;
return prefix + localPart.slice(Math.max(0, localPart.length - maxLength + prefix.length));
}
. 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

@colifran colifran added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. p2 @aws-cdk/core Related to core CDK functionality and removed needs-triage This issue or PR still needs to be triaged. labels May 9, 2024
@renschler
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@aws-cdk/core Related to core CDK functionality bug This issue is a bug. p2
Projects
None yet
Development

No branches or pull requests

2 participants