ProofedMesh/deploy/trillian
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
..
00-namespace.yaml transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604) 2026-10-25 18:39:44 +00:00
10-mysql.yaml transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604) 2026-10-25 18:39:44 +00:00
20-logserver.yaml transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604) 2026-10-25 18:39:44 +00:00
21-logsigner.yaml transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604) 2026-10-25 18:39:44 +00:00
30-createtree-job.yaml transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604) 2026-10-25 18:39:44 +00:00
40-transparency-log.yaml transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604) 2026-10-25 18:39:44 +00:00
README.md transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604) 2026-10-25 18:39:44 +00:00
storage.sql transparency-log: real Trillian backend wiring + agent-trust deploy (DEV-604) 2026-10-25 18:39:44 +00:00

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

# 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.

# 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:

# 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.