diff --git a/wildduck/cert-reconciler.yaml b/wildduck/cert-reconciler.yaml new file mode 100644 index 0000000..f2bf677 --- /dev/null +++ b/wildduck/cert-reconciler.yaml @@ -0,0 +1,89 @@ +--- +# WildDuck and ZoneMTA read the mounted wildduck-tls cert into an in-memory +# TLS cache at startup and never re-read it, so after cert-manager renews the +# secret, long-lived pods keep serving the boot-time cert until restarted +# (IMAP/submission served an expired cert on 2026-07-21). Haraka watches the +# cert files and reloads on its own, but is stamped too for a uniform +# guarantee. +# +# This CronJob stamps a hash of the current cert onto the pod templates: +# unchanged hash is a no-op (k8s only rolls when the template changes), a +# changed cert triggers a rollout. Idempotent and self-healing. +apiVersion: v1 +kind: ServiceAccount +metadata: + name: wildduck-cert-reconciler +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: wildduck-cert-reconciler +rules: + # Enough to patch the pod template annotation on the mail-server deployments. + - apiGroups: ["apps"] + resources: ["deployments"] + verbs: ["get", "patch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: wildduck-cert-reconciler +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: wildduck-cert-reconciler +subjects: + - kind: ServiceAccount + name: wildduck-cert-reconciler + namespace: wildduck +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: wildduck-cert-reconciler +spec: + # cert-manager renews ~30 days before expiry; daily is plenty to pick up. + schedule: "17 3 * * *" + successfulJobsHistoryLimit: 1 + failedJobsHistoryLimit: 3 + concurrencyPolicy: Forbid + jobTemplate: + spec: + backoffLimit: 2 + ttlSecondsAfterFinished: 86400 + template: + spec: + serviceAccountName: wildduck-cert-reconciler + restartPolicy: OnFailure + containers: + - name: reconcile + image: mirror.gcr.io/library/alpine:3.19 + command: + - sh + - -euc + - | + apk add --no-cache curl >/dev/null + CERT_HASH=$(sha256sum /cert/tls.crt | cut -c1-16) + echo "Cert content hash: $CERT_HASH" + APISERVER=https://kubernetes.default.svc + SA=/var/run/secrets/kubernetes.io/serviceaccount + KTOKEN=$(cat "$SA/token") + for DEP in wildduck zonemta haraka; do + curl -fsS --cacert "$SA/ca.crt" \ + -H "Authorization: Bearer $KTOKEN" \ + -H "Content-Type: application/strategic-merge-patch+json" \ + -X PATCH \ + "$APISERVER/apis/apps/v1/namespaces/wildduck/deployments/$DEP" \ + --data '{"spec":{"template":{"metadata":{"annotations":{"k-space.ee/wildduck-tls-hash":"'"$CERT_HASH"'"}}}}}' \ + >/dev/null + echo " stamped $DEP (rolls only if hash changed)" + done + echo "done" + volumeMounts: + - name: cert + mountPath: /cert + readOnly: true + volumes: + - name: cert + secret: + secretName: wildduck-tls diff --git a/wildduck/kustomization.yaml b/wildduck/kustomization.yaml index 26fc066..3ef22bc 100644 --- a/wildduck/kustomization.yaml +++ b/wildduck/kustomization.yaml @@ -4,6 +4,7 @@ kind: Kustomization namespace: wildduck resources: +- ./cert-reconciler.yaml - ./certificate.yaml - ./clamav.yaml - ./dns.yaml