signing-service: Dockerfile, K8s manifests (agent-trust), image CI, secret convention
- Dockerfile: multi-stage (golang:1.23-alpine3.21 → distroless/static:nonroot), CGO off, trimpath, stripped, UID/GID 65532, read-only rootfs friendly. - deploy/signing-service/: ServiceAccount, Deployment (2 replicas, probes, tight SecurityContext), ClusterIP Service on :8081, NetworkPolicy (ingress from agent-trust, DNS egress), example Secret, and README with the full dev + prod secret convention. - .forgejo/workflows/build-images.yml: builds image on push + PR, pushes :<git-sha> + :main to harbor.basicstack.de on main pushes only. Clone pattern matches validate.yml (sha256 repo, manual git clone). - README updates: service README gains Deployment section; repo README layout now mentions services/signing-service, services/transparency-log, deploy/, and the new workflow. Scope matches DEV-605. OpenBao, OPA and Pocket-ID JWKS stay stubs and move in their own follow-up subtasks; the prod secret shape is documented so those subtasks can plug in without reshaping the Deployment. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
5d338dd725
commit
b52936ce9d
11 changed files with 555 additions and 2 deletions
75
.forgejo/workflows/build-images.yml
Normal file
75
.forgejo/workflows/build-images.yml
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
name: build-images
|
||||
# Build + push container images for ProofedMesh services on every push to
|
||||
# main. PRs run the build to catch Dockerfile breakage but do not push.
|
||||
#
|
||||
# The repository is sha256 — same gotchas as validate.yml apply: no
|
||||
# actions/checkout, clone manually from a trixie/alpine container with
|
||||
# git ≥ 2.42.
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
signing-service:
|
||||
name: signing-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/signing-service
|
||||
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
|
||||
git --version
|
||||
docker --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 build \
|
||||
-f services/signing-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'
|
||||
run: |
|
||||
set -eu
|
||||
echo "${{ secrets.HARBOR_PASSWORD }}" | \
|
||||
docker login "${REGISTRY}" --username "${{ secrets.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 }}"
|
||||
|
|
@ -75,8 +75,14 @@ services/
|
|||
per-diem-calculator/ # deterministic calculator (Go)
|
||||
admin-api/ # admin surface for per-diem rate management (Go)
|
||||
attest-reconciler/ # periodic worker: re-signs per_diem_rates rows without rate_change_attestation_ref (Go)
|
||||
signing-service/ # DSSE signing service (Go, ADR-0005) + Dockerfile
|
||||
transparency-log/ # Trillian-backed transparency log (Go, ADR-0005)
|
||||
deploy/
|
||||
trillian/ # Trillian MySQL + log server/signer (namespace agent-trust)
|
||||
signing-service/ # signing-service Deployment/Service/NetworkPolicy + Secret convention
|
||||
.forgejo/workflows/
|
||||
validate.yml # JSON-Schema (ajv) + Rego (opa fmt + opa test) + Go (vet + test)
|
||||
validate.yml # JSON-Schema (ajv) + Rego (opa fmt + opa test) + Go (vet + test)
|
||||
build-images.yml # Container-Images für ProofedMesh-Services → harbor.basicstack.de
|
||||
```
|
||||
|
||||
Weitere Verzeichnisse (weitere Services, Deployment-Manifeste, Senken-/Datenquellen-Adapter) folgen im Zuge der Architekturentwicklung.
|
||||
|
|
|
|||
10
deploy/signing-service/00-serviceaccount.yaml
Normal file
10
deploy/signing-service/00-serviceaccount.yaml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: signing-service
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: signing-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: trust-core
|
||||
automountServiceAccountToken: true
|
||||
30
deploy/signing-service/10-secret-dev.example.yaml
Normal file
30
deploy/signing-service/10-secret-dev.example.yaml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# signing-service dev secret — EXAMPLE ONLY.
|
||||
#
|
||||
# This file is a template. The real cluster secret MUST be created either as
|
||||
# a SealedSecret (preferred, see deploy/signing-service/README.md) or applied
|
||||
# imperatively with values that never land in the git repo.
|
||||
#
|
||||
# Secret shape (dev mode):
|
||||
# SIGNING_BEARERS — comma-separated bearer=subject pairs for the static
|
||||
# token validator (-bearers flag)
|
||||
# KEY_<SLUG> — hex-encoded 64-byte Ed25519 private key per key_ref
|
||||
# ("/" "-" "." → "_", upper-case; see service README)
|
||||
#
|
||||
# Generate a dev key:
|
||||
# go run ./cmd/signing-service -gen-dev-key
|
||||
#
|
||||
# For key_ref "trust-api/rate-change-signer-2026-q4" the env name is
|
||||
# KEY_TRUST_API_RATE_CHANGE_SIGNER_2026_Q4.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: signing-service-dev
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: signing-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
proofedmesh.basicstack.de/mode: dev
|
||||
type: Opaque
|
||||
stringData:
|
||||
SIGNING_BEARERS: "dev-token=agent-admin-api"
|
||||
KEY_TRUST_API_RATE_CHANGE_SIGNER_2026_Q4: "REPLACE_WITH_128_HEX_CHARS_FROM_gen-dev-key"
|
||||
97
deploy/signing-service/20-deployment.yaml
Normal file
97
deploy/signing-service/20-deployment.yaml
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: signing-service
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: signing-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: signing-service
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: signing-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: trust-core
|
||||
spec:
|
||||
serviceAccountName: signing-service
|
||||
automountServiceAccountToken: true
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 65532
|
||||
runAsGroup: 65532
|
||||
fsGroup: 65532
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: signing-service
|
||||
image: harbor.basicstack.de/basicstack/signing-service:dev
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- "-listen=:8081"
|
||||
- "-backend=memory"
|
||||
- "-keys=trust-api/rate-change-signer-2026-q4"
|
||||
- "-keys-from-env"
|
||||
- "-bearers=$(SIGNING_BEARERS)"
|
||||
env:
|
||||
# Pulled from the dev Secret below; see deploy/signing-service/README.md
|
||||
# for the full secret shape and the SealedSecret recipe.
|
||||
- name: SIGNING_BEARERS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: signing-service-dev
|
||||
key: SIGNING_BEARERS
|
||||
- name: KEY_TRUST_API_RATE_CHANGE_SIGNER_2026_Q4
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: signing-service-dev
|
||||
key: KEY_TRUST_API_RATE_CHANGE_SIGNER_2026_Q4
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8081
|
||||
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
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: signing-service
|
||||
18
deploy/signing-service/30-service.yaml
Normal file
18
deploy/signing-service/30-service.yaml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: signing-service
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: signing-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
app.kubernetes.io/component: trust-core
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: signing-service
|
||||
ports:
|
||||
- name: http
|
||||
port: 8081
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
51
deploy/signing-service/40-networkpolicy.yaml
Normal file
51
deploy/signing-service/40-networkpolicy.yaml
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# NetworkPolicy for signing-service.
|
||||
#
|
||||
# Cluster-wide NetworkPolicy enforcement is already active on BasicStack
|
||||
# (see cluster memory on NetworkPolicy activation). Any pod in agent-trust
|
||||
# without a policy is isolated by default; this file is the explicit
|
||||
# allow-list for signing-service.
|
||||
#
|
||||
# Ingress: anyone in agent-trust (admin-api, attest-reconciler, transparency-log)
|
||||
# plus future callers reaching through an in-cluster ingress controller.
|
||||
# Egress: DNS (kube-system/coredns), and later the OpenBao Transit namespace
|
||||
# and Pocket ID (JWKS) when those backends are wired. For now the
|
||||
# service only needs DNS egress — the memory backend and static
|
||||
# validator are self-contained.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: signing-service
|
||||
namespace: agent-trust
|
||||
labels:
|
||||
app.kubernetes.io/name: signing-service
|
||||
app.kubernetes.io/part-of: proofedmesh
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: signing-service
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: agent-trust
|
||||
ports:
|
||||
- port: 8081
|
||||
protocol: TCP
|
||||
# Future: add admin-api / app namespaces here once they are deployed and
|
||||
# labelled. Keep the list conservative — add on demand, not speculatively.
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- port: 53
|
||||
protocol: UDP
|
||||
- port: 53
|
||||
protocol: TCP
|
||||
165
deploy/signing-service/README.md
Normal file
165
deploy/signing-service/README.md
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
# signing-service — deployment
|
||||
|
||||
Kubernetes manifests for the ADR-0005 signing-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/signing-service/
|
||||
├── 00-serviceaccount.yaml — ServiceAccount with projected SA token
|
||||
├── 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 :8081
|
||||
├── 40-networkpolicy.yaml — ingress from agent-trust, DNS egress
|
||||
└── README.md — this file
|
||||
```
|
||||
|
||||
## Apply order
|
||||
|
||||
```bash
|
||||
kubectl apply -f deploy/trillian/00-namespace.yaml # if not yet applied
|
||||
kubectl apply -f deploy/signing-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/signing-service/20-deployment.yaml
|
||||
kubectl apply -f deploy/signing-service/30-service.yaml
|
||||
kubectl apply -f deploy/signing-service/40-networkpolicy.yaml
|
||||
```
|
||||
|
||||
Reach the service in-cluster at:
|
||||
|
||||
```
|
||||
http://signing-service.agent-trust.svc.cluster.local:8081
|
||||
```
|
||||
|
||||
Health check (also used by Deployment probes):
|
||||
|
||||
```
|
||||
GET /healthz → 200 {"status":"ok"}
|
||||
```
|
||||
|
||||
Sign endpoint (per ADR-0005):
|
||||
|
||||
```
|
||||
POST /api/v1/sign/dsse
|
||||
Authorization: Bearer <token from SIGNING_BEARERS>
|
||||
```
|
||||
|
||||
## K8s secret convention
|
||||
|
||||
The signing-service never pulls secrets from the pod filesystem or a config
|
||||
map. Every secret value is an **environment variable sourced from one
|
||||
Kubernetes Secret** named `signing-service-<mode>`. This keeps the SealedSecret
|
||||
rotation path uniform and lets the Deployment stay identical across modes.
|
||||
|
||||
### Dev mode (`-backend=memory`)
|
||||
|
||||
Secret: `signing-service-dev`
|
||||
|
||||
| Env key | Purpose |
|
||||
| ------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| `SIGNING_BEARERS` | `-bearers` value, comma-separated `bearer=subject` pairs |
|
||||
| `KEY_<SLUG>` | one entry per `key_ref`; value is a hex-encoded 64-byte Ed25519 private key |
|
||||
|
||||
Slug rule: upper-case the `key_ref`, then replace `/`, `-`, `.` with `_`. For
|
||||
`trust-api/rate-change-signer-2026-q4` the env name is
|
||||
`KEY_TRUST_API_RATE_CHANGE_SIGNER_2026_Q4`.
|
||||
|
||||
Mint a dev key:
|
||||
|
||||
```bash
|
||||
cd services/signing-service
|
||||
go run ./cmd/signing-service -gen-dev-key
|
||||
# → prints 128 hex chars (64 bytes raw Ed25519 private key)
|
||||
```
|
||||
|
||||
Create the Secret for the cluster (never commit the raw value):
|
||||
|
||||
```bash
|
||||
BEARER_TOKEN="$(openssl rand -hex 32)"
|
||||
HEX_KEY="$(cd services/signing-service && go run ./cmd/signing-service -gen-dev-key)"
|
||||
|
||||
kubectl create secret generic signing-service-dev \
|
||||
-n agent-trust \
|
||||
--from-literal="SIGNING_BEARERS=${BEARER_TOKEN}=agent-admin-api" \
|
||||
--from-literal="KEY_TRUST_API_RATE_CHANGE_SIGNER_2026_Q4=${HEX_KEY}"
|
||||
```
|
||||
|
||||
For the GitOps path, convert the above to a SealedSecret with `kubeseal`
|
||||
and commit the sealed form under the same filename as the example:
|
||||
|
||||
```bash
|
||||
kubectl create secret generic signing-service-dev -n agent-trust \
|
||||
--from-literal="SIGNING_BEARERS=${BEARER_TOKEN}=agent-admin-api" \
|
||||
--from-literal="KEY_TRUST_API_RATE_CHANGE_SIGNER_2026_Q4=${HEX_KEY}" \
|
||||
--dry-run=client -o yaml | \
|
||||
kubeseal --controller-namespace=sealed-secrets --format=yaml \
|
||||
> deploy/signing-service/10-secret-dev.sealed.yaml
|
||||
```
|
||||
|
||||
The `10-secret-dev.example.yaml` file is kept as the shape reference; the
|
||||
real sealed form is `10-secret-dev.sealed.yaml` (not yet committed — ships
|
||||
when the first real deployment lands).
|
||||
|
||||
### Prod mode (future, out of scope for DEV-605)
|
||||
|
||||
Secret: `signing-service-prod`
|
||||
|
||||
Planned env keys — kept here so future subtasks don't invent a new shape:
|
||||
|
||||
| Env key | Purpose | Subtask |
|
||||
| ---------------------------- | ----------------------------------------------------------------- | ------------------------ |
|
||||
| `OPENBAO_ADDR` | OpenBao server URL (`https://openbao.agent-trust.svc:8200`) | OpenBao Transit signer |
|
||||
| `OPENBAO_ROLE` | AppRole / Kubernetes auth role name for the signing-service pod | OpenBao Transit signer |
|
||||
| `OPENBAO_TOKEN` (bootstrap) | optional bootstrap token; prefer SA-token-based login once wired | OpenBao Transit signer |
|
||||
| `POCKET_ID_JWKS_URL` | `https://auth.basicstack.de/.well-known/jwks.json` | Pocket-ID JWKS validator |
|
||||
| `POCKET_ID_ISSUER` | `https://auth.basicstack.de` | Pocket-ID JWKS validator |
|
||||
| `POCKET_ID_AUDIENCE` | expected `aud` claim (e.g. `trust-api`) | Pocket-ID JWKS validator |
|
||||
| `OPA_URL` | OPA sidecar / service URL (`http://opa.agent-trust.svc:8181`) | OPA policy evaluator |
|
||||
|
||||
The matching command-line flags (`-backend=openbao`, `-policy=opa`,
|
||||
`-validator=pocket-id`) land with the respective subtasks. Until then the
|
||||
Deployment stays on `-backend=memory` with the dev Secret above; switching
|
||||
is a Deployment patch and a Secret swap — no manifest reshape.
|
||||
|
||||
## Verifying the deployment
|
||||
|
||||
```bash
|
||||
# Pod comes up, probe says OK:
|
||||
kubectl -n agent-trust rollout status deploy/signing-service
|
||||
|
||||
# In-cluster reachability from a throwaway pod:
|
||||
kubectl -n agent-trust run curl --image=curlimages/curl:8 -it --rm --restart=Never -- \
|
||||
curl -sS http://signing-service:8081/healthz
|
||||
```
|
||||
|
||||
End-to-end sign check (adjust `BEARER_TOKEN` to the one you stored in the
|
||||
Secret):
|
||||
|
||||
```bash
|
||||
kubectl -n agent-trust run curl --image=curlimages/curl:8 -it --rm --restart=Never -- \
|
||||
curl -sS -X POST http://signing-service:8081/api/v1/sign/dsse \
|
||||
-H "Authorization: Bearer ${BEARER_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"key_ref":"trust-api/rate-change-signer-2026-q4",
|
||||
"statement":{"_type":"https://in-toto.io/Statement/v1",
|
||||
"subject":[{"name":"per_diem_rate/PDR-1","digest":{"sha256":"aaaa"}}],
|
||||
"predicateType":"https://basicstack.de/attestations/rate_change/v1",
|
||||
"predicate":{"change_kind":"create","rate_id":"PDR-1"}}}'
|
||||
```
|
||||
|
||||
## Image
|
||||
|
||||
Images are built by `.forgejo/workflows/build-images.yml` on every push to
|
||||
`main` and pushed to:
|
||||
|
||||
```
|
||||
harbor.basicstack.de/basicstack/signing-service:<git-sha>
|
||||
harbor.basicstack.de/basicstack/signing-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.
|
||||
4
services/signing-service/.dockerignore
Normal file
4
services/signing-service/.dockerignore
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
**/*_test.go.bak
|
||||
**/.DS_Store
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
41
services/signing-service/Dockerfile
Normal file
41
services/signing-service/Dockerfile
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
# ---------- build ----------
|
||||
FROM golang:1.23-alpine3.21 AS build
|
||||
|
||||
ENV CGO_ENABLED=0 \
|
||||
GOOS=linux \
|
||||
GOFLAGS="-trimpath"
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
# Only the signing-service module is needed. Copy the module files first so
|
||||
# the dependency download layer is cacheable independently of source edits.
|
||||
COPY services/signing-service/go.mod ./
|
||||
# go.sum will be copied in when the module gains external deps; today the
|
||||
# module is stdlib-only.
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go mod download || true
|
||||
|
||||
COPY services/signing-service/ ./
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
go vet ./... && \
|
||||
go test ./... && \
|
||||
go build -ldflags="-s -w" -o /out/signing-service ./cmd/signing-service
|
||||
|
||||
# ---------- runtime ----------
|
||||
# Distroless static+nonroot: no shell, no package manager, UID/GID 65532.
|
||||
# gcr.io/distroless/static-debian12:nonroot pins to the latest nonroot
|
||||
# variant; CI tags images by git sha so this is reproducible at the point
|
||||
# of build.
|
||||
FROM gcr.io/distroless/static-debian12:nonroot
|
||||
|
||||
COPY --from=build /out/signing-service /signing-service
|
||||
|
||||
USER 65532:65532
|
||||
EXPOSE 8081
|
||||
ENTRYPOINT ["/signing-service"]
|
||||
CMD ["-listen=:8081", "-backend=memory"]
|
||||
|
|
@ -19,7 +19,9 @@ admin-api and other clients use as the single attestation reference.
|
|||
- **Pocket ID JWKS verifier**: stub. Dev uses the static `-bearers` table.
|
||||
Separate subtask.
|
||||
- **OPA policy decider**: stub. Dev uses AllowAll. Separate subtask.
|
||||
- **Deployment manifest**: separate subtask.
|
||||
- **Deployment manifest**: Kubernetes manifests under
|
||||
[`deploy/signing-service/`](../../deploy/signing-service/) + Dockerfile
|
||||
next to this README. See **Deployment** below.
|
||||
|
||||
## Layout
|
||||
|
||||
|
|
@ -111,3 +113,57 @@ Prod wiring in the OpenBao-Transit subtask: Signer backend becomes
|
|||
backend becomes `policy.OPA` with the Rego bundle under `policies/signing/`
|
||||
(not yet created), token validator becomes `token.PocketID` with JWKS from
|
||||
`https://auth.basicstack.de/.well-known/jwks.json`.
|
||||
|
||||
## Deployment
|
||||
|
||||
### Container image
|
||||
|
||||
Multi-stage build in `services/signing-service/Dockerfile`:
|
||||
|
||||
- build stage: `golang:1.23-alpine3.21`, runs `go vet` + `go test` before `go build`
|
||||
- runtime stage: `gcr.io/distroless/static-debian12:nonroot` (UID/GID 65532, no shell)
|
||||
- the final binary is static (`CGO_ENABLED=0`), stripped (`-ldflags="-s -w"`),
|
||||
reproducible (`-trimpath`)
|
||||
|
||||
Build locally from the repo root:
|
||||
|
||||
```bash
|
||||
docker build -f services/signing-service/Dockerfile \
|
||||
-t harbor.basicstack.de/basicstack/signing-service:dev .
|
||||
```
|
||||
|
||||
CI (`.forgejo/workflows/build-images.yml`) builds on every push + PR to
|
||||
`main` and pushes `:<git-sha>` + `:main` to Harbor on main pushes only.
|
||||
|
||||
### Kubernetes
|
||||
|
||||
Manifests live under
|
||||
[`deploy/signing-service/`](../../deploy/signing-service/) and target the
|
||||
`agent-trust` namespace. See
|
||||
[`deploy/signing-service/README.md`](../../deploy/signing-service/README.md)
|
||||
for the full secret convention (dev + prod layout), the SealedSecret
|
||||
recipe, and the apply order.
|
||||
|
||||
Minimal example (dev mode, in-memory key, static bearer):
|
||||
|
||||
```bash
|
||||
BEARER_TOKEN="$(openssl rand -hex 32)"
|
||||
HEX_KEY="$(go run ./cmd/signing-service -gen-dev-key)"
|
||||
|
||||
kubectl apply -f ../../deploy/trillian/00-namespace.yaml
|
||||
kubectl apply -f ../../deploy/signing-service/00-serviceaccount.yaml
|
||||
|
||||
kubectl create secret generic signing-service-dev -n agent-trust \
|
||||
--from-literal="SIGNING_BEARERS=${BEARER_TOKEN}=agent-admin-api" \
|
||||
--from-literal="KEY_TRUST_API_RATE_CHANGE_SIGNER_2026_Q4=${HEX_KEY}"
|
||||
|
||||
kubectl apply -f ../../deploy/signing-service/20-deployment.yaml
|
||||
kubectl apply -f ../../deploy/signing-service/30-service.yaml
|
||||
kubectl apply -f ../../deploy/signing-service/40-networkpolicy.yaml
|
||||
|
||||
kubectl -n agent-trust rollout status deploy/signing-service
|
||||
```
|
||||
|
||||
In-cluster the service is reachable at
|
||||
`http://signing-service.agent-trust.svc.cluster.local:8081` (health check
|
||||
`/healthz`).
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue