Compare commits
2 Commits
feat/feder
...
feat/feder
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
586129faf4 | ||
|
|
92c6431ccf |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -9,3 +9,6 @@ coverage
|
|||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
docs/reports/
|
docs/reports/
|
||||||
|
|
||||||
|
# Step-CA dev password — real file is gitignored; commit only the .example
|
||||||
|
infra/step-ca/dev-password
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ services:
|
|||||||
postgres-federated:
|
postgres-federated:
|
||||||
image: pgvector/pgvector:pg17
|
image: pgvector/pgvector:pg17
|
||||||
profiles: [federated]
|
profiles: [federated]
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- '${PG_FEDERATED_HOST_PORT:-5433}:5432'
|
- '${PG_FEDERATED_HOST_PORT:-5433}:5432'
|
||||||
environment:
|
environment:
|
||||||
@@ -45,6 +46,7 @@ services:
|
|||||||
valkey-federated:
|
valkey-federated:
|
||||||
image: valkey/valkey:8-alpine
|
image: valkey/valkey:8-alpine
|
||||||
profiles: [federated]
|
profiles: [federated]
|
||||||
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- '${VALKEY_FEDERATED_HOST_PORT:-6380}:6379'
|
- '${VALKEY_FEDERATED_HOST_PORT:-6380}:6379'
|
||||||
volumes:
|
volumes:
|
||||||
@@ -55,6 +57,64 @@ services:
|
|||||||
timeout: 3s
|
timeout: 3s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Step-CA — Mosaic Federation internal certificate authority
|
||||||
|
#
|
||||||
|
# Image: pinned to 0.27.4 (latest stable as of late 2025).
|
||||||
|
# `latest` is forbidden per Mosaic image policy (immutable tag required for
|
||||||
|
# reproducible deployments and digest-first promotion in CI).
|
||||||
|
#
|
||||||
|
# Profile: `federated` — this service must not start in non-federated dev.
|
||||||
|
#
|
||||||
|
# Password:
|
||||||
|
# Dev: bind-mount ./infra/step-ca/dev-password (gitignored; copy from
|
||||||
|
# ./infra/step-ca/dev-password.example and customise locally).
|
||||||
|
# Prod: replace the bind-mount with a Docker secret:
|
||||||
|
# secrets:
|
||||||
|
# ca_password:
|
||||||
|
# external: true
|
||||||
|
# and reference it as `/run/secrets/ca_password` (same path the
|
||||||
|
# init script already uses).
|
||||||
|
#
|
||||||
|
# Provisioner: "mosaic-fed" (consumed by apps/gateway/src/federation/ca.service.ts)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
step-ca:
|
||||||
|
image: smallstep/step-ca:0.27.4
|
||||||
|
profiles: [federated]
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '${STEP_CA_HOST_PORT:-9000}:9000'
|
||||||
|
volumes:
|
||||||
|
- step_ca_data:/home/step
|
||||||
|
# init script — executed as the container entrypoint
|
||||||
|
- ./infra/step-ca/init.sh:/usr/local/bin/mosaic-step-ca-init.sh:ro
|
||||||
|
# X.509 template skeleton (wired in M2-04)
|
||||||
|
- ./infra/step-ca/templates:/etc/step-ca-templates:ro
|
||||||
|
# Dev password file — GITIGNORED; copy from dev-password.example
|
||||||
|
# In production, replace this with a Docker secret (see comment above).
|
||||||
|
- ./infra/step-ca/dev-password:/run/secrets/ca_password:ro
|
||||||
|
entrypoint: ['/bin/sh', '/usr/local/bin/mosaic-step-ca-init.sh']
|
||||||
|
healthcheck:
|
||||||
|
# The healthcheck requires the root cert to exist, which is only true
|
||||||
|
# after init.sh has completed on first boot. start_period gives init
|
||||||
|
# time to finish before Docker starts counting retries.
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
'CMD',
|
||||||
|
'step',
|
||||||
|
'ca',
|
||||||
|
'health',
|
||||||
|
'--ca-url',
|
||||||
|
'https://localhost:9000',
|
||||||
|
'--root',
|
||||||
|
'/home/step/certs/root_ca.crt',
|
||||||
|
]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pg_federated_data:
|
pg_federated_data:
|
||||||
valkey_federated_data:
|
valkey_federated_data:
|
||||||
|
step_ca_data:
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ Goal: Two federated-tier gateways stood up on Portainer at `mos-test-1.woltje.co
|
|||||||
Goal: An admin can create a federation grant; counterparty enrolls; cert is signed by Step-CA with SAN OIDs for `grantId` + `subjectUserId`. No runtime federation traffic flows yet (that's M3).
|
Goal: An admin can create a federation grant; counterparty enrolls; cert is signed by Step-CA with SAN OIDs for `grantId` + `subjectUserId`. No runtime federation traffic flows yet (that's M3).
|
||||||
|
|
||||||
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
| id | status | description | issue | agent | branch | depends_on | estimate | notes |
|
||||||
| --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | ------ | ---------------------------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | ------ | ---------------------------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| FED-M2-01 | needs-qa | DB migration: `federation_grants`, `federation_peers`, `federation_audit_log` tables + enum types (`grant_status`, `peer_state`). Drizzle schema + migration generation; migration tests. | #461 | sonnet | feat/federation-m2-schema | — | 5K | PR #486 open. First review NEEDS CHANGES (missing DESC indexes + reserved cols). Remediation subagent `a673dd9355dc26f82` in flight in worktree `agent-a4404ac1`. |
|
| FED-M2-01 | needs-qa | DB migration: `federation_grants`, `federation_peers`, `federation_audit_log` tables + enum types (`grant_status`, `peer_state`). Drizzle schema + migration generation; migration tests. | #461 | sonnet | feat/federation-m2-schema | — | 5K | PR #486 open. First review NEEDS CHANGES (missing DESC indexes + reserved cols). Remediation subagent `a673dd9355dc26f82` in flight in worktree `agent-a4404ac1`. |
|
||||||
| FED-M2-02 | not-started | Add Step-CA sidecar to `docker-compose.federated.yml`: official `smallstep/step-ca` image, persistent CA volume, JWK provisioner config baked into init script. | #461 | sonnet | feat/federation-m2-stepca | DEPLOY-02 | 4K | Profile-gated under `federated`. CA password from secret; dev compose uses dev-only password file. |
|
| FED-M2-02 | not-started | Add Step-CA sidecar to `docker-compose.federated.yml`: official `smallstep/step-ca` image, persistent CA volume, JWK provisioner config baked into init script. | #461 | sonnet | feat/federation-m2-stepca | DEPLOY-02 | 4K | Profile-gated under `federated`. CA password from secret; dev compose uses dev-only password file. |
|
||||||
| FED-M2-03 | not-started | Scope JSON schema + validator: `resources` allowlist, `excluded_resources`, `include_teams`, `include_personal`, `max_rows_per_query`. Vitest unit tests for valid + invalid scopes. | #461 | sonnet | feat/federation-m2-scope-schema | — | 4K | Validator independent of CA — reusable from grant CRUD + (later) M3 scope enforcement. |
|
| FED-M2-03 | not-started | Scope JSON schema + validator: `resources` allowlist, `excluded_resources`, `include_teams`, `include_personal`, `max_rows_per_query`. Vitest unit tests for valid + invalid scopes. | #461 | sonnet | feat/federation-m2-scope-schema | — | 4K | Validator independent of CA — reusable from grant CRUD + (later) M3 scope enforcement. |
|
||||||
| FED-M2-04 | not-started | `apps/gateway/src/federation/ca.service.ts`: Step-CA client (CSR submission, OID-bearing cert retrieval). Mocked + integration tests against real Step-CA container. | #461 | sonnet | feat/federation-m2-ca-service | M2-02 | 6K | SAN OIDs: `grantId` (custom OID 1.3.6.1.4.1.99999.1) + `subjectUserId` (1.3.6.1.4.1.99999.2). Document OID assignments in PRD/SETUP. |
|
| FED-M2-04 | not-started | `apps/gateway/src/federation/ca.service.ts`: Step-CA client (CSR submission, OID-bearing cert retrieval). Mocked + integration tests against real Step-CA container. | #461 | sonnet | feat/federation-m2-ca-service | M2-02 | 6K | SAN OIDs: `grantId` (custom OID 1.3.6.1.4.1.99999.1) + `subjectUserId` (1.3.6.1.4.1.99999.2). Document OID assignments in PRD/SETUP. **Acceptance**: must (a) wire `federation.tpl` template into `mosaic-fed` provisioner config and (b) include a unit/integration test asserting issued certs contain BOTH OIDs — fails-loud guard against silent OID stripping (carry-forward from M2-02 review). |
|
||||||
| FED-M2-05 | not-started | Sealed storage for `client_key_pem` reusing existing `provider_credentials` sealing key. Tests prove DB-at-rest is ciphertext, not PEM. Key rotation path documented (deferred impl). | #461 | sonnet | feat/federation-m2-key-sealing | M2-01 | 5K | Separate from M2-06 to keep crypto seam isolated; reviewer focus is sealing only. |
|
| FED-M2-05 | not-started | Sealed storage for `client_key_pem` reusing existing `provider_credentials` sealing key. Tests prove DB-at-rest is ciphertext, not PEM. Key rotation path documented (deferred impl). | #461 | sonnet | feat/federation-m2-key-sealing | M2-01 | 5K | Separate from M2-06 to keep crypto seam isolated; reviewer focus is sealing only. |
|
||||||
| FED-M2-06 | not-started | `grants.service.ts`: CRUD + status transitions (`pending` → `active` → `revoked`); integrates M2-03 (scope) + M2-05 (sealing). Unit tests cover all transitions including invalid ones. | #461 | sonnet | feat/federation-m2-grants-service | M2-03, M2-05 | 6K | Business logic only — CSR + cert work delegated to M2-04. Revocation handler is M6. |
|
| FED-M2-06 | not-started | `grants.service.ts`: CRUD + status transitions (`pending` → `active` → `revoked`); integrates M2-03 (scope) + M2-05 (sealing). Unit tests cover all transitions including invalid ones. | #461 | sonnet | feat/federation-m2-grants-service | M2-03, M2-05 | 6K | Business logic only — CSR + cert work delegated to M2-04. Revocation handler is M6. |
|
||||||
| FED-M2-07 | not-started | `enrollment.controller.ts`: short-lived single-use token endpoint; CSR signing; updates grant `pending` → `active`; emits enrollment audit (table-only write, M4 tightens). | #461 | sonnet | feat/federation-m2-enrollment | M2-04, M2-06 | 6K | Tokens single-use with 410 on replay; tokens TTL'd at 15min; rate-limited at request layer (M4 introduces guard, M2 uses simple lock). |
|
| FED-M2-07 | not-started | `enrollment.controller.ts`: short-lived single-use token endpoint; CSR signing; updates grant `pending` → `active`; emits enrollment audit (table-only write, M4 tightens). | #461 | sonnet | feat/federation-m2-enrollment | M2-04, M2-06 | 6K | Tokens single-use with 410 on replay; tokens TTL'd at 15min; rate-limited at request layer (M4 introduces guard, M2 uses simple lock). |
|
||||||
|
|||||||
1
infra/step-ca/dev-password.example
Normal file
1
infra/step-ca/dev-password.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
dev-only-step-ca-password-do-not-use-in-production
|
||||||
60
infra/step-ca/init.sh
Executable file
60
infra/step-ca/init.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# infra/step-ca/init.sh
|
||||||
|
#
|
||||||
|
# Idempotent first-boot initialiser for the Mosaic Federation CA.
|
||||||
|
#
|
||||||
|
# On the first run (no /home/step/config/ca.json present) this script:
|
||||||
|
# 1. Initialises Step-CA with a JWK provisioner named "mosaic-fed".
|
||||||
|
# 2. Writes the CA configuration to the persistent volume at /home/step.
|
||||||
|
#
|
||||||
|
# On subsequent runs (config already exists) this script skips init and
|
||||||
|
# starts the CA directly.
|
||||||
|
#
|
||||||
|
# The provisioner name "mosaic-fed" is consumed by:
|
||||||
|
# apps/gateway/src/federation/ca.service.ts (added in M2-04)
|
||||||
|
#
|
||||||
|
# Password source:
|
||||||
|
# Dev: mounted from ./infra/step-ca/dev-password via bind mount.
|
||||||
|
# Prod: mounted from a Docker secret at /run/secrets/ca_password.
|
||||||
|
#
|
||||||
|
# OID template:
|
||||||
|
# infra/step-ca/templates/federation.tpl is copied into the CA config
|
||||||
|
# directory so the JWK provisioner can reference it. The template
|
||||||
|
# skeleton is wired in M2-04 when the CA service lands the SAN-bearing
|
||||||
|
# CSR work.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
CA_CONFIG="/home/step/config/ca.json"
|
||||||
|
PASSWORD_FILE="/run/secrets/ca_password"
|
||||||
|
|
||||||
|
if [ ! -f "${CA_CONFIG}" ]; then
|
||||||
|
echo "[step-ca init] First boot detected — initialising Mosaic Federation CA..."
|
||||||
|
|
||||||
|
step ca init \
|
||||||
|
--name "Mosaic Federation CA" \
|
||||||
|
--dns "localhost" \
|
||||||
|
--dns "step-ca" \
|
||||||
|
--address ":9000" \
|
||||||
|
--provisioner "mosaic-fed" \
|
||||||
|
--password-file "${PASSWORD_FILE}" \
|
||||||
|
--provisioner-password-file "${PASSWORD_FILE}" \
|
||||||
|
--no-db
|
||||||
|
|
||||||
|
echo "[step-ca init] CA initialised."
|
||||||
|
|
||||||
|
# Copy the X.509 template into the Step-CA config directory so the
|
||||||
|
# provisioner can reference it in M2-04.
|
||||||
|
if [ -f "/etc/step-ca-templates/federation.tpl" ]; then
|
||||||
|
mkdir -p /home/step/templates
|
||||||
|
cp /etc/step-ca-templates/federation.tpl /home/step/templates/federation.tpl
|
||||||
|
echo "[step-ca init] Federation X.509 template copied to /home/step/templates/."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[step-ca init] Startup complete."
|
||||||
|
else
|
||||||
|
echo "[step-ca init] Config already exists — skipping init."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[step-ca init] Starting Step-CA on :9000..."
|
||||||
|
exec step-ca /home/step/config/ca.json --password-file "${PASSWORD_FILE}"
|
||||||
48
infra/step-ca/templates/federation.tpl
Normal file
48
infra/step-ca/templates/federation.tpl
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"subject": {{ toJson .Subject }},
|
||||||
|
"sans": {{ toJson .SANs }},
|
||||||
|
|
||||||
|
{{- /*
|
||||||
|
Mosaic Federation X.509 Certificate Template
|
||||||
|
============================================
|
||||||
|
This template is used by the "mosaic-fed" JWK provisioner to sign
|
||||||
|
federation client certificates.
|
||||||
|
|
||||||
|
Custom OID extensions (per PRD §6):
|
||||||
|
1.3.6.1.4.1.99999.1 — mosaic.federation.grantId (UUID string)
|
||||||
|
1.3.6.1.4.1.99999.2 — mosaic.federation.subjectUserId (UUID string)
|
||||||
|
|
||||||
|
TODO (M2-04): Wire actual OID extensions below once the CA service
|
||||||
|
(apps/gateway/src/federation/ca.service.ts) lands the SAN-bearing CSR
|
||||||
|
work and the template can be exercised end-to-end.
|
||||||
|
|
||||||
|
Step-CA template reference:
|
||||||
|
https://smallstep.com/docs/step-ca/templates
|
||||||
|
|
||||||
|
Expected final shape of the extensions block (placeholder — not yet
|
||||||
|
activated):
|
||||||
|
|
||||||
|
"extensions": [
|
||||||
|
{
|
||||||
|
"id": "1.3.6.1.4.1.99999.1",
|
||||||
|
"critical": false,
|
||||||
|
"value": {{ toJson (first .Token.mosaic_grant_id) }}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1.3.6.1.4.1.99999.2",
|
||||||
|
"critical": false,
|
||||||
|
"value": {{ toJson (first .Token.mosaic_subject_user_id) }}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
The provisioner must pass these values in the ACME/JWK token payload
|
||||||
|
(token claims `mosaic_grant_id` and `mosaic_subject_user_id`) when
|
||||||
|
submitting the CSR. M2-04 owns that work.
|
||||||
|
*/ -}}
|
||||||
|
|
||||||
|
"keyUsage": ["digitalSignature"],
|
||||||
|
"extKeyUsage": ["clientAuth"],
|
||||||
|
"basicConstraints": {
|
||||||
|
"isCA": false
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user