# Catalyst Blueprint values for bp-cert-manager-powerdns-webhook.
#
# Per docs/INVIOLABLE-PRINCIPLES.md #4 ("never hardcode") every
# operationally-meaningful value is configurable; cluster overlays in
# clusters/<sovereign>/ may override any of these without rebuilding the
# Blueprint OCI artifact.
#
# Default-values render produces a runnable webhook + the paired
# ClusterIssuer is gated behind `clusterIssuer.enabled` (default false)
# so that `helm template` with no values renders cleanly without
# requiring operator-supplied PowerDNS API host/secret. The bootstrap-kit
# slot in clusters/_template/bootstrap-kit/<NN>-bp-cert-manager-powerdns-
# webhook.yaml flips clusterIssuer.enabled=true and supplies the per-
# Sovereign PowerDNS host + apiKeySecretRef.

global:
  # When set, ALL image pulls in this chart route through this registry.
  # Used post-handover when the Sovereign's own Harbor takes over the
  # proxy_cache role from contabo's central Harbor. Empty = no rewrite
  # (image references use upstream defaults). When non-empty, the
  # deployment.yaml template prepends this value to webhook.image.repository:
  #   image: "<global.imageRegistry>/<webhook.image.repository>:<tag>"
  # Tracked under #560.
  imageRegistry: ""

catalystBlueprint:
  upstream:
    chart: cert-manager-webhook-pdns
    version: "3.2.5"
    repo: "https://github.com/zachomedia/cert-manager-webhook-pdns"
    images:
      webhook: "zachomedia/cert-manager-webhook-pdns"

# ─── Webhook protocol identity ───────────────────────────────────────────
# The groupName + solverName tuple is how cert-manager's DNS-01
# ClusterIssuer addresses this webhook. They MUST match the values
# configured in the ClusterIssuer template (clusterissuer.yaml).
webhook:
  # API group registered as /apis/acme.powerdns.openova.io/v1alpha1.
  # Distinct from the dynadot webhook's group so both can coexist on the
  # same cluster during the onboarding overlap window.
  groupName: acme.powerdns.openova.io
  # Solver name — MUST match the Name() return value of the upstream
  # webhook binary. The zachomedia/cert-manager-webhook-pdns binary's
  # Name() method returns "pdns" (hardcoded). cert-manager calls
  # POST /apis/<groupName>/v1alpha1/<solverName> — if solverName doesn't
  # match the registered resource name, cert-manager gets a 404 and
  # reports "the server could not find the requested resource".
  # This MUST stay "pdns" unless the upstream binary changes its Name().
  solverName: pdns

  # Replica count. Single-replica works for one Sovereign; bump to 2 in
  # an HA overlay so a node drain doesn't stall an in-flight challenge.
  replicas: 1

  # Upstream image — published to Docker Hub by the zachomedia repo. The
  # Catalyst Sovereign's pull path is documented in
  # docs/INVIOLABLE-PRINCIPLES.md #4a; operators that mirror to a
  # private registry override `repository` here.
  image:
    repository: zachomedia/cert-manager-webhook-pdns
    # Default tag tracks the chart's appVersion. Override per-Sovereign
    # to pin a specific upstream tag without rebuilding this blueprint.
    tag: ""
    pullPolicy: IfNotPresent

  # Listen port for the aggregated apiserver. The Service + APIService
  # resources both reference this. Upstream binary defaults to 8443
  # via --secure-port=8443 so we keep the same.
  securePort: 8443

  # Pod log level — debug surfaces every PowerDNS HTTP exchange.
  logLevel: info

  # Resource budget — small. The webhook does no caching, no informers;
  # each Present/CleanUp is one (or two) HTTPS calls to the in-cluster
  # PowerDNS service.
  resources:
    requests:
      cpu: 50m
      memory: 64Mi
    limits:
      memory: 256Mi

  # Pod-level securityContext — non-root + readOnlyRootFilesystem.
  # Upstream image runs as UID 100 by default; we keep the same to avoid
  # surprising operators familiar with the upstream chart.
  podSecurityContext:
    runAsNonRoot: true
    runAsUser: 100
    runAsGroup: 100
    fsGroup: 100
    seccompProfile:
      type: RuntimeDefault

  containerSecurityContext:
    runAsNonRoot: true
    runAsUser: 100
    runAsGroup: 100
    readOnlyRootFilesystem: true
    allowPrivilegeEscalation: false
    capabilities:
      drop: ["ALL"]

  # HTTP proxy passthrough — operators behind an outbound proxy override
  # these. Mirrors upstream chart behaviour. Empty = unset.
  http_proxy: ""
  https_proxy: ""
  no_proxy: ""

  # Pod scheduling.
  nodeSelector: {}
  tolerations: []
  affinity: {}
  podAnnotations: {}
  priorityClassName: ""
  imagePullSecrets: []

