Skip to main content

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 NamePurposeEncryptionRotationAccessed By
/isb/<ns>/Auth/JwtSecretJWT signing key for API authCustomer KMS30 days (auto)Lambda Authorizer, SSO Handler
/isb/<ns>/Auth/IdpCertSAML IdP X.509 certificateCustomer KMSManualSSO 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):

StepActionImplementation
createSecretGenerate new 32-char random password via GetRandomPasswordCommand, store as AWSPENDINGActive
setSecretNo-op (no external system to update)NOOP
testSecretNo-op (JWT validation tested on first use)NOOP
finishSecretPromote AWSPENDING to AWSCURRENT via UpdateSecretVersionStageCommandActive

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:

  1. Download the new IdP metadata from IAM Identity Center
  2. Extract the X.509 certificate
  3. Update the secret via AWS Console or CLI:
    aws secretsmanager update-secret \
    --secret-id /isb/<namespace>/Auth/IdpCert \
    --secret-string "$(cat new-certificate.pem)"
  4. 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 NameTypePurposeCreated By
/isb/<ns>/data/configString (JSON)DynamoDB table names, AppConfig IDs, KMS key ID, solution versionCDK (IsbDataResources)
/isb/<ns>/idc/configString (JSON)Identity Center instance configurationCDK

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

SecretPurpose
AWS_ROLE_ARNIAM role ARN for OIDC-based CDK deployment

innovation-sandbox-on-aws-costs

SecretPurpose
AWS_ROLE_ARNIAM role ARN for OIDC-based CDK deployment
COST_EXPLORER_ROLE_ARNCross-account role for Cost Explorer queries
ISB_LEASES_LAMBDA_ARNISB Leases Lambda ARN (passed as CDK context)

innovation-sandbox-on-aws-deployer

SecretPurpose
AWS_DEPLOY_ROLE_ARNIAM role ARN for ECR/Lambda deployment

ndx

SecretPurpose
ISB_NDX_USERS_GROUP_IDIdentity Center group ID for signup Lambda

ndx-try-aws-scp

SecretPurpose
AWS_ROLE_ARNIAM role ARN for Terraform deployment
SLACK_BUDGET_ALERT_EMAILEmail for Slack-routed budget alerts

GitHub Variables (Non-Secret)

Some workflows also use GitHub Variables (non-sensitive, visible in settings):

VariableExampleUsed By
AWS_REGIONus-east-1billing-seperator, costs
EVENT_BUS_NAMEISBEventBuscosts
ALERT_EMAILteam emailcosts

4. Lambda Environment Variables

Non-sensitive configuration is passed to Lambda functions via environment variables at deployment time:

VariableExample ValuePurpose
JWT_SECRET_NAME/isb/ndx-try-isb/Auth/JwtSecretSecret name reference (not the secret itself)
IDP_CERT_SECRET_NAME/isb/ndx-try-isb/Auth/IdpCertSecret name reference
INTERMEDIATE_ROLE_ARNarn:aws:iam::...:role/InnovationSandbox-ndx-IntermediateRoleCross-account hop
IDC_ROLE_ARNarn:aws:iam::<idc-account>:role/...Identity Center access
ISB_NAMESPACEndx-try-isbNamespace for parameter resolution
APP_CONFIG_APPLICATION_IDApplication IDAppConfig lookup
APP_CONFIG_ENVIRONMENT_IDEnvironment IDAppConfig lookup
APP_CONFIG_PROFILE_IDProfile IDAppConfig lookup
POWERTOOLS_SERVICE_NAMESsoHandlerStructured logging
USER_AGENT_EXTRACustom user agentSDK 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

SecretMethodFrequencyLambdaAutomated
JWT SecretSecrets Manager rotation30 daysJwtSecretRotatorYes
IdP CertificateManual update~1 year (cert expiry)N/ANo
GitHub Secrets (Role ARNs)Manual updateWhen IAM roles recreatedN/ANo
GitHub Secrets (API Keys)Manual updateRecommended 90 daysN/ANo

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 unsafePlainText used only for placeholder values (IdP cert initial value)

Audit Trail

All secret access is logged via CloudTrail:

  • secretsmanager:GetSecretValue -- secret retrieval
  • secretsmanager:PutSecretValue -- rotation writes
  • secretsmanager:UpdateSecretVersionStage -- rotation promotion
  • ssm:GetParameter -- parameter reads
  • kms:Decrypt -- KMS key usage for decryption


Generated from source analysis. See 00-repo-inventory.md for full inventory.