deploy: verification-service + dummy-sink Phase-1 manifests (DEV-615)
Some checks failed
build-images / signing-service image (push) Successful in 23s
build-images / verification-service image (push) Successful in 48s
build-images / dummy-sink image (push) Successful in 49s
validate / JSON Schema (ajv) (push) Successful in 23s
validate / Rego (opa fmt + opa test) (push) Successful in 15s
phase1-smoke / Phase 1 end-to-end smoke (push) Successful in 1m0s
validate / DB (migrate up/down dry-run) (push) Successful in 24s
validate / Go (vet + test) (push) Has been cancelled
Some checks failed
build-images / signing-service image (push) Successful in 23s
build-images / verification-service image (push) Successful in 48s
build-images / dummy-sink image (push) Successful in 49s
validate / JSON Schema (ajv) (push) Successful in 23s
validate / Rego (opa fmt + opa test) (push) Successful in 15s
phase1-smoke / Phase 1 end-to-end smoke (push) Successful in 1m0s
validate / DB (migrate up/down dry-run) (push) Successful in 24s
validate / Go (vet + test) (push) Has been cancelled
ServiceAccount, Deployment, Service, and NetworkPolicy per sub-directory, matching the signing-service template (non-root, read-only rootfs, distroless, agent-trust namespace). verification-service — NetworkPolicy allows ingress only from approval-service, testagent-transform, and dummy-sink, and egress only to transparency-log, trust-pg, Pocket-ID JWKS (HTTPS/443), and kube-dns. dummy-sink — adds a 1Gi longhorn PVC `dummy-sink-data` mounted at /data/dummy-sink, projected trust-api SA token at /var/run/secrets/tokens/trust-api, Pocket-ID client creds from a dev Secret, strategy Recreate (RWO PVC). NetworkPolicy allows ingress from testagent-transform and egress to verification-service, signing-service, transparency-log, HTTPS/443 (Pocket-ID OIDC), and kube-dns. build-images workflow — adds verification-service and dummy-sink jobs mirroring signing-service's BuildKit + Harbor pipeline. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
5bbd3b2c68
commit
5d2bf73d84
14 changed files with 888 additions and 0 deletions
|
|
@ -101,3 +101,139 @@ jobs:
|
|||
set -eu
|
||||
docker push "${{ steps.tags.outputs.image_sha }}"
|
||||
docker push "${{ steps.tags.outputs.image_main }}"
|
||||
|
||||
verification-service:
|
||||
name: verification-service image
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: debian:trixie-slim
|
||||
options: --user 0:0
|
||||
env:
|
||||
REGISTRY: harbor.basicstack.de
|
||||
IMAGE_REPO: harbor.basicstack.de/basicstack/verification-service
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- name: Install git + buildx client + ca-certs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
git curl ca-certificates docker.io docker-cli docker-buildx
|
||||
git --version
|
||||
docker --version
|
||||
docker buildx version
|
||||
|
||||
- name: Clone repository (sha256-aware)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git clone --depth=1 --branch="${GITHUB_REF_NAME}" \
|
||||
"https://x-access-token:${GITHUB_TOKEN}@forgejo.basicstack.de/${GITHUB_REPOSITORY}.git" \
|
||||
"${GITHUB_WORKSPACE}"
|
||||
cd "${GITHUB_WORKSPACE}"
|
||||
git log -1 --oneline
|
||||
|
||||
- name: Resolve tags
|
||||
id: tags
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse --short HEAD)"
|
||||
echo "sha=${SHA}" >> "${GITHUB_OUTPUT}"
|
||||
echo "image_sha=${IMAGE_REPO}:${SHA}" >> "${GITHUB_OUTPUT}"
|
||||
echo "image_main=${IMAGE_REPO}:main" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Build image
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx build \
|
||||
--load \
|
||||
-f services/verification-service/Dockerfile \
|
||||
-t "${{ steps.tags.outputs.image_sha }}" \
|
||||
-t "${{ steps.tags.outputs.image_main }}" \
|
||||
.
|
||||
|
||||
- name: Login to Harbor (push jobs only)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }}
|
||||
HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }}
|
||||
run: |
|
||||
set -eu
|
||||
echo "${HARBOR_PASSWORD}" | \
|
||||
docker login "${REGISTRY}" --username "${HARBOR_USERNAME}" --password-stdin
|
||||
|
||||
- name: Push image (main only)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
set -eu
|
||||
docker push "${{ steps.tags.outputs.image_sha }}"
|
||||
docker push "${{ steps.tags.outputs.image_main }}"
|
||||
|
||||
dummy-sink:
|
||||
name: dummy-sink image
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: debian:trixie-slim
|
||||
options: --user 0:0
|
||||
env:
|
||||
REGISTRY: harbor.basicstack.de
|
||||
IMAGE_REPO: harbor.basicstack.de/basicstack/dummy-sink
|
||||
DOCKER_BUILDKIT: "1"
|
||||
steps:
|
||||
- name: Install git + buildx client + ca-certs
|
||||
run: |
|
||||
set -euo pipefail
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
git curl ca-certificates docker.io docker-cli docker-buildx
|
||||
git --version
|
||||
docker --version
|
||||
docker buildx version
|
||||
|
||||
- name: Clone repository (sha256-aware)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git clone --depth=1 --branch="${GITHUB_REF_NAME}" \
|
||||
"https://x-access-token:${GITHUB_TOKEN}@forgejo.basicstack.de/${GITHUB_REPOSITORY}.git" \
|
||||
"${GITHUB_WORKSPACE}"
|
||||
cd "${GITHUB_WORKSPACE}"
|
||||
git log -1 --oneline
|
||||
|
||||
- name: Resolve tags
|
||||
id: tags
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHA="$(git rev-parse --short HEAD)"
|
||||
echo "sha=${SHA}" >> "${GITHUB_OUTPUT}"
|
||||
echo "image_sha=${IMAGE_REPO}:${SHA}" >> "${GITHUB_OUTPUT}"
|
||||
echo "image_main=${IMAGE_REPO}:main" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Build image
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
docker buildx build \
|
||||
--load \
|
||||
-f services/dummy-sink/Dockerfile \
|
||||
-t "${{ steps.tags.outputs.image_sha }}" \
|
||||
-t "${{ steps.tags.outputs.image_main }}" \
|
||||
.
|
||||
|
||||
- name: Login to Harbor (push jobs only)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
env:
|
||||
HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }}
|
||||
HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }}
|
||||
run: |
|
||||
set -eu
|
||||
echo "${HARBOR_PASSWORD}" | \
|
||||
docker login "${REGISTRY}" --username "${HARBOR_USERNAME}" --password-stdin
|
||||
|
||||
- name: Push image (main only)
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
set -eu
|
||||
docker push "${{ steps.tags.outputs.image_sha }}"
|
||||
docker push "${{ steps.tags.outputs.image_main }}"
|
||||
|
|
|
|||
14
deploy/dummy-sink/00-serviceaccount.yaml
Normal file
14
deploy/dummy-sink/00-serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: dummy-sink
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: sink
|
||||
# dummy-sink uses an EXPLICIT projected SA token mounted into
|
||||
# /var/run/secrets/tokens/trust-api as the second half of the
|
||||
# signing-service double-identity contract (DEV-611). The default
|
||||
# auto-mount is disabled so the pod only sees that scoped token.
|
||||
automountServiceAccountToken: false
|
||||
22
deploy/dummy-sink/10-pvc.yaml
Normal file
22
deploy/dummy-sink/10-pvc.yaml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# PersistentVolumeClaim for the dummy-sink payload store.
|
||||
#
|
||||
# Payloads land at /data/dummy-sink/<decision_id>.json via atomic
|
||||
# .tmp+rename (see services/dummy-sink/README.md). 1Gi is intentionally
|
||||
# small — this is a mock sink for Phase-1 smoke, not a production store.
|
||||
# Longhorn is BasicStack's cluster default dynamic-provisioning class.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: dummy-sink-data
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: sink
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
30
deploy/dummy-sink/11-secret-dev.example.yaml
Normal file
30
deploy/dummy-sink/11-secret-dev.example.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# dummy-sink dev secret — EXAMPLE ONLY.
|
||||
#
|
||||
# This file is a template. The real cluster Secret MUST be created either
|
||||
# as a SealedSecret (preferred, see deploy/dummy-sink/README.md) or
|
||||
# applied imperatively with values that never land in the git repo.
|
||||
#
|
||||
# Secret shape (Phase 1):
|
||||
# POCKET_ID_ISSUER — Pocket-ID OIDC issuer URL.
|
||||
# POCKET_ID_CLIENT_ID — client id for this agent.
|
||||
# POCKET_ID_CLIENT_SECRET — client secret for this agent.
|
||||
# POCKET_ID_AUDIENCE — expected `aud` claim (defaults to trust-api).
|
||||
#
|
||||
# The projected SA token for the X-Workload-Token side is mounted from
|
||||
# the pod's own ServiceAccount via a projected volume — no Secret
|
||||
# required for that half of the double-identity contract.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: dummy-sink-dev
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
proofedmesh.basicstack.de/mode: dev
|
||||
type: Opaque
|
||||
stringData:
|
||||
POCKET_ID_ISSUER: "https://auth.basicstack.de"
|
||||
POCKET_ID_CLIENT_ID: "REPLACE_WITH_POCKET_ID_CLIENT_ID"
|
||||
POCKET_ID_CLIENT_SECRET: "REPLACE_WITH_POCKET_ID_CLIENT_SECRET"
|
||||
POCKET_ID_AUDIENCE: "trust-api"
|
||||
125
deploy/dummy-sink/20-deployment.yaml
Normal file
125
deploy/dummy-sink/20-deployment.yaml
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dummy-sink
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: sink
|
||||
spec:
|
||||
# ReadWriteOnce PVC → single-writer. Scaling past replicas=1 requires
|
||||
# an RWX storage class or per-pod PVCs (not Phase-1 scope).
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 5
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: sink
|
||||
spec:
|
||||
serviceAccountName: dummy-sink
|
||||
# Default auto-mount is off; the projected `trust-api`-audience
|
||||
# token is provided via an explicit projected volume below.
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: dummy-sink
|
||||
image: harbor.basicstack.de/basicstack/dummy-sink:dev
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- "-listen=:8083"
|
||||
- "-data-dir=/data/dummy-sink"
|
||||
- "-verify-url=http://verification-service.agent-trust.svc.cluster.local:8084"
|
||||
- "-signing-url=http://signing-service.agent-trust.svc.cluster.local:8081"
|
||||
- "-tlog-url=http://transparency-log.agent-trust.svc.cluster.local"
|
||||
- "-sink-id=sink:dummy-sink"
|
||||
- "-actor=agent-dummy-sink"
|
||||
- "-signing-key-ref=trust-api/agent-dummy-sink-2026-q4"
|
||||
env:
|
||||
# Pocket-ID client credentials for the Authorization: Bearer
|
||||
# half of the signing-service double-identity check.
|
||||
- name: POCKET_ID_ISSUER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dummy-sink-dev
|
||||
key: POCKET_ID_ISSUER
|
||||
- name: POCKET_ID_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dummy-sink-dev
|
||||
key: POCKET_ID_CLIENT_ID
|
||||
- name: POCKET_ID_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dummy-sink-dev
|
||||
key: POCKET_ID_CLIENT_SECRET
|
||||
- name: POCKET_ID_AUDIENCE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: dummy-sink-dev
|
||||
key: POCKET_ID_AUDIENCE
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8083
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 20
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "256Mi"
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data/dummy-sink
|
||||
- name: workload-token
|
||||
mountPath: /var/run/secrets/tokens
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: dummy-sink-data
|
||||
# Projected ServiceAccount token addressed to the trust-api
|
||||
# audience. signing-service accepts this via its K8s
|
||||
# TokenReview path (DEV-609) as the workload-identity side of
|
||||
# the double-identity contract.
|
||||
- name: workload-token
|
||||
projected:
|
||||
sources:
|
||||
- serviceAccountToken:
|
||||
path: trust-api
|
||||
audience: trust-api
|
||||
expirationSeconds: 3600
|
||||
18
deploy/dummy-sink/30-service.yaml
Normal file
18
deploy/dummy-sink/30-service.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: dummy-sink
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: sink
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
ports:
|
||||
- name: http
|
||||
port: 8083
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
86
deploy/dummy-sink/40-networkpolicy.yaml
Normal file
86
deploy/dummy-sink/40-networkpolicy.yaml
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# NetworkPolicy for dummy-sink.
|
||||
#
|
||||
# Cluster-wide NetworkPolicy enforcement is active on BasicStack (see
|
||||
# cluster memory on NetworkPolicy activation). Pods in agent-trust
|
||||
# without an explicit allow-list are isolated; this file is the
|
||||
# explicit allow-list for dummy-sink per DEV-615.
|
||||
#
|
||||
# Ingress callers (per Phase-1 architecture V1.1 §7):
|
||||
# - testagent-transform — the sole producer of handover deliveries.
|
||||
#
|
||||
# Egress reachable targets (Phase-1 §7 Senken-Vertrag):
|
||||
# - verification-service — chain verification + replay guard.
|
||||
# - signing-service — handover-attestation signing.
|
||||
# - transparency-log — logging the signed handover envelope.
|
||||
# - DNS — kube-dns for service name resolution.
|
||||
#
|
||||
# dummy-sink intentionally does NOT egress Pocket-ID directly; it uses
|
||||
# the Pocket-ID OIDC endpoint via HTTPS/443 as the client_credentials
|
||||
# source for its Bearer token to signing-service. That cluster-external
|
||||
# target is included below.
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: dummy-sink
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: testagent-transform
|
||||
ports:
|
||||
- port: 8083
|
||||
protocol: TCP
|
||||
egress:
|
||||
# verification-service (same namespace).
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
ports:
|
||||
- port: 8084
|
||||
protocol: TCP
|
||||
# signing-service (same namespace).
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: signing-service
|
||||
ports:
|
||||
- port: 8081
|
||||
protocol: TCP
|
||||
# transparency-log (same namespace, port 80 → container 8080).
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: transparency-log
|
||||
ports:
|
||||
- port: 8080
|
||||
protocol: TCP
|
||||
# Pocket-ID OIDC token exchange + JWKS — cluster-external HTTPS/443.
|
||||
- ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
# DNS egress to kube-dns.
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
115
deploy/dummy-sink/README.md
Normal file
115
deploy/dummy-sink/README.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
# dummy-sink — deployment
|
||||
|
||||
Kubernetes manifests for the ADR-0007 dummy-sink in the `agent-trust`
|
||||
namespace. The namespace itself is created by
|
||||
[`deploy/trillian/00-namespace.yaml`](../trillian/00-namespace.yaml);
|
||||
this directory assumes that file (or an equivalent) is already applied.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
deploy/dummy-sink/
|
||||
├── 00-serviceaccount.yaml — ServiceAccount (auto-mount off, projected token used)
|
||||
├── 10-pvc.yaml — PVC dummy-sink-data (1Gi, longhorn, RWO)
|
||||
├── 11-secret-dev.example.yaml — TEMPLATE for the dev-mode Secret
|
||||
├── 20-deployment.yaml — Deployment (non-root, read-only rootfs, distroless)
|
||||
├── 30-service.yaml — ClusterIP Service on :8083
|
||||
├── 40-networkpolicy.yaml — ingress from testagent-transform,
|
||||
│ egress to verification-service / signing-service / transparency-log
|
||||
└── README.md — this file
|
||||
```
|
||||
|
||||
## Apply order
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/trillian/00-namespace.yaml # if not yet applied
|
||||
kubectl apply -f deploy/dummy-sink/00-serviceaccount.yaml
|
||||
kubectl apply -f deploy/dummy-sink/10-pvc.yaml
|
||||
# Create the dev Secret separately (see "Secret convention" below) — DO NOT
|
||||
# commit the real values. The .example file is a template, not a secret.
|
||||
kubectl apply -f deploy/dummy-sink/20-deployment.yaml
|
||||
kubectl apply -f deploy/dummy-sink/30-service.yaml
|
||||
kubectl apply -f deploy/dummy-sink/40-networkpolicy.yaml
|
||||
```
|
||||
|
||||
Reach the sink in-cluster at:
|
||||
|
||||
```
|
||||
http://dummy-sink.agent-trust.svc.cluster.local:8083
|
||||
```
|
||||
|
||||
Health check (also used by Deployment probes):
|
||||
|
||||
```
|
||||
GET /healthz → 200 {"status":"ok"}
|
||||
```
|
||||
|
||||
Deliver endpoint (per ADR-0007 / DEV-611):
|
||||
|
||||
```
|
||||
POST /api/v1/sink/dummy/deliver
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
## PVC
|
||||
|
||||
`dummy-sink-data` is a 1 Gi Longhorn RWO claim mounted at
|
||||
`/data/dummy-sink`. The Deployment uses `strategy.type: Recreate`
|
||||
because the PVC is `ReadWriteOnce` — a rolling update would try to
|
||||
schedule a second pod against the same volume. Scaling past a single
|
||||
replica requires an RWX storage class or per-pod PVCs; neither is in
|
||||
Phase-1 scope.
|
||||
|
||||
## K8s secret convention
|
||||
|
||||
The dummy-sink reads one Secret, `dummy-sink-dev` (or
|
||||
`dummy-sink-prod` for production), with the Pocket-ID client
|
||||
credentials. Shape:
|
||||
|
||||
| Env key | Purpose |
|
||||
| ------------------------- | ---------------------------------------------- |
|
||||
| `POCKET_ID_ISSUER` | Pocket-ID OIDC issuer URL |
|
||||
| `POCKET_ID_CLIENT_ID` | Pocket-ID client id for this agent |
|
||||
| `POCKET_ID_CLIENT_SECRET` | Pocket-ID client secret for this agent |
|
||||
| `POCKET_ID_AUDIENCE` | Expected `aud` claim (default `trust-api`) |
|
||||
|
||||
The second half of the double-identity contract (the projected
|
||||
ServiceAccount token at `/var/run/secrets/tokens/trust-api`) is wired
|
||||
by the Deployment's `projected` volume and does not require a Secret.
|
||||
|
||||
For the GitOps path, convert the created Secret to a SealedSecret and
|
||||
commit the sealed form under the same filename as the example:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic dummy-sink-dev -n agent-trust \
|
||||
--from-literal="POCKET_ID_ISSUER=https://auth.basicstack.de" \
|
||||
--from-literal="POCKET_ID_CLIENT_ID=<id>" \
|
||||
--from-literal="POCKET_ID_CLIENT_SECRET=<secret>" \
|
||||
--from-literal="POCKET_ID_AUDIENCE=trust-api" \
|
||||
--dry-run=client -o yaml | \
|
||||
kubeseal --controller-namespace=sealed-secrets --format=yaml \
|
||||
> deploy/dummy-sink/11-secret-dev.sealed.yaml
|
||||
```
|
||||
|
||||
## NetworkPolicy
|
||||
|
||||
See `40-networkpolicy.yaml`. In short:
|
||||
|
||||
- **Ingress:** `testagent-transform` pods only (matched by
|
||||
`app.kubernetes.io/name`).
|
||||
- **Egress:** `verification-service` (:8084), `signing-service`
|
||||
(:8081), `transparency-log` (:8080), external HTTPS/443 for the
|
||||
Pocket-ID OIDC endpoint, DNS to `kube-dns`.
|
||||
|
||||
## Image
|
||||
|
||||
Images are built by `.forgejo/workflows/build-images.yml` on every push
|
||||
to `main` and pushed to:
|
||||
|
||||
```
|
||||
harbor.basicstack.de/basicstack/dummy-sink:<git-sha>
|
||||
harbor.basicstack.de/basicstack/dummy-sink:main (floating)
|
||||
```
|
||||
|
||||
The Deployment currently references `:dev`; a GitOps apply will pin it
|
||||
to a `:<git-sha>` once the image build pipeline has produced one.
|
||||
13
deploy/verification-service/00-serviceaccount.yaml
Normal file
13
deploy/verification-service/00-serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: verification-service
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: trust-core
|
||||
# verification-service does not call the Kubernetes API server (DEV-612
|
||||
# scope: HTTP verification only), so the default auto-mounted SA token
|
||||
# serves no purpose and is disabled to keep the pod boundary tight.
|
||||
automountServiceAccountToken: false
|
||||
28
deploy/verification-service/10-secret-dev.example.yaml
Normal file
28
deploy/verification-service/10-secret-dev.example.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# verification-service dev secret — EXAMPLE ONLY.
|
||||
#
|
||||
# This file is a template. The real cluster Secret MUST be created either
|
||||
# as a SealedSecret (preferred, see deploy/verification-service/README.md)
|
||||
# or applied imperatively with values that never land in the git repo.
|
||||
#
|
||||
# Secret shape (Phase 1):
|
||||
# VERIFICATION_SIGNER_KEYS — comma- / newline-separated keyid=<enc>:<data>
|
||||
# loaded into the in-memory keyring.
|
||||
# Encodings: pem | hex | b64 | b64u.
|
||||
#
|
||||
# The verifier itself does not reach OpenBao Transit in Phase 1; it only
|
||||
# needs the public halves of the approval and transform signers to
|
||||
# validate DSSE envelopes it pulls from the transparency-log.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: verification-service-dev
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
proofedmesh.basicstack.de/mode: dev
|
||||
type: Opaque
|
||||
stringData:
|
||||
VERIFICATION_SIGNER_KEYS: |
|
||||
trust-api/approval-signer-2026-q4=pem:REPLACE_WITH_PEM_PUBLIC_KEY
|
||||
trust-api/agent-testagent-transform-2026-q4=hex:REPLACE_WITH_64_HEX_PUBLIC_KEY
|
||||
98
deploy/verification-service/20-deployment.yaml
Normal file
98
deploy/verification-service/20-deployment.yaml
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: verification-service
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: trust-core
|
||||
spec:
|
||||
replicas: 2
|
||||
revisionHistoryLimit: 5
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: trust-core
|
||||
spec:
|
||||
serviceAccountName: verification-service
|
||||
automountServiceAccountToken: false
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: verification-service
|
||||
image: harbor.basicstack.de/basicstack/verification-service:dev
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- "-listen=:8084"
|
||||
- "-backend=memory"
|
||||
env:
|
||||
- name: TLOG_URL
|
||||
value: "http://transparency-log.agent-trust.svc.cluster.local"
|
||||
# Keyids are the Phase-1 defaults; override via the Secret
|
||||
# below if a different key rotation lands.
|
||||
- name: APPROVAL_SIGNER_KEY_ID
|
||||
value: "trust-api/approval-signer-2026-q4"
|
||||
- name: TRANSFORM_SIGNER_KEY_ID
|
||||
value: "trust-api/agent-testagent-transform-2026-q4"
|
||||
# Public-key material for the DSSE validators. The Secret
|
||||
# name resolves per environment (see 10-secret-dev.example.yaml
|
||||
# for the shape).
|
||||
- name: VERIFICATION_SIGNER_KEYS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: verification-service-dev
|
||||
key: VERIFICATION_SIGNER_KEYS
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8084
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 10
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 20
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
limits:
|
||||
cpu: "1000m"
|
||||
memory: "512Mi"
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
18
deploy/verification-service/30-service.yaml
Normal file
18
deploy/verification-service/30-service.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: verification-service
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: trust-core
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: verification-service
|
||||
ports:
|
||||
- name: http
|
||||
port: 8084
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
93
deploy/verification-service/40-networkpolicy.yaml
Normal file
93
deploy/verification-service/40-networkpolicy.yaml
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# NetworkPolicy for verification-service.
|
||||
#
|
||||
# Cluster-wide NetworkPolicy enforcement is active on BasicStack (see
|
||||
# cluster memory on NetworkPolicy activation). Pods in agent-trust
|
||||
# without an explicit allow-list are isolated; this file is the
|
||||
# explicit allow-list for verification-service per DEV-615.
|
||||
#
|
||||
# Ingress callers (per Phase-1 architecture V1.1 §7):
|
||||
# - approval-service — supplies the approval predicate when a
|
||||
# fast chain pre-check is needed.
|
||||
# - testagent-transform — the transformation agent that submits
|
||||
# a chain to vet before shipping to the sink.
|
||||
# - dummy-sink — the Phase-1 sink that calls back into
|
||||
# verify/chain before persisting a payload.
|
||||
#
|
||||
# Egress reachable targets:
|
||||
# - transparency-log — pulls DSSE envelopes + inclusion proofs
|
||||
# (agent-trust/transparency-log, port 80).
|
||||
# - trust-pg — Postgres backing the replay-store
|
||||
# `verification_decisions` table (store=postgres).
|
||||
# - Pocket-ID JWKS — out-of-cluster `auth.basicstack.de` JWKS
|
||||
# fetch for OIDC validators (HTTPS/443).
|
||||
# - DNS — kube-dns in kube-system for service name
|
||||
# resolution.
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: verification-service
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: verification-service
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: approval-service
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: testagent-transform
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: dummy-sink
|
||||
ports:
|
||||
- port: 8084
|
||||
protocol: TCP
|
||||
egress:
|
||||
# transparency-log (in-cluster, agent-trust namespace, port 80).
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: transparency-log
|
||||
ports:
|
||||
- port: 8080
|
||||
protocol: TCP
|
||||
# trust-api Postgres (replay-store backend). Reached via the
|
||||
# `trust-pg` Service in the same namespace.
|
||||
- to:
|
||||
- podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: trust-pg
|
||||
ports:
|
||||
- port: 5432
|
||||
protocol: TCP
|
||||
# Pocket-ID JWKS discovery is served from the public ingress
|
||||
# (`auth.basicstack.de`). Egress is cluster-external HTTPS; no
|
||||
# cluster selector will match, so this is an unrestricted
|
||||
# TCP/443 egress rule.
|
||||
- ports:
|
||||
- port: 443
|
||||
protocol: TCP
|
||||
# DNS egress to kube-dns (cluster hostname resolution + out-of-
|
||||
# cluster CNAME lookups for Pocket-ID JWKS).
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
92
deploy/verification-service/README.md
Normal file
92
deploy/verification-service/README.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# verification-service — deployment
|
||||
|
||||
Kubernetes manifests for the ADR-0006 verification-service in the
|
||||
`agent-trust` namespace. The namespace itself is created by
|
||||
[`deploy/trillian/00-namespace.yaml`](../trillian/00-namespace.yaml);
|
||||
this directory assumes that file (or an equivalent) is already applied.
|
||||
|
||||
## Layout
|
||||
|
||||
```
|
||||
deploy/verification-service/
|
||||
├── 00-serviceaccount.yaml — ServiceAccount (auto-mount off)
|
||||
├── 10-secret-dev.example.yaml — TEMPLATE for the dev-mode Secret
|
||||
├── 20-deployment.yaml — Deployment (non-root, read-only rootfs, distroless)
|
||||
├── 30-service.yaml — ClusterIP Service on :8084
|
||||
├── 40-networkpolicy.yaml — ingress from approval-service / testagent-transform / dummy-sink,
|
||||
│ egress to TLog + trust-pg + Pocket-ID JWKS
|
||||
└── README.md — this file
|
||||
```
|
||||
|
||||
## Apply order
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/trillian/00-namespace.yaml # if not yet applied
|
||||
kubectl apply -f deploy/verification-service/00-serviceaccount.yaml
|
||||
# Create the dev Secret separately (see "Secret convention" below) — DO NOT
|
||||
# commit the real values. The .example file is a template, not a secret.
|
||||
kubectl apply -f deploy/verification-service/20-deployment.yaml
|
||||
kubectl apply -f deploy/verification-service/30-service.yaml
|
||||
kubectl apply -f deploy/verification-service/40-networkpolicy.yaml
|
||||
```
|
||||
|
||||
Reach the service in-cluster at:
|
||||
|
||||
```
|
||||
http://verification-service.agent-trust.svc.cluster.local:8084
|
||||
```
|
||||
|
||||
Health check (also used by Deployment probes):
|
||||
|
||||
```
|
||||
GET /healthz → 200 {"status":"ok"}
|
||||
```
|
||||
|
||||
Verify endpoint (per ADR-0006 / DEV-612):
|
||||
|
||||
```
|
||||
POST /api/v1/verify/chain
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
## K8s secret convention
|
||||
|
||||
The verification-service only reads one Secret — `verification-service-dev`
|
||||
(or `verification-service-prod` when the key rotation uses production
|
||||
material). The Secret shape is a single key, `VERIFICATION_SIGNER_KEYS`,
|
||||
whose value is the comma- or newline-separated keyid=`<enc>:<data>` spec
|
||||
documented in `services/verification-service/README.md`.
|
||||
|
||||
For the GitOps path, convert the created Secret to a SealedSecret with
|
||||
`kubeseal` and commit the sealed form under the same filename as the
|
||||
example:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic verification-service-dev -n agent-trust \
|
||||
--from-file=VERIFICATION_SIGNER_KEYS=./signer-keys.spec \
|
||||
--dry-run=client -o yaml | \
|
||||
kubeseal --controller-namespace=sealed-secrets --format=yaml \
|
||||
> deploy/verification-service/10-secret-dev.sealed.yaml
|
||||
```
|
||||
|
||||
## NetworkPolicy
|
||||
|
||||
See `40-networkpolicy.yaml`. In short:
|
||||
|
||||
- **Ingress:** `approval-service`, `testagent-transform`, `dummy-sink`
|
||||
pods (matched by `app.kubernetes.io/name`).
|
||||
- **Egress:** `transparency-log` (:8080), `trust-pg` (:5432), external
|
||||
HTTPS/443 for the Pocket-ID JWKS, DNS to `kube-dns`.
|
||||
|
||||
## Image
|
||||
|
||||
Images are built by `.forgejo/workflows/build-images.yml` on every push
|
||||
to `main` and pushed to:
|
||||
|
||||
```
|
||||
harbor.basicstack.de/basicstack/verification-service:<git-sha>
|
||||
harbor.basicstack.de/basicstack/verification-service:main (floating)
|
||||
```
|
||||
|
||||
The Deployment currently references `:dev`; a GitOps apply will pin it
|
||||
to a `:<git-sha>` once the image build pipeline has produced one.
|
||||
Loading…
Add table
Reference in a new issue