# ─── PowerDNS API endpoint ───────────────────────────────────────────────
# The webhook's per-ChallengeRequest config block (rendered into the
# ClusterIssuer below) carries the PowerDNS API URL + API-key secret
# reference. The webhook itself does NOT read these from env — it
# decodes them out of the ChallengeRequest config supplied by the issuer.
#
# Default target: contabo's central PowerDNS at https://pdns.openova.io —
# the authoritative server for omani.works (delegated from Dynadot to
# ns1/2/3.openova.io which front this PowerDNS). Every Sovereign at
# otechN.omani.works writes its ACME challenge TXT records HERE, NOT to
# its own local PowerDNS, because the Sovereign-side PowerDNS is only
# reachable through the public DNS chain AFTER pool-domain-manager has
# committed the NS delegation to contabo — and Let's Encrypt validates
# DNS-01 challenges against the public DNS chain, not the Sovereign's
# local view. Caught live on otech43–46.
#
# Per docs/INVIOLABLE-PRINCIPLES.md #4 every URL/zone is operator-
# overridable; cluster overlays MAY set powerdns.host to a different
# endpoint (e.g. https://pdns.<other-pool>.example for a non-omani.works
# pool). The default value lets a fresh Sovereign install render a
# working ClusterIssuer without any per-cluster override.
powerdns:
  # Base URL of the PowerDNS REST API. Defaults to the contabo central
  # PowerDNS public ingress so a fresh Sovereign overlay needs no host
  # override. Empty string causes the chart to skip ClusterIssuer
  # rendering entirely — see templates/clusterissuer.yaml's skip-render
  # guard. Cluster overlays may override to a different pool's endpoint.
  host: "https://pdns.openova.io"
  # PowerDNS server ID — virtually always "localhost" per the upstream
  # API contract. Operators with a multi-tenant PowerDNS deployment
  # override this per-Sovereign.
  serverID: "localhost"
  # Header name for API-key auth. PowerDNS canonicalises to X-API-Key.
  apiKeyHeaderName: "X-API-Key"
  # HTTP auth scheme — PowerDNS uses an X-API-Key header (no scheme).
  apiKeyScheme: ""
  # TTL of the inserted ACME challenge TXT records. PowerDNS chart
  # default is 60s; cert-manager polls every 10s by default.
  ttl: 60
  # PEM-encoded CA bundle used to verify the PowerDNS API TLS cert when
  # `host` points at an https endpoint with a private CA. Empty = use
  # the system store. Cluster overlays inject the per-Sovereign root CA
  # here.
  caBundle: ""
  # Additional headers (e.g. for an Envoy filter or per-tenant auth).
  headers: {}

  # API-key Secret reference. The upstream zachomedia/cert-manager-webhook-pdns
  # binary reads the key out of this Secret via the K8s clientset on
  # every Present/CleanUp call (NOT via Pod secretKeyRef). Two important
  # constraints from the upstream code:
  #
  #   1. The Secret is read from `ChallengeRequest.ResourceNamespace`,
  #      which for a ClusterIssuer is cert-manager's cluster-resource-
  #      namespace (typically `cert-manager`). The upstream binary IGNORES
  #      any `namespace:` field on the apiKeySecretRef in the ClusterIssuer
  #      config — it always reads from ResourceNamespace. So this Secret
  #      MUST live in the cert-manager namespace on every Sovereign.
  #   2. The Secret's `api-key` value MUST match the API key configured
  #      on the central PowerDNS (contabo) at `powerdns.host`. On every
  #      Sovereign, this is provisioned at control-plane boot time by
  #      cloud-init (mirroring the dynadot-api-credentials seeding
  #      pattern; see infra/hetzner/cloudinit-control-plane.tftpl).
  apiKeySecretRef:
    # Name of the K8s Secret holding the PowerDNS API key. The Secret
    # MUST live in the cert-manager namespace and contain the API key
    # of the PowerDNS at `powerdns.host` (default contabo PowerDNS).
    name: powerdns-api-credentials
    # Key inside the Secret. Default `api-key` matches the canonical
    # `powerdns-api-credentials` schema produced by bp-powerdns.
    key: api-key
    # Namespace field — KEPT for backward-compatibility but the upstream
    # webhook IGNORES it. The Secret is always read from the cert-manager
    # release namespace (= ChallengeRequest.ResourceNamespace for
    # ClusterIssuers). Leave blank.
    namespace: ""

