Skip to content

Secret Delivery Pattern

How partition-scoped operational secrets sourced from 1Password are delivered into AWS Secrets Manager at deploy time, where they then become available to:

  • CloudFormation Custom Resource Lambdas at deploy time (e.g., Postmark Sender Signature registration).
  • Runtime workloads via External Secrets Operator (ESO) in the partition’s Kubernetes cluster.

For where the source-of-truth secrets are stored in 1Password (vault layout, scoping rules), see secrets-vault.md.

┌─────────────────┐ op read ┌───────────┐ --parameters ┌───────────────────────┐
│ Arda-{Env}OAM │ ───────────────► │ amm.sh │ ─────────────────►│ CDK / CFN template │
│ (1Password) │ │ │ (NoEcho) │ (CfnParameter + │
└─────────────────┘ └───────────┘ │ SecretValue.cfn…) │
└──────────┬────────────┘
┌─────────────────────┐
│ AWS Secrets Manager │
│ (per-partition SM │
│ secret with stable │
│ ARN export) │
└──────────┬──────────┘
┌────────────────────────┴───────────────┐
▼ ▼
┌─────────────────┐ ┌──────────────────────┐
│ CR Lambda at │ │ ESO ExternalSecret │
│ deploy time │ │ → Kubernetes Secret │
│ (GetSecretValue)│ │ → pod env / mount │
└─────────────────┘ └──────────────────────┘
  1. Source of truth in 1Password. The secret value lives in the partition’s Arda-{Env}OAM vault (per the Secrets Vault convention). Same item titled identically across all four partition vaults — values may differ per partition but the schema doesn’t.
  2. Operator reads via amm.sh. amm.sh (or a callee) runs op read 'op://Arda-{Env}OAM/<item>/<field>' to capture the value in a shell variable. In GitHub Actions, echo "::add-mask::$value" is called immediately after the read to prevent accidental log exposure — op-sourced values are not auto-masked by GHA (only ${{ secrets.* }} are).
  3. Pass to CloudFormation / CDK as a NoEcho parameter. amm.sh invokes either:
    • aws cloudformation deploy --parameter-overrides "<Param>=$value" for raw-CFN stacks (e.g., partitionSecrets.cfn.yaml), or
    • cdk deploy --parameters "<Param>=$value" for CDK stacks. The CDK side declares a CfnParameter with noEcho: true and creates the SM secret via SecretValue.cfnParameter(thatParam).
  4. CloudFormation creates the SM secret. Type: AWS::SecretsManager::Secret with SecretString: !Ref <Param> (raw CFN) or the CDK-synthesized equivalent. NoEcho keeps the value out of DescribeStacks, change-sets, and event logs. RemovalPolicy.RETAIN defends against accidental destroy.
  5. Stack exports the SM secret ARN. Downstream stacks (CR Lambdas, ESO ExternalSecret manifests) consume the secret by ARN through Fn::ImportValue.
  6. Consumers read via GetSecretValue. CR Lambdas at deploy time; ESO at runtime (projecting into a Kubernetes Secret for the pod). The same SM secret can serve both consumer classes.

Source-of-truth-first rotation: update the 1Password item, then re-run amm.sh for the affected partition. The next cdk deploy / aws cloudformation deploy passes the new value through the NoEcho parameter and CloudFormation updates the SM secret’s SecretString. Direct aws secretsmanager put-secret-value writes will be reverted on the next deploy — this is intentional, keeping 1Password as the unambiguous source of truth.

  • partitionSecrets.cfn.yaml — the canonical raw-CFN instance of the pattern. Six secrets (ArdaApiKey, ArdaSignupKey, HubspotClientKey, HubspotPAT, PylonWidgetKey, AmazonCreatorsApi) all follow this shape today. See infrastructure/src/main/cfn/partitionSecrets.cfn.yaml and the Secrets step in infrastructure/amm.sh.
  • Phase 4 partition-email stack — Postmark account token — the canonical CDK instance. postmarkCredentialOpReference(partition) in platform/postmark-service.ts returns the op:// reference; amm.sh reads it; the partition-email stack declares a NoEcho CfnParameter and creates the SM secret via SecretValue.cfnParameter().
  • Use this pattern when the secret is sourced externally (third-party API key, vendor token, manually-issued credential) and needs to land in AWS Secrets Manager.
  • Do not use this pattern when:
    • The secret is generated by AWS itself (e.g., RDS-generated master passwords) — use the SM-native generateSecretString instead.
    • The secret is generated by CDK at synth time — use generateSecretString directly; no 1Password round-trip needed.
    • The secret never needs to land in AWS (e.g., a CI-only token consumed by GitHub Actions) — use ${{ secrets.* }} directly.
  • Secrets Vault — 1Password vault layout (the source-of-truth side of this pattern).
  • amm.sh — the operator entry point that orchestrates the op → CFN call.
  • partitionSecrets.cfn.yaml — the canonical worked example today.

Copyright: (c) Arda Systems 2025-2026, All rights reserved