# Default values for certctl Helm chart # This is a YAML-formatted file. # Declare variables to be passed into your templates. # Namespace override (optional) namespace: "" # Global configuration commonLabels: {} imagePullSecrets: [] nameOverride: "" fullnameOverride: "" # ============================================================================== # Certctl Server Configuration # ============================================================================== server: # Number of replicas (for HA deployments) replicas: 1 # Image configuration image: repository: ghcr.io/certctl-io/certctl tag: "" # defaults to Chart.appVersion pullPolicy: IfNotPresent # Server port port: 8443 # Resource requests and limits resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi # Pod security context securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: - ALL # Liveness and readiness probes (HTTPS-only as of v2.2). # # The two paths exposed for probes are `/health` and `/ready` — # registered in internal/api/router/router.go:76-85 and bypassing the # auth middleware via the no-auth list at cmd/server/main.go:920. # Both serve the same JSON shape today (`{"status":"healthy"}` / # `{"status":"ready"}`) but exist as separate routes so liveness and # readiness can diverge in the future without renaming. livenessProbe: httpGet: path: /health port: https scheme: HTTPS initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 # U-2 (P1, cat-u-healthcheck_protocol_mismatch — adjacent fix): pre-U-2 # the readiness probe pointed at `/readyz`, the conventional kube-flavor # name. The certctl server doesn't register `/readyz` (only `/health` # and `/ready`) — see cmd/server/main.go:920 and # internal/api/router/router.go:81. K8s readiness probes therefore # received a 404 (or, with auth enabled, a 401 from the api-key middleware # because `/readyz` was NOT in the no-auth bypass set), pods stayed # `NotReady` indefinitely, and Helm rollouts stalled. Post-U-2 the path # matches a registered route. readinessProbe: httpGet: path: /ready port: https scheme: HTTPS initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 2 # TLS configuration — REQUIRED. HTTPS is the only supported mode (v2.2+). # Operator must configure EXACTLY ONE of: # (a) server.tls.existingSecret: # pre-existing kubernetes.io/tls Secret # (b) server.tls.certManager.enabled: true # provision a cert-manager Certificate CR # Refusing to set either makes `helm template` fail with a diagnostic pointing at docs/tls.md. tls: # Name of a pre-existing Secret (type kubernetes.io/tls) holding tls.crt + tls.key (+ optional ca.crt). # Leave empty to fall through to the cert-manager path. existingSecret: "" # Mount path for the TLS Secret inside the server + agent containers. mountPath: /etc/certctl/tls # cert-manager auto-provisioning. Opt-in (off by default per milestone §3.4). certManager: enabled: false # Secret name the cert-manager Certificate CR writes into. Agents and the server # both read from this Secret. If empty, defaults to "-tls". secretName: "" # Cert-manager issuer reference. issuerRef: name: "" # e.g. "letsencrypt-prod" or "internal-ca" kind: ClusterIssuer # ClusterIssuer or Issuer group: cert-manager.io # Subject fields on the issued cert. commonName: "certctl-server" dnsNames: - certctl-server - localhost # Certificate lifetime + renewal window. duration: 2160h # 90 days renewBefore: 360h # 15 days # Service type (ClusterIP, LoadBalancer, NodePort) service: type: ClusterIP port: 8443 annotations: {} # Authentication configuration. # Valid types: "api-key" (production) or "none" (demo only — disables # authentication on the API and logs a loud Warn at server startup). # For JWT/OIDC, run an authenticating gateway in front of certctl # (oauth2-proxy / Envoy ext_authz / Traefik ForwardAuth / Pomerium) # and set type=none here so the gateway terminates federated identity. # See docs/architecture.md "Authenticating-gateway pattern". # # G-1 (P1): pre-G-1 the chart accepted server.auth.type=jwt and the # certctl-server container silently routed every request through the # api-key bearer middleware — silent auth downgrade. Post-G-1 the # chart's `certctl.validateAuthType` template helper rejects any value # outside {api-key, none} at template time. See # docs/upgrade-to-v2-jwt-removal.md if you previously set type=jwt. auth: type: api-key apiKey: "" # REQUIRED when type=api-key (set via --set or values override). # Logging configuration logging: level: info # debug, info, warn, error format: json # json or text # SMTP configuration for email notifications (optional) smtp: enabled: false host: "" port: 587 username: "" password: "" fromAddress: "" useTLS: true # Certificate digest digest (periodic email summary) digest: enabled: false interval: "24h" recipients: [] # Example: # - admin@example.com # - ops@example.com # Enrollment over Secure Transport (EST) configuration est: enabled: false issuerID: "iss-local" profileID: "" # Rate limiting configuration rateLimiting: rps: 100 # Requests per second burst: 200 # Burst capacity # Network scanning configuration networkScan: enabled: false interval: "6h" # Certificate key generation mode keygen: mode: agent # Options: agent (production), server (demo with warning) # CORS configuration cors: origins: "" # Comma-separated list, empty means deny all cross-origin requests # Issuer connectors configuration issuer: local: enabled: true # For sub-CA mode, provide these paths: # caCertPath: /path/to/ca.crt # caKeyPath: /path/to/ca.key acme: enabled: false directoryURL: "" email: "" challengeType: "http-01" # Options: http-01, dns-01, dns-persist-01 # DNS configuration (for dns-01 or dns-persist-01) # dnsPresentScript: /path/to/dns-present.sh # dnsCleanupScript: /path/to/dns-cleanup.sh # dnsPropagationWait: "30s" # dnsPersistIssuerDomain: "validation.example.com" # EAB configuration (for ZeroSSL, Google Trust Services, etc.) # eabKid: "" # eabHmac: "" stepca: enabled: false # rootCAPath: /path/to/root_ca.crt # intermediateCAPath: /path/to/intermediate_ca.crt # provisionerName: "" # provisionerPassword: "" openssl: enabled: false # signScript: /path/to/sign.sh # revokeScript: /path/to/revoke.sh # crlScript: /path/to/crl.sh # timeoutSeconds: 30 # Notifier connectors configuration notifiers: slack: enabled: false # webhookUrl: "" # channel: "" # username: "" # iconEmoji: "" teams: enabled: false # webhookUrl: "" pagerduty: enabled: false # routingKey: "" # severity: warning opsgenie: enabled: false # apiKey: "" # priority: P3 # Additional environment variables # Will be passed as-is to the server container env: {} # Example: # CERTCTL_SCHEDULER_RENEWAL_CHECK_INTERVAL: "1h" # CERTCTL_DATABASE_MAX_CONNS: "25" # Additional volume mounts for custom configurations # volumeMounts: [] # - name: ca-cert # mountPath: /etc/ssl/certs/ca.crt # subPath: ca.crt # Additional volumes # volumes: [] # - name: ca-cert # secret: # secretName: ca-cert # ============================================================================== # PostgreSQL Configuration # ============================================================================== postgresql: # Enable/disable PostgreSQL (set to false if using external database) enabled: true # Image configuration image: repository: postgres tag: "16-alpine" pullPolicy: IfNotPresent # Authentication auth: database: certctl username: certctl # REQUIRED — set via `--set postgresql.auth.password=` or values override. # # WARNING (U-1): rotating this value after first deploy does NOT change the # database password. The `postgres:16-alpine` image runs `initdb` only when # /var/lib/postgresql/data is empty, so POSTGRES_PASSWORD is written into # pg_authid exactly once — on the first boot of the StatefulSet's PVC. # Subsequent rollouts pick up the new env value in the postgres container # but the certctl-server container's CERTCTL_DATABASE_URL also picks up # the new value, while pg_authid still expects the old one — leading to # `pq: password authentication failed for user "certctl"` (SQLSTATE 28P01). # # The certctl-server emits guidance via internal/repository/postgres/db.go:: # wrapPingError when it sees SQLSTATE 28P01 at startup. To resolve in a # Helm deployment: # - Non-destructive (preferred for environments with data): # kubectl exec -it -postgres-0 -- \ # psql -U certctl -c "ALTER ROLE certctl PASSWORD '';" # then update the secret/values to match and let the certctl-server # pod restart against the matching credential. # - Destructive (DESTROYS DATA — only acceptable on dev/demo PVCs): # helm uninstall && \ # kubectl delete pvc -l app.kubernetes.io/name=certctl,app.kubernetes.io/component=postgres && \ # helm install ... # PVC re-creates empty, initdb seeds new password password: "" # ───────────────────────────────────────────────────────────────────── # Bundle B / Audit M-018 (PCI-DSS Req 4 / CWE-319): TLS to Postgres # ───────────────────────────────────────────────────────────────────── # postgresql.tls.mode is wired into the database-url sslmode parameter # (see templates/_helpers.tpl::certctl.databaseURL). # # Acceptable values (lib/pq): # disable — no TLS (default, preserves in-cluster pod-to-pod # traffic on the K8s pod network). # require — TLS required, no certificate verification. # verify-ca — TLS required + verify CA chain. # verify-full — TLS required + verify CA chain + verify hostname. # # PCI-DSS Req 4 v4.0 §2.2.5 requires verify-ca or verify-full when the # database carries sensitive data crossing untrusted networks (RDS, # Cloud SQL, cross-VPC, etc). The bundled Helm Postgres runs in the # same pod network as certctl-server; sslmode=disable is acceptable # there only when the cluster CNI provides L2/L3 encryption (Cilium # WireGuard, Calico Wireguard, Tailscale operator, etc). # # When mode != disable AND tls.caSecretRef is set, the CA bundle is # mounted at /etc/postgresql-ca/ca.crt and the server's PGSSLROOTCERT # env points there. caSecretRef must reference an existing Secret with # a "ca.crt" key. tls: mode: disable # caSecretRef: "" # Secret with ca.crt key (required for verify-ca/verify-full) # Storage configuration storage: size: 10Gi storageClass: "" # Uses default StorageClass if empty # deleteOnTermination: false # Keep data on Helm uninstall # Resource requests and limits resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi # Pod security context securityContext: runAsNonRoot: true runAsUser: 999 runAsGroup: 999 fsGroup: 999 # Liveness and readiness probes livenessProbe: exec: command: - /bin/sh - -c - pg_isready -U certctl -d certctl initialDelaySeconds: 10 periodSeconds: 10 timeoutSeconds: 5 failureThreshold: 3 readinessProbe: exec: command: - /bin/sh - -c - pg_isready -U certctl -d certctl initialDelaySeconds: 5 periodSeconds: 5 timeoutSeconds: 3 failureThreshold: 2 # Service configuration service: type: ClusterIP port: 5432 # PostgreSQL-specific settings postgresqlConfig: {} # Example: # max_connections: "200" # shared_buffers: "256MB" # ============================================================================== # Certctl Agent Configuration # ============================================================================== agent: # Enable/disable agent deployment enabled: true # Deployment strategy: DaemonSet (recommended) or Deployment kind: DaemonSet # Options: DaemonSet, Deployment # Image configuration image: repository: ghcr.io/certctl-io/certctl-agent tag: "" # defaults to Chart.appVersion pullPolicy: IfNotPresent # Number of replicas (for Deployment kind; ignored for DaemonSet) replicas: 1 # Resource requests and limits resources: requests: cpu: 50m memory: 64Mi limits: cpu: 200m memory: 256Mi # Pod security context securityContext: runAsNonRoot: true runAsUser: 1000 runAsGroup: 1000 fsGroup: 1000 readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: - ALL # Agent name (can be overridden per pod via StatefulSet ordinals) name: "" # If empty, uses release name # Key storage directory keyDir: /var/lib/certctl/keys # Certificate discovery directories (comma-separated) discoveryDirs: "" # Example: "/etc/ssl/certs,/etc/pki/tls" # Node selector for agent pods (for DaemonSet) nodeSelector: {} # Example: # node-role.kubernetes.io/worker: "true" # Tolerations for agent pods tolerations: [] # Example: # - key: node-role # operator: Equal # value: worker # effect: NoSchedule # Affinity rules affinity: {} # Additional environment variables env: {} # ============================================================================== # Ingress Configuration # ============================================================================== ingress: enabled: false className: "" annotations: {} # kubernetes.io/ingress.class: nginx # Optional cert-manager integration for the public-facing Ingress cert. # This is completely independent of server.tls.* — the Ingress terminates # an *additional* TLS hop between the internet and the in-cluster Service. # Leave disabled unless an Ingress is exposing certctl to the outside world. certManager: enabled: false issuerRef: name: "" # e.g. "letsencrypt-prod" kind: ClusterIssuer # ClusterIssuer or Issuer hosts: - host: certctl.local paths: - path: / pathType: Prefix tls: [] # - secretName: certctl-tls # hosts: # - certctl.local # ============================================================================== # Service Account Configuration # ============================================================================== serviceAccount: create: true annotations: {} name: "" # defaults to release name if empty # ============================================================================== # RBAC Configuration # ============================================================================== rbac: create: true # ============================================================================== # Kubernetes Secrets Target Connector # ============================================================================== kubernetesSecrets: # Enable RBAC rules for managing TLS Secrets enabled: false # ============================================================================== # Pod Disruption Budget (for HA deployments) # ============================================================================== podDisruptionBudget: enabled: false minAvailable: 1 # maxUnavailable: 1 # ============================================================================== # Monitoring Configuration # ============================================================================== monitoring: enabled: false # Prometheus ServiceMonitor serviceMonitor: enabled: false interval: 30s scrapeTimeout: 10s # labels: {} # selector: {} # ============================================================================== # Advanced Configuration # ============================================================================== # Node affinity for server pods nodeAffinity: {} # Pod affinity for server pods podAffinity: {} # Pod anti-affinity for server pods (for HA) podAntiAffinity: {} # Example: # podAntiAffinity: # preferredDuringSchedulingIgnoredDuringExecution: # - weight: 100 # podAffinityTerm: # labelSelector: # matchExpressions: # - key: app.kubernetes.io/name # operator: In # values: # - certctl # topologyKey: kubernetes.io/hostname # Custom labels for all resources customLabels: {} # Custom annotations for all resources customAnnotations: {}