# ─── ClusterIssuer (paired with the webhook) ─────────────────────────────
# Rendered into a single ClusterIssuer named letsencrypt-dns01-prod-
# powerdns. The Sovereign's per-cluster Certificate resources reference
# this issuer via cert-manager.io/cluster-issuer annotation or
# spec.issuerRef.
#
# Default `enabled: false` — the chart's default-values render in CI
# (blueprint-release.yaml smoke test) skips the ClusterIssuer because
# operators may want to flip the active issuer (acme staging vs prod)
# at install time without having the chart silently provision a prod
# issuer. Cluster overlays flip this to true. The companion `host`
# default already points at contabo PowerDNS so no host override is
# needed — only `enabled: true` + `email`.
clusterIssuer:
  enabled: false
  name: letsencrypt-dns01-prod-powerdns
  # ACME account email used for renewal notifications.
  email: ops@openova.io
  # Production Let's Encrypt directory. Per docs/INVIOLABLE-PRINCIPLES.md
  # #4 (never hardcode), this is operator-overridable from the cluster
  # overlay. The staging endpoint
  # (https://acme-staging-v02.api.letsencrypt.org/directory) is now
  # exposed as a SECOND ClusterIssuer (see clusterIssuer.staging below)
  # so production-vs-staging is no longer a one-or-the-other knob —
  # both issuers live side by side and chart consumers (e.g.
  # bp-catalyst-platform's wildcardCert.useStaging) flip the cert's
  # issuerRef to pick.
  acmeServer: https://acme-v02.api.letsencrypt.org/directory
  # Name of the Secret cert-manager uses to store the ACME account key.
  privateKeySecretRefName: letsencrypt-dns01-prod-powerdns-account-key
  # Helm hook ordering — the ClusterIssuer is a cert-manager.io/v1 CRD
  # that only exists after bp-cert-manager has installed its CRDs. Same
  # post-install hook pattern as bp-cert-manager's own ClusterIssuers.
  helmHookEnabled: true

  # ─── Staging (Let's Encrypt staging) ClusterIssuer (Fix #123) ────────
  # Renders a SECOND ClusterIssuer alongside the production one above,
  # pointing at LE's staging ACME directory. Same DNS-01 webhook config,
  # same PowerDNS endpoint, same API key — only the ACME account +
  # directory URL differ. Staging issues from a separate trust root
  # (Fake LE Intermediate X1) so browsers reject without an explicit
  # exception, but `curl -sk` and Playwright's `ignoreHTTPSErrors: true`
  # accept the cert. Generous rate limits (~30k certs/3h) make staging
  # the safe default for QA Sovereigns whose iteration cadence would
  # exhaust LE production's 5/168h ceiling on the first wipe-cycle hour.
  #
  # Default `enabled: true` so every Sovereign that has the production
  # issuer ALSO has staging available. Operators that want production-
  # only on a customer Sovereign flip `staging.enabled: false` in the
  # per-cluster overlay. The `name` is distinct from production so the
  # two ClusterIssuers never collide.
  #
  # Per docs/INVIOLABLE-PRINCIPLES.md #4 the ACME URL is overridable —
  # an operator wiring a private staging ACME (e.g. an internal Smallstep
  # CA) sets `staging.acmeServer` from the per-cluster overlay without
  # rebuilding this Blueprint.
  staging:
    enabled: true
    name: letsencrypt-dns01-staging-powerdns
    acmeServer: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRefName: letsencrypt-dns01-staging-powerdns-account-key

# ─── Service + APIService ────────────────────────────────────────────────
service:
  # ClusterIP — cert-manager calls the webhook via the kube-apiserver's
  # aggregated-apiserver path; no external exposure required.
  type: ClusterIP
  port: 443

# Skip the APIService registration if the operator has wired the webhook
# in some other way (e.g. an external OIDC-fronted apiserver). Default
# true — the chart owns the contract.
apiService:
  enabled: true
  groupPriorityMinimum: 1000
  versionPriority: 15

# ─── Serving certificate (issued by cert-manager itself) ─────────────────
servingCert:
  # Disable if the operator wires the cert from elsewhere (e.g. an
  # external Vault PKI). Default true.
  enabled: true
  duration: 8760h    # 1y
  renewBefore: 720h  # 30d
  caDuration: 43800h # 5y

# ─── ServiceAccount + RBAC ───────────────────────────────────────────────
serviceAccount:
  create: true
  name: ""
  annotations: {}

certManager:
  # The cert-manager controller's ServiceAccount — used in the
  # `domain-solver` ClusterRoleBinding so cert-manager can call this
  # webhook's CRD group. Defaults match the canonical bp-cert-manager
  # release.
  namespace: cert-manager
  serviceAccountName: cert-manager

rbac:
  # The webhook needs:
  #   - get on flowcontrol.apiserver.k8s.io and authentication.k8s.io
  #     (it's an aggregated apiserver, so the standard kube-apiserver
  #     proxying RBAC applies)
  #   - get on the PowerDNS API-key Secret, in whatever namespace the
  #     operator chose to place it
  create: true

# ─── ServiceMonitor ──────────────────────────────────────────────────────
# DEFAULT FALSE per docs/BLUEPRINT-AUTHORING.md §11.2. The
# kube-prometheus-stack CRDs ship with a separate Application Blueprint;
# enabling this on a fresh Sovereign that has not yet reconciled the
# observability tier creates a circular dependency. Operator opts in
# from the per-cluster overlay after observability lands.
serviceMonitor:
  enabled: false

# ─── NetworkPolicy ───────────────────────────────────────────────────────
# Egress to the PowerDNS API endpoint (TCP/8081 in-cluster, TCP/443 if
# operator routes externally) is the only outbound the webhook needs.
# Ingress is restricted to the kube-apiserver IP range.
networkPolicy:
  enabled: false
