ProofedMesh/deploy/trillian/README.md
CTO 951f22f80e
Some checks failed
build-images / signing-service image (push) Failing after 14s
validate / JSON Schema (ajv) (push) Successful in 21s
validate / Rego (opa fmt + opa test) (push) Successful in 14s
validate / Go (vet + test) (push) Successful in 30s
transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604)
- services/transparency-log/pkg/backend/trillian.go: real gRPC client
  on github.com/google/trillian v1.6.1. Submit() does QueueLeaf with the
  canonical DSSE envelope as LeafValue and the raw content_hash bytes as
  LeafIdentityHash, then polls GetLatestSignedLogRoot +
  GetInclusionProofByHash until the leaf is sequenced. Idempotency uses
  Trillian's ALREADY_EXISTS signal on the inner QueuedLogLeaf.Status, so
  a repeated submit still returns 200 OK with the originally-assigned
  log_index + a valid proof against the current STH.
- Unconfigured backend (missing endpoint or LogID) returns the historical
  ErrTrillianUnavailable so the httpapi layer keeps mapping it to 503
  instead of silently 500. Existing stub-returns-503 test updated.
- cmd/transparency-log: pointer-receiver Trillian wiring; flag docs
  mention the production SRV name.
- services/transparency-log/Dockerfile: distroless multi-stage build
  used by deploy/trillian/40-transparency-log.yaml.

- deploy/trillian/: full K8s stack for the agent-trust namespace —
  MySQL 8.4 StatefulSet (schema init ConfigMap vendored from upstream
  storage.sql v1.6.1), Trillian LogServer + LogSigner Deployments on
  gcr.io/trillian-opensource-ci:v1.6.1, createtree Job that writes
  the TreeID to a ConfigMap (re-apply safe), transparency-log
  Deployment with --backend=trillian pulling TRILLIAN_LOG_ID from
  the ConfigMap.
- deploy/trillian/README.md: first-time apply + smoke-test + secret
  rotation + signer-key clarification (STH key is Trillian-internal;
  DSSE envelope signing stays with signing-service / OpenBao).

- services/admin-api: go.mod / go.sum pick up the new transitive deps
  (trillian + grpc) via the local replace on transparency-log.
- services/transparency-log/README.md: Backends table updated; next-
  steps list no longer carries Trillian-deployment as a follow-up.

Deployed to the live cluster from this heartbeat: MySQL + LogServer +
LogSigner running in agent-trust, tree id 8482455225499059043, service
smoke-tested end-to-end (201 on first POST, 200 on idempotent repeat,
409 on content_hash mismatch, tree_size=2 with correct audit path).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-25 18:39:44 +00:00

102 lines
4.8 KiB
Markdown

