apiVersion: apps/v1 kind: Deployment metadata: name: worker namespace: honeydue labels: app.kubernetes.io/name: worker app.kubernetes.io/part-of: honeydue spec: # Asynq's Scheduler is a singleton — running >1 replica fires every cron # task once per replica (duplicate daily digests, onboarding emails, etc.). # Keep at 1 until asynq.PeriodicTaskManager with Redis leader election is # wired in cmd/worker/main.go. replicas: 1 strategy: type: RollingUpdate rollingUpdate: maxUnavailable: 0 maxSurge: 1 selector: matchLabels: app.kubernetes.io/name: worker template: metadata: labels: app.kubernetes.io/name: worker app.kubernetes.io/part-of: honeydue spec: serviceAccountName: worker # Explicit pod-level opt-out (audit F11) — defense-in-depth on top of # the ServiceAccount-level setting in rbac.yaml. automountServiceAccountToken: false imagePullSecrets: - name: gitea-credentials securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault containers: - name: worker image: IMAGE_PLACEHOLDER # Replaced by 03-deploy.sh imagePullPolicy: IfNotPresent # audit CODE-L4 — explicit; images are SHA/digest-pinned ports: # health + Prometheus /metrics (in-cluster only; scraped by vmagent) - name: metrics containerPort: 6060 protocol: TCP securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] envFrom: - configMapRef: name: honeydue-config # Audit CODE-F8: secrets are NOT injected as environment variables. # Env vars are readable for the life of the pod via /proc//environ # and leak into crash dumps / child processes. honeydue-secrets is # mounted read-only at /etc/honeydue/secrets (mode 0400) and the Go # config layer (config.loadFileSecrets) reads each key from its file. # Non-secret config still arrives via the configMapRef above. volumeMounts: - name: app-secrets mountPath: /etc/honeydue/secrets readOnly: true - name: apns-key mountPath: /secrets/apns readOnly: true - name: tmp mountPath: /tmp resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi livenessProbe: exec: command: ["pgrep", "-f", "/app/worker"] initialDelaySeconds: 15 periodSeconds: 30 timeoutSeconds: 5 volumes: # Audit CODE-F8: the whole honeydue-secrets Secret, projected as files. # defaultMode 0400 → readable only by the container's runAsUser (1000). - name: app-secrets secret: secretName: honeydue-secrets defaultMode: 0400 - name: apns-key secret: secretName: honeydue-apns-key items: - key: apns_auth_key.p8 path: apns_auth_key.p8 - name: tmp emptyDir: sizeLimit: 64Mi --- # Allow vmagent to scrape the worker's /metrics on :6060 (default-deny-all is in # force; the worker otherwise receives no ingress). Additive — see node-exporter. apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-ingress-to-worker-metrics namespace: honeydue spec: podSelector: matchLabels: app.kubernetes.io/name: worker policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app.kubernetes.io/name: vmagent ports: - port: 6060 protocol: TCP --- # vmagent's base egress policy only opens :8000/:8080 to the pod CIDR; this # additive policy opens :6060 for the worker scrape (leaves the base untouched). apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: allow-egress-from-vmagent-to-worker namespace: honeydue spec: podSelector: matchLabels: app.kubernetes.io/name: vmagent policyTypes: - Egress egress: - to: - ipBlock: cidr: 10.42.0.0/16 ports: - port: 6060 protocol: TCP