Secrets Management
Last Updated: 2026-03-06 Sources:
innovation-sandbox-on-aws(auth-api.ts, secret-rotator-handler.ts, sso-handler/config.ts),innovation-sandbox-on-aws-costs,innovation-sandbox-on-aws-billing-seperator,innovation-sandbox-on-aws-deployer,ndx,ndx-try-aws-scp, GitHub Actions workflow files
Executive Summary
The NDX:Try AWS platform manages secrets across three tiers: AWS Secrets Manager for runtime application secrets (JWT signing key, IdP certificate, API keys), SSM Parameter Store for non-sensitive configuration sharing between CDK stacks, and GitHub repository secrets for CI/CD deployment parameters. Only the JWT signing secret has automated rotation (30-day cycle via a dedicated Lambda function); all other secrets require manual rotation. All Secrets Manager secrets are encrypted with customer-managed KMS keys.
Secrets Management Architecture
1. AWS Secrets Manager
Secrets Inventory
| Secret Name | Purpose | Encryption | Rotation | Accessed By |
|---|---|---|---|---|
/isb/<ns>/Auth/JwtSecret | JWT signing key for API auth | Customer KMS | 30 days (auto) | Lambda Authorizer, SSO Handler |
/isb/<ns>/Auth/IdpCert | SAML IdP X.509 certificate | Customer KMS | Manual | SSO Handler |
JWT Secret
Full Path: /isb/<namespace>/Auth/JwtSecret
CDK Definition (auth-api.ts):
const jwtTokenSecret = new Secret(scope, "JwtSecret", {
secretName: `${SECRET_NAME_PREFIX}/${props.namespace}/Auth/JwtSecret`,
description: "The secret for JWT used by Innovation Sandbox",
encryptionKey: kmsKey,
generateSecretString: {
passwordLength: 32,
},
});
Rotation Schedule:
jwtTokenSecret.addRotationSchedule("RotationSchedule", {
rotationLambda: jwtSecretRotatorLambda.lambdaFunction,
automaticallyAfter: Duration.days(30),
rotateImmediatelyOnUpdate: true,
});
Rotation Lambda: JwtSecretRotator with reservedConcurrentExecutions: 1 to prevent concurrent rotation.
Rotation Process (from secret-rotator-handler.ts):
| Step | Action | Implementation |
|---|---|---|
createSecret | Generate new 32-char random password via GetRandomPasswordCommand, store as AWSPENDING | Active |
setSecret | No-op (no external system to update) | NOOP |
testSecret | No-op (JWT validation tested on first use) | NOOP |
finishSecret | Promote AWSPENDING to AWSCURRENT via UpdateSecretVersionStageCommand | Active |
Access Pattern:
const secretsManagerHelper = IsbClients.secretsManager(env);
const jwtSecret = await secretsManagerHelper.getStringSecret(env.JWT_SECRET_NAME);
The authorizer Lambda caches the JWT secret in a module-level variable (let jwtSecret = "") initialized lazily on first invocation, reducing Secrets Manager API calls across warm Lambda invocations.
Source: authorizer/src/authorization.ts lines 18, 129-133
IdP Certificate
Full Path: /isb/<namespace>/Auth/IdpCert
CDK Definition (auth-api.ts):
const idpCertSecret = new Secret(scope, "IdpCert", {
secretName: `${SECRET_NAME_PREFIX}/${props.namespace}/Auth/IdpCert`,
description: "IAM Identity Center Certificate of the ISB SAML 2.0 custom app",
encryptionKey: kmsKey,
secretStringValue: SecretValue.unsafePlainText(
"Please paste the IAM Identity Center Certificate of the" +
" Innovation Sandbox SAML 2.0 custom application here"
),
});
Format: PEM-encoded X.509 certificate
Rotation: Manual. When the IAM Identity Center SAML application certificate rotates (typically annually), an administrator must:
- Download the new IdP metadata from IAM Identity Center
- Extract the X.509 certificate
- Update the secret via AWS Console or CLI:
aws secretsmanager update-secret \--secret-id /isb/<namespace>/Auth/IdpCert \--secret-string "$(cat new-certificate.pem)"
- Test SAML authentication
Access Pattern: The SSO handler fetches both secrets in a single batch call:
const allSecrets = await secretsManagerHelper.getStringSecrets(
env.JWT_SECRET_NAME,
env.IDP_CERT_SECRET_NAME,
);
Source: sso-handler/src/config.ts lines 37-39
IAM Access Policy
Lambda functions that need secret access receive a targeted policy:
const secretAccessPolicy = new aws_iam.PolicyStatement({
actions: ["secretsmanager:GetSecretValue"],
effect: aws_iam.Effect.ALLOW,
resources: [jwtTokenSecret.secretArn, idpCertSecret.secretArn],
});
ssoLambda.lambdaFunction.addToRolePolicy(secretAccessPolicy);
kmsKey.grantEncryptDecrypt(ssoLambda.lambdaFunction);
Both secretsmanager:GetSecretValue and kms:Encrypt/Decrypt are required because the secrets are encrypted with a customer-managed KMS key.
Source: auth-api.ts lines 103-107, 166-168
2. SSM Parameter Store
Parameter Inventory
| Parameter Name | Type | Purpose | Created By |
|---|---|---|---|
/isb/<ns>/data/config | String (JSON) | DynamoDB table names, AppConfig IDs, KMS key ID, solution version | CDK (IsbDataResources) |
/isb/<ns>/idc/config | String (JSON) | Identity Center instance configuration | CDK |
Data Configuration Parameter
Parameter Name: Set by sharedDataSsmParamName(namespace)
Contents (JSON):
{
"configApplicationId": "...",
"configEnvironmentId": "...",
"globalConfigConfigurationProfileId": "...",
"nukeConfigConfigurationProfileId": "...",
"reportingConfigConfigurationProfileId": "...",
"accountTable": "SandboxAccountTable-...",
"leaseTemplateTable": "LeaseTemplateTable-...",
"leaseTable": "LeaseTable-...",
"tableKmsKeyId": "key-id",
"solutionVersion": "...",
"supportedSchemas": "[\"1\"]"
}
Purpose: Cross-stack configuration sharing. Lambda functions read this parameter at startup to discover DynamoDB table names and AppConfig profile IDs without hardcoding them.
Source: isb-data-resources.ts lines 86-106
Access Pattern
Lambda functions are granted SSM read access via a shared helper:
grantIsbSsmParameterRead(
ssoLambda.lambdaFunction.role as Role,
sharedIdcSsmParamName(props.namespace),
props.idcAccountId,
);
3. GitHub Secrets
Repository-Level Secrets
GitHub repository secrets are used to pass deployment-time configuration to GitHub Actions workflows. These are encrypted at rest by GitHub and masked in workflow logs.
innovation-sandbox-on-aws-billing-seperator
| Secret | Purpose |
|---|---|
AWS_ROLE_ARN | IAM role ARN for OIDC-based CDK deployment |
innovation-sandbox-on-aws-costs
| Secret | Purpose |
|---|---|
AWS_ROLE_ARN | IAM role ARN for OIDC-based CDK deployment |
COST_EXPLORER_ROLE_ARN | Cross-account role for Cost Explorer queries |
ISB_LEASES_LAMBDA_ARN | ISB Leases Lambda ARN (passed as CDK context) |
innovation-sandbox-on-aws-deployer
| Secret | Purpose |
|---|---|
AWS_DEPLOY_ROLE_ARN | IAM role ARN for ECR/Lambda deployment |
ndx
| Secret | Purpose |
|---|---|
ISB_NDX_USERS_GROUP_ID | Identity Center group ID for signup Lambda |
ndx-try-aws-scp
| Secret | Purpose |
|---|---|
AWS_ROLE_ARN | IAM role ARN for Terraform deployment |
SLACK_BUDGET_ALERT_EMAIL | Email for Slack-routed budget alerts |
GitHub Variables (Non-Secret)
Some workflows also use GitHub Variables (non-sensitive, visible in settings):
| Variable | Example | Used By |
|---|---|---|
AWS_REGION | us-east-1 | billing-seperator, costs |
EVENT_BUS_NAME | ISBEventBus | costs |
ALERT_EMAIL | team email | costs |
4. Lambda Environment Variables
Non-sensitive configuration is passed to Lambda functions via environment variables at deployment time:
| Variable | Example Value | Purpose |
|---|---|---|
JWT_SECRET_NAME | /isb/ndx-try-isb/Auth/JwtSecret | Secret name reference (not the secret itself) |
IDP_CERT_SECRET_NAME | /isb/ndx-try-isb/Auth/IdpCert | Secret name reference |
INTERMEDIATE_ROLE_ARN | arn:aws:iam::...:role/InnovationSandbox-ndx-IntermediateRole | Cross-account hop |
IDC_ROLE_ARN | arn:aws:iam::<idc-account>:role/... | Identity Center access |
ISB_NAMESPACE | ndx-try-isb | Namespace for parameter resolution |
APP_CONFIG_APPLICATION_ID | Application ID | AppConfig lookup |
APP_CONFIG_ENVIRONMENT_ID | Environment ID | AppConfig lookup |
APP_CONFIG_PROFILE_ID | Profile ID | AppConfig lookup |
POWERTOOLS_SERVICE_NAME | SsoHandler | Structured logging |
USER_AGENT_EXTRA | Custom user agent | SDK call attribution |
These are never used for sensitive values. Actual secrets are always resolved at runtime from Secrets Manager.
Source: auth-api.ts lines 143-154, rest-api-all.ts lines 83-89
5. Secret Flow Diagram
6. Secrets Rotation Summary
| Secret | Method | Frequency | Lambda | Automated |
|---|---|---|---|---|
| JWT Secret | Secrets Manager rotation | 30 days | JwtSecretRotator | Yes |
| IdP Certificate | Manual update | ~1 year (cert expiry) | N/A | No |
| GitHub Secrets (Role ARNs) | Manual update | When IAM roles recreated | N/A | No |
| GitHub Secrets (API Keys) | Manual update | Recommended 90 days | N/A | No |
7. Naming Conventions
Secrets Manager
Pattern: /{prefix}/{namespace}/{category}/{name}
Examples:
/isb/ndx-try-isb/Auth/JwtSecret/isb/ndx-try-isb/Auth/IdpCert
The prefix is defined by SECRET_NAME_PREFIX from isb-types.js.
SSM Parameter Store
Pattern: /{namespace}/{stack}/{parameter-name}
Examples:
/isb/ndx-try-isb/data/config/isb/ndx-try-isb/idc/config
GitHub Secrets
Pattern: UPPERCASE_WITH_UNDERSCORES
Examples: AWS_ROLE_ARN, COST_EXPLORER_ROLE_ARN, ISB_NDX_USERS_GROUP_ID
8. Security Best Practices
Implemented
- Customer-managed KMS encryption for all Secrets Manager secrets
- Automatic rotation for the most critical secret (JWT signing key)
- Least-privilege IAM policies scoped to specific secret ARNs
- Lambda-level secret caching to reduce API call frequency
- Reserved concurrency of 1 on the rotation Lambda to prevent race conditions
- Secrets never stored in Lambda environment variables (only name references)
- GitHub secrets masked in workflow logs
- CDK
unsafePlainTextused only for placeholder values (IdP cert initial value)
Audit Trail
All secret access is logged via CloudTrail:
secretsmanager:GetSecretValue-- secret retrievalsecretsmanager:PutSecretValue-- rotation writessecretsmanager:UpdateSecretVersionStage-- rotation promotionssm:GetParameter-- parameter readskms:Decrypt-- KMS key usage for decryption
Related Documents
- 60-auth-architecture.md - JWT and SAML authentication flows
- 61-encryption.md - KMS key management and encryption at rest
- 05-service-control-policies.md - Guardrails protecting ISB resources
- 10-isb-core-architecture.md - Core ISB Lambda and data architecture
- 51-oidc-configuration.md - GitHub OIDC and role ARN configuration
Generated from source analysis. See 00-repo-inventory.md for full inventory.