# deploy/trillian — Trillian + transparency-log on Kubernetes
Deploys the production backend for ADR-0005 `POST /api/v1/log/entries` into
the `agent-trust` namespace of the BasicStack cluster.
## Component overview
| Manifest | What it brings up |
| ----------------------------- | ----------------------------------------------------- |
| `00-namespace.yaml` | `agent-trust` namespace. |
| `10-mysql.yaml` | MySQL 8.4 StatefulSet + schema init ConfigMap + secret. |
| `20-logserver.yaml` | Trillian LogServer Deployment + Service (8090 gRPC). |
| `21-logsigner.yaml` | Trillian LogSigner Deployment (`--force_master=true`). |
| `30-createtree-job.yaml` | One-shot Job that creates the LOG tree and writes the TreeID into a ConfigMap. Safe to re-apply. |
| `40-transparency-log.yaml` | `transparency-log` Deployment (ADR-0005 HTTP server) with `--backend=trillian`. |
`storage.sql` is the upstream Trillian MySQL schema (v1.6.1) vendored for
reference. The applied form lives inline inside the `trillian-mysql-init`
ConfigMap in `10-mysql.yaml`.
## First-time deploy
```bash
# From a node with cluster access (e.g. k3s-cp-1).
kubectl apply -f deploy/trillian/00-namespace.yaml
kubectl apply -f deploy/trillian/10-mysql.yaml
kubectl -n agent-trust rollout status statefulset/trillian-mysql --timeout=5m
kubectl apply -f deploy/trillian/20-logserver.yaml
kubectl apply -f deploy/trillian/21-logsigner.yaml
kubectl -n agent-trust rollout status deployment/trillian-log-server --timeout=5m
kubectl -n agent-trust rollout status deployment/trillian-log-signer --timeout=5m
# Creates the tree and populates the trillian-log-id ConfigMap.
kubectl apply -f deploy/trillian/30-createtree-job.yaml
kubectl -n agent-trust wait --for=condition=complete job/trillian-createtree --timeout=5m
kubectl -n agent-trust get configmap trillian-log-id -o jsonpath='{.data.TRILLIAN_LOG_ID}{"\n"}'
# Build and push the transparency-log image to the in-cluster registry.
# See services/transparency-log/Dockerfile.
docker build -t harbor.basicstack.de/proofedmesh/transparency-log:v0.1.0 \
-f services/transparency-log/Dockerfile services/transparency-log
docker push harbor.basicstack.de/proofedmesh/transparency-log:v0.1.0
kubectl apply -f deploy/trillian/40-transparency-log.yaml
kubectl -n agent-trust rollout status deployment/transparency-log --timeout=5m
```
## Smoke test
Exercise the deployed service end-to-end against the real Trillian backend.
```bash
# From inside the cluster:
kubectl -n agent-trust run smoke --rm -it --restart=Never --image=curlimages/curl -- \
sh -euxc '
ENV=$(printf "%s" "{\"payloadType\":\"application/vnd.in-toto+json\",\"payload\":\"aGVsbG8\",\"signatures\":[{\"keyid\":\"k\",\"sig\":\"c2ln\"}]}")
# content_hash comes from the admin-api attest.EnvelopeContentHash (or the
# transparency-log backend.EnvelopeContentHash helper). The two produce
# byte-identical SHA-256(canonical envelope).
curl -sS -X POST http://transparency-log.agent-trust.svc.cluster.local/api/v1/log/entries \
-H "Content-Type: application/json" \
-d "{\"envelope\":$ENV,\"content_hash\":\"<precomputed>\"}"
'
```
A successful first submit returns `201 Created` with an inclusion proof
against a tree size >= 1. Repeating the same submit returns `200 OK` with
the same `log_index` (idempotency).
## Secret rotation
`trillian-mysql` currently ships with hard-coded credentials for the first
deploy. Rotate them to a Sealed Secret before any real attestations land:
```bash
# Example — generate new creds, seal them, and replace 10-mysql.yaml's Secret.
NEW_PW=$(openssl rand -hex 24)
kubectl create secret generic trillian-mysql \
--namespace agent-trust \
--from-literal=MYSQL_ROOT_PASSWORD="$(openssl rand -hex 24)" \
--from-literal=MYSQL_USER=trillian \
--from-literal=MYSQL_PASSWORD="$NEW_PW" \
--from-literal=MYSQL_DATABASE=trillian \
--from-literal=MYSQL_URI="trillian:$NEW_PW@tcp(trillian-mysql.agent-trust.svc.cluster.local:3306)/trillian?parseTime=true" \
--dry-run=client -o yaml \
| kubeseal -o yaml > deploy/trillian/sealed-mysql-secret.yaml
```
Replace the plain Secret block in `10-mysql.yaml` with that sealed form and
re-apply.
## Signer key (future work)
Trillian stores its own tree-head signing key inside the `Trees` MySQL row.
That key is created and sealed by the createtree Job (`--signature_algorithm=ED25519`),
and the LogSigner uses it to sign STHs internally. **Our** transparency-log
service does not touch that key.
Services like admin-api sign DSSE envelopes via the separate `signing-service`
(DEV-599, already shipped). That service uses OpenBao Transit (future) or an
in-process keyring (current). It is unrelated to the Trillian tree key.