refactor(infra): migrate cross-stack auth references to SSM Parameter Store#117
Open
okamoto-aws wants to merge 2 commits intomainfrom
Open
refactor(infra): migrate cross-stack auth references to SSM Parameter Store#117okamoto-aws wants to merge 2 commits intomainfrom
okamoto-aws wants to merge 2 commits intomainfrom
Conversation
… Store Replaces CDK-auto-generated CloudFormation Export/Import between AuthStack and downstream stacks (RuntimeStack, AgentStack, WebUiStack) with SSM Parameter Store. Decouples AuthStack from downstream so that future Auth changes can be implemented declaratively without triggering cross-stack export deletion errors (of the kind PR #112 had to work around with a custom resource). - AuthStack publishes 7 SSM parameters under /sdpm/auth/* (user-pool-id, user-pool-arn, web-client-id, mcp-client-id, mcp-custom-scope, cognito-domain-prefix, oidc-discovery-url) - RuntimeStack, AgentStack, and WebUiStack read values via SSM instead of receiving construct references through props - AuthStack retains 3 legacy auto-generated exports explicitly via overrideLogicalId to keep existing deployments upgradeable in-place without "Cannot delete export" errors - Downstream stacks receive a useAuthStack: boolean prop; when true, they read from SSM and enable Cognito-specific features, when false (external IdP) they fall back to the existing string props - Explicit stack.addDependency(authStack) ensures SSM params exist before downstream reads them Design: see docs/internal/ssm-cross-stack-refs.md
detect-secrets flags the CDK-generated hex hashes in the legacy export names (e.g. 6BA7E5F2Arn686ACC00) as Base64 high-entropy strings. These are stable construct-path hashes, not credentials — retained for backward compatibility of cross-stack imports. Annotate each line with '// pragma: allowlist secret' to silence the scanner.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The problem
CDK auto-generates CloudFormation
Exports/Fn::ImportValuepairs whenever one stack passes a construct reference to another stack. This creates a hidden rigidity:UserPoolClientscope change, which requires replacement) fails withCannot delete export <name> as it is in use by <stack>.As the project grows, each new Auth change risks another round of custom resource workarounds.
The goal
Replace CDK's automatic cross-stack
Export/Importwith SSM Parameter Store for values passed fromAuthStackto downstream stacks. This decouplesAuthStackfromRuntimeStack,AgentStack, andWebUiStack, so that future Auth changes can be implemented declaratively inAuthStackwithout fear of breaking downstream deployments.Why SSM Parameter Store
AWS re:Post explicitly recommends SSM Parameter Store for cross-stack value sharing in production workloads: 'Using SSM parameters unties the relationship between exporting stack and importing stack.'. Industry guidance (Chariot Solutions, Darren Holland) converges on the same advice: 'use outputs lavishly, exports sparingly'.
Constraints
This repo is deployed across many independent environments on arbitrary prior commits. There is no central coordination or staged rollout.
This rules out phased migration PR series because we cannot require users to deploy intermediate versions.
Design
Values migrated
authStack.userPool.userPoolArn/sdpm/auth/user-pool-arnauthStack.userPool.userPoolId/sdpm/auth/user-pool-idauthStack.userPoolClient.userPoolClientId/sdpm/auth/web-client-idauthStack.mcpClientId/sdpm/auth/mcp-client-id(sentinel-when unset)authStack.mcpCustomScope/sdpm/auth/mcp-custom-scopeauthStack.cognitoDomainPrefix/sdpm/auth/cognito-domain-prefixauthStack.oidcDiscoveryUrl/sdpm/auth/oidc-discovery-urlDownstream stacks receive a
useAuthStack: booleanprop; when true they read values from SSM, when false (external IdP mode) they fall back to existing string props (oidcDiscoveryUrl,allowedClients).Backward compatibility
Existing deployed environments have these auto-generated exports registered and imported by downstream stacks:
SdpmAuth:ExportsOutputRefUserPool6BA7E5F296FD7236SdpmAuth:ExportsOutputRefUserPoolWebClient4C9370B02E2C9FF9SdpmAuth:ExportsOutputFnGetAttUserPool6BA7E5F2Arn686ACC00If we simply remove the construct references (which this PR does), CDK stops emitting those exports, and CloudFormation refuses to delete them while any deployed downstream template still imports them ('Cannot delete export').
The official remediation is to manually retain the legacy exports using
overrideLogicalIdand a matchingexportName. This PR does exactly that inauth-stack.ts:The legacy exports are retained indefinitely to satisfy the invariant above. Comments in
auth-stack.tswarn future maintainers not to remove them.Deployment order
bin/infra.tsadds explicitstack.addDependency(authStack)calls so thatSdpmAuthdeploys first and its SSM parameters exist before downstream stacks try to read them.Compatibility matrix
deploy.shmcpCustomScope)deploy.shmcpCustomScopeSSM param populated. ✅deploy.shUpdateCognitoCallbackUrlscustom resource retained for now (out of scope for this PR). ✅deploy.shChanges
infra/lib/auth-stack.tsoverrideLogicalId, exportAUTH_SSM_PARAMSconstantinfra/lib/web-ui-stack.tsUserPoolviafromUserPoolArn; always include MCP scope inUpdateCognitoCallbackUrlsandaws-exports.jsoninfra/lib/runtime-stack.tsuseAuthStack: booleanprop; in AuthStack mode, read OIDC URL / UserPool ID / Domain Prefix / MCP Client ID / MCP Scope from SSM; fall back to props for external IdPinfra/lib/agent-stack.tsuseAuthStack: booleanprop; in AuthStack mode, read OIDC URL and WebClient ID from SSM; fall back toprops.allowedClientsfor external IdPinfra/bin/infra.tsauthStack.userPool/authStack.userPoolClient/authStack.clientId/authStack.oidcDiscoveryUrletc. to downstream stacks (that triggered CDK auto-generated exports). PassuseAuthStack: !!authStackinstead. Add explicitstack.addDependency(authStack)Risks and mitigations
section 'Outputs' already contains <id>at synth time: CDK emits an auto-export whenever a downstream stack holds a construct reference to an AuthStack resource. If that auto-export shares the logical ID of our manually retained legacy export, synth fails. Mitigated by removing everyauthStack.userPool,authStack.userPoolClient,authStack.clientId, andauthStack.oidcDiscoveryUrlreference inbin/infra.ts. Verified viacdk synth SdpmAuth.ssm.StringParameter.valueForStringParameteremits a CFN template parameter of typeAWS::SSM::Parameter::Value<String>; CloudFormation resolves the real value at deploy time. Values are always fresh.UpdateCognitoCallbackUrlscustom resource (from PR fix: add sdpm-mcp/invoke scope to WebClient OAuth config #112) remains: Out of scope for this PR. The custom resource continues to be the single source of truth for WebClient OAuth scopes, now always includingsdpm-mcp/invoke.Verification
npx tsc --noEmit— clean, 0 errorsnpx cdk synth SdpmAuth— success; template contains:AWS::SSM::ParameterresourcesDeployment verification is out of scope for this PR; the author will test after merge.
References