Skip to content

feat(auth,app): ES256 JWT signing + AWS Secrets Manager key resolver#58

Merged
jcsvwinston merged 1 commit into
mainfrom
feature/es256-aws-secrets-manager
May 14, 2026
Merged

feat(auth,app): ES256 JWT signing + AWS Secrets Manager key resolver#58
jcsvwinston merged 1 commit into
mainfrom
feature/es256-aws-secrets-manager

Conversation

@jcsvwinston
Copy link
Copy Markdown
Owner

Summary

Implements the MVP scope the owner approved on 2026-05-14 — ES256 (P-256) JWT signing and an AWS Secrets Manager resolver for JWT key material. Designed and justified in ADR-005.

Lands on top of v0.7.0 (PR #57). Explicit non-goals, deferred to a follow-up: P-384/ES512/Ed25519, GCP Secret Manager, Azure Key Vault, HashiCorp Vault.

ES256 — pure stdlib, no new dependency

  • pkg/auth: ES256 SigningAlgorithm + SigningKey.ECDSAPrivate. The validate / signingMethod / signMaterial / verifyMaterial / toJWK switches each gain an ES256 case. validate rejects any curve other than P-256 — a P-384 key with algorithm: ES256 fails fast at App.New. JWK gains Crv/X/Y; toJWK emits kty: EC, crv: P-256 with RFC 7518 §6.2 fixed-length (32-byte, left-padded) coordinates.
  • pkg/app/jwt_setup.go: ES256 case in loadJWTKey; parseECDSAPrivateKey accepts SEC1 and PKCS#8, rejects non-P-256. Shared PEM plumbing factored out (loadPEMBytes, decodeSinglePEMBlock).

AWS Secrets Manager resolver

  • New package pkg/auth/secrets: a Resolver interface, EnvResolver (zero-dependency — env:NAME and bare names), AWSSecretsManagerResolver (aws-sm:<id> and aws-sm:<id>#<json-key>), and a Chain that routes by scheme. The AWS SDK sits behind a one-method secretsManagerAPI interface; every constructor returns the framework's own Resolver interface — no AWS SDK type reaches a stable pkg/* signature.
  • jwt_setup.go: secret_env / pem_env are now resolver references. A bare name or env:NAME reads the environment (unchanged behaviour); aws-sm:<id> reads AWS Secrets Manager. The AWS SDK client is built lazily — only when a jwt_keys[] entry uses the aws-sm: scheme — so non-AWS deployments never touch AWS credential resolution.
  • buildJWTManager now takes a context.Context for lazy resolver construction.

Dependency

Adds github.com/aws/aws-sdk-go-v2/config and .../service/secretsmanager — the first cloud-vendor SDK in the tree, gated entirely to pkg/auth/secrets. dependency-impact review recorded at docs/reports/dependency_impact_aws_sdk_2026-05-14.md — verdict ACCEPT-WITH-NOTE: firewall clean, Apache-2.0, ~3-5 MB link delta, two cosmetic/Phase-4 follow-ups noted.

Test plan

  • pkg/auth/jwt_es256_test.go — sign/validate roundtrip, JWKS EC key shape, P-256 enforcement (rejects P-384), coordinate left-padding invariant, alg-vs-kid mismatch rejection.
  • pkg/auth/secrets/*_test.goEnvResolver (bare + env: forms), AWSSecretsManagerResolver against a fake SDK client (plain secret, JSON-key fragment, missing secret, binary-secret rejection, SDK-error propagation), Chain scheme routing, compile-time proof the real *secretsmanager.Client satisfies the narrowed interface.
  • pkg/app/jwt_setup_es256_test.go — ES256 PEM loading (SEC1 + PKCS#8), non-P-256 rejection, RSA-material rejection, JWKS auto-mount via ES256.
  • resolver-chain wiring tests — env: prefix acceptance, lazy AWS construction (env-only keyset never builds the AWS resolver).
  • go test ./... — clean.
  • bash scripts/ci/check_contract_freeze.sh — PASS (ES256 additions are additive; firewall test confirms no AWS type leak).

Known follow-ups (from the dependency-impact review)

  • Re-run go mod tidy once the pre-existing admin/proto replace-directive issue is unblocked, so the AWS modules carry correct // direct annotations.
  • Phase 4: consider making the AWS SDK an opt-in link dependency (build tag / plugin) — pkg/app currently imports pkg/auth/secrets unconditionally.

🤖 Generated with Claude Code

Implements the MVP scope approved by the owner on 2026-05-14 (ADR-005):
P-256 ES256 plus an AWS Secrets Manager resolver. Explicit non-goals
deferred to a follow-up: P-384/ES512/Ed25519, GCP Secret Manager,
Azure Key Vault, HashiCorp Vault.

ES256 (pure stdlib, no new dependency):

- pkg/auth: ES256 SigningAlgorithm + SigningKey.ECDSAPrivate. The
  validate / signingMethod / signMaterial / verifyMaterial / toJWK
  switches each gain an ES256 case. validate rejects any curve other
  than P-256. JWK gains Crv/X/Y fields; toJWK emits kty:EC, crv:P-256
  with RFC 7518 fixed-length (32-byte, left-padded) coordinates.
- pkg/app/jwt_setup.go: ES256 case in loadJWTKey; parseECDSAPrivateKey
  accepts SEC1 and PKCS#8, rejects non-P-256. Shared PEM plumbing
  (loadPEMBytes, decodeSinglePEMBlock) factored out.

AWS Secrets Manager resolver:

- New package pkg/auth/secrets: a Resolver interface, EnvResolver
  (zero-dep, resolves env:NAME and bare names), AWSSecretsManagerResolver
  (aws-sm:<id> and aws-sm:<id>#<json-key>), and a Chain that routes by
  scheme. The AWS SDK sits behind a one-method secretsManagerAPI
  interface; every constructor returns the framework's own Resolver
  interface — no AWS SDK type reaches a stable pkg/* signature.
- jwt_setup.go: secret_env / pem_env are now resolver references. A
  bare name or env:NAME reads the environment (unchanged behaviour);
  aws-sm:<id> reads AWS Secrets Manager. The AWS SDK client is built
  lazily — only when a jwt_keys[] entry uses the aws-sm: scheme — so
  non-AWS deployments never touch AWS credential resolution.
- buildJWTManager now takes a context.Context for the lazy resolver
  construction; App.New passes context.Background().

Dependency: adds github.com/aws/aws-sdk-go-v2/{config,service/
secretsmanager}. First cloud-vendor SDK in the tree, gated entirely to
pkg/auth/secrets. dependency-impact review recorded at
docs/reports/dependency_impact_aws_sdk_2026-05-14.md (verdict:
ACCEPT-WITH-NOTE — firewall clean, Apache-2.0, ~3-5MB link delta).

Tests: pkg/auth/jwt_es256_test.go (sign/validate, JWKS EC shape,
P-256 enforcement, coordinate padding), pkg/auth/secrets/* (EnvResolver,
AWSSecretsManagerResolver with a fake SDK client, Chain routing),
pkg/app/jwt_setup_es256_test.go (PEM loading SEC1+PKCS#8, JWKS
auto-mount), plus resolver-chain wiring tests. Full go test ./...
and contract freeze green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jcsvwinston jcsvwinston merged commit e53f72b into main May 14, 2026
9 checks passed
jcsvwinston added a commit that referenced this pull request May 14, 2026
Session End Protocol for the iteration that swept the post-ADR-004
queue, released v0.7.0, and landed the ES256 + AWS Secrets Manager MVP
(PRs #56, #57, #58).

- Archive the iteration at docs/iterations/2026-05-14-v0.7.0-release-and-es256.md.
- Reset CURRENT_ITERATION.md to an empty slate with a ranked
  candidate-next-steps list (CSRF hardening is the top item).
- Refresh HANDOFF.md: main @ e53f72b, v0.7.0 tagged, no active
  iteration, open housekeeping noted.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant