- 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>
102 lines
4.8 KiB
Markdown
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.
|