Files
suaveplan/openspec/archive/2026-05-02-testing-integration-step-ca/proposal.md
2026-05-27 23:29:53 +01:00

4.7 KiB

Why

Several integration scenarios on genesis need real HTTPS, not rejectUnauthorized: false shortcuts: WebRTC transports (drive-webrtc, transport-webrtc) refuse non-secure contexts; HTTPS edges (server-caddy-imgproxy, future Caddy/nginx fixtures); SMTPS/IMAPS variants of testing-integration-mailpit; the HTTPS path of testing-integration-garage-s3. Today every consumer either rolls its own self-signed certs (and a per-suite trust dance) or skips the secure variant entirely.

We already have a working step-ca deployment at ../certs/ (PostLocker CA: smallstep/step-ca + Caddy/LE + a Hono API + a download portal). The CA core is reusable for tests; the public-ops layers (Caddy/LE, REST API, SQLite key DB, ACME, domain-policy) are not. This change ports the CA nucleus into the existing testing-integration-* plugin family so any fixture can opt into a real PKI chain with one line.

What Changes

New plugin package — @suaveplan/testing-integration-step-ca

  • Lives at packages/dev/testing-integration-step-ca (mirrors the testing-integration-* plugin convention from CLAUDE.md).
  • Implements ServiceFixture<StepCaConfig> from @suaveplan/testing-integration with image smallstep/step-ca pinned to a digest.
  • StepCaConfig is { caUrl: string; rootCertPem: string; rootCertPath: string; fingerprint: string; provisionerName: string; provisionerPassword: string }.
  • Env-bridge passthrough mode (5 vars, all required): STEPCA_URL, STEPCA_ROOT_CERT_PEM, STEPCA_ROOT_CERT_PATH, STEPCA_FINGERPRINT, STEPCA_PROVISIONER_PASSWORD. Partial set (4 of 5) falls through to container mode. Passthrough stop() is a no-op.
  • Container mode: starts step-ca, runs an init+configure sequence equivalent to ../certs/scripts/init-ca.sh + configure-ca.sh but trimmed to the parts a test harness needs:
    1. step ca init --deployment-type standalone --name <caName> --dns <dnsNames> --address :8443 --provisioner <provName> --password-file <pwFile>
    2. read /home/step/certs/root_ca.crt → rootCertPem
    3. step certificate fingerprint /home/step/certs/root_ca.crt → fingerprint
    4. step crypto jwk create (EC P-256, ES256, sig) for the JWK provisioner, password-encrypted
    5. jq insert the JWK provisioner into ca.json
    6. write rootCertPem to a host tmpfile and expose its path as rootCertPath
  • Health: GET https://localhost:<caPort>/health with TLS verification disabled (the root isn't trusted yet on the host); injectable fetchFn. Network errors / non-ok → false. Default timeout STEPCA_HEALTH_TIMEOUT_MS = 60_000, poll STEPCA_HEALTH_POLL_INTERVAL_MS = 1000.
  • Public helper issueCert({ commonName, sans?, ttl? }) → { certPem, keyPem, chainPem, serial } — shells step ca certificate against the JWK provisioner inside the container via the injected execFn (default container.exec). Returns leaf + chain PEMs and the serial.
  • Errors prefixed INTEGRATION.STEP_CA.* via @suaveplan/error. Injectable containerFactory, execFn, and fetchFn for unit-testability of the fixture itself (mirrors the garage-s3 contract).
  • Pre-publish gate: 100% Istanbul coverage per openspec/AGENTS.md §7.

Out of scope — explicitly NOT ported

  • Caddy / Let's Encrypt public TLS (tests don't need DNS or public certs).
  • Hono OpenAPI cert-management service (api/), the SQLite API-key DB, and bearer-scope auth.
  • Web download portal (web/) and the step CLI binary mirror.
  • ACME provisioner and domain-policy enforcement (API_ALLOWED_DOMAINS).

The fixture issues leaves directly through the JWK provisioner using step ca certificate — no REST hop, no auth layer, no policy enforcement (test code knows what names it wants).

Capabilities

New Capabilities

  • testing-integration-step-ca — a ServiceFixture for smallstep/step-ca (image policy, env-bridge, health-check contract, init+configure sequence, issueCert() helper, error code prefix).

Impact

  • Affected code: new package at /mnt/s/web/genesis/packages/dev/testing-integration-step-ca/**.
  • Affected consumers: opt-in. drive-webrtc, transport-webrtc, transport-https, server-caddy-imgproxy, and the secure variants of mailpit / garage-s3 fixtures gain a path to real-chain HTTPS. Zero behaviour change for fixtures that don't depend on it.
  • Catalog additions (root package.json): none — testcontainers and @suaveplan/testing-integration are already pinned.
  • CLAUDE.md / MANIFEST.md: add the new package to the Packages section in CLAUDE.md, then run bun scripts/folder-reorg/generate-manifest.ts to refresh MANIFEST.md.
  • Cross-cutting: new capability spec only; no existing capability is modified. On archive the spec folds into openspec/specs/testing-integration-step-ca/spec.md.