Skip to content

9. Security

Goal: restrict who can reach the database at two layers — the Kubernetes network (NetworkPolicy) and PostgreSQL's own host-based auth (pg_hba.conf) — and apply sane RBAC. Defense in depth.

The two layers

flowchart LR
    other["Other pods"] -. blocked by NetworkPolicy .-> pg
    app["App pods (label app=myapp)"] -->|allowed| netpol["NetworkPolicy<br/>(L3/L4 firewall)"] --> hba["pg_hba.conf<br/>(Postgres-level auth)"] --> pg["PostgreSQL"]
  • NetworkPolicy decides which pods may even open a TCP connection to 5432.
  • pg_hba.conf decides which clients PostgreSQL itself accepts, and how they authenticate.

Step 9.1 — A NetworkPolicy (the network firewall)

Allow only your application pods (and the database's own pods, for replication) to reach the cluster on 5432.

netpol.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-app-to-pg
  namespace: production
spec:
  podSelector:
    matchLabels:
      cnpg.io/cluster: pg          # (1)!
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: myapp           # (2)!
      ports:
        - protocol: TCP
          port: 5432
    - from:
        - podSelector:
            matchLabels:
              cnpg.io/cluster: pg  # (3)!
      ports:
        - protocol: TCP
          port: 5432
  1. Selects the database pods (the operator labels them cnpg.io/cluster: pg).
  2. Only pods labeled app: myapp may connect.
  3. Allow the database pods to talk to each other (replication).

Does your CNI enforce NetworkPolicy?

A NetworkPolicy is only meaningful if the network plugin enforces it. On kube-hetzner, k3s ships a built-in NetworkPolicy controller that enforces policies even with the default Flannel CNI; Cilium and Calico enforce them too. If you explicitly disabled the controller, the policy is silently ignored. Confirm which CNI you chose and that enforcement is on.

kubectl apply -f netpol.yaml
kubectl describe networkpolicy allow-app-to-pg -n production

Step 9.2 — pg_hba with dynamic pod selectors (CNPG 1.29)

CloudNativePG 1.29 added podSelectorRefs: you write pg_hba.conf rules that target client pods by label, and the operator resolves their (changing) IPs for you — no static CIDRs to maintain. Add this to the Cluster's spec.postgresql:

  postgresql:
    # ... synchronous, parameters from chapter 6 ...
    pg_hba:                          # (1)!
      - hostssl app app_user podSelectorRefs(app=myapp) scram-sha-256
  1. Allow user app_user into database app over TLS, but only from pods labeled app=myapp, authenticating with SCRAM. The exact podSelectorRefs syntax is new — confirm it against the 1.29 docs for your minor version.

This complements the NetworkPolicy: even if something slips past the network layer, PostgreSQL itself only trusts the right pods.

Step 9.3 — RBAC hygiene

The operator already creates tightly-scoped RBAC for itself. Your job is to not hand application service accounts permission to touch CNPG resources:

  • Application pods need access to the Secret (to read the DB password) and the network path — nothing more.
  • Do not grant apps get/list/edit on Cluster, Backup, or ObjectStore. Those are operations concerns.

A minimal Role for an app that only reads its DB secret:

app-rbac.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: read-pg-app-secret
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["pg-app-credentials"]
    verbs: ["get"]

(Bind it to the app's ServiceAccount with a RoleBinding.)

Step 9.4 — TLS is already on

CNPG issues and rotates TLS certificates for client and replication traffic by default. Using hostssl in pg_hba (above) requires clients to use TLS. You generally do not need to manage these certs by hand.

What could go wrong

  • NetworkPolicy ignored → CNI not enforcing; verify your CNI/controller.
  • Locked yourself out → a too-strict NetworkPolicy or pg_hba can block the app or even replication. Always allow the cnpg.io/cluster: pg → 5432 path for intra-cluster replication.
  • podSelectorRefs syntax → it is version-specific and new; check the exact form in the 1.29 docs before relying on it.

Where to go deeper

Next: Failover & switchover.