# db-access-control-manifests.yaml # # Companion manifests for the guide # "Calico NetworkPolicy based DB access control - build, verify, operate" # (calico-db-access-control.html, section 2 "resources" and section 5 "verification") # # Document order == safe apply order. The rule "namespace label first, allow # policy later" (section 4, pitfall 7-1) is enforced by the file layout itself: # (a) GNS dbzone-all - the whole DB zone (the blocking net) # (b) GNS dbset-mysql-pay - one DB set (unit of allowance, failover IPs) # (c) GNP deny-db-egress - order 500 guardrail (Log -> Deny -> Allow) # (d) Namespace - carries the db-egress-control=enabled switch # (e) Deployment db-client-allowed - client matched by the allow policy # (f) Deployment db-client-denied - control group, matched by no allow rule # (g) NP allow-db-mysql-pay - order 200 exception # # Apply: kubectl apply -f db-access-control-manifests.yaml # Clean: kubectl delete namespace calico-egress-policy-test # WARNING: never run "kubectl delete -f" on this whole file. That would # remove the GNS/GNP as well, the deny rule would lose its match target, # and the control would silently fail open (pitfall 7-5). # # Replace before real use: # 10.20.0.0/16 -> the real DB zone subnet # 10.20.30.11/32, .12/32 -> every failover IP confirmed by the DBA (2.1, 7-2) # nicolaka/netshoot:v0.14 -> internal registry mirror (air-gapped cluster) # calico-egress-policy-test -> the target namespace # app == "db-client-allowed" -> the real workload label (e.g. app=pay-api) # # Prerequisite: applying projectcalico.org/v3 resources with kubectl requires # calico-apiserver. Without it, apply (a)(b)(c)(g) with: calicoctl apply -f --- # --------------------------------------------------------------------------- # (a) Whole DB zone - "the blocking net". Also covers DB hosts not yet # registered as a set, so an omission fails closed instead of open. # --------------------------------------------------------------------------- apiVersion: projectcalico.org/v3 kind: GlobalNetworkSet metadata: name: dbzone-all labels: kind: db # broad label the deny policy matches on spec: nets: - 10.20.0.0/16 # DB zone subnet (replace with the real range) --- # --------------------------------------------------------------------------- # (b) One DB set - "the unit of allowance". Holds every IP the service may # point at after a failover, not just the current one. # --------------------------------------------------------------------------- apiVersion: projectcalico.org/v3 kind: GlobalNetworkSet metadata: name: dbset-mysql-pay labels: kind: db # same broad label as the zone set -> denied by default db-set: mysql-pay # narrow label the allow policy matches on (sets only) spec: nets: - 10.20.30.11/32 # current primary VIP - 10.20.30.12/32 # failover candidate - register all of them (7-2) --- # --------------------------------------------------------------------------- # (c) The guardrail - denies DB-bound egress from labelled namespaces. # --------------------------------------------------------------------------- apiVersion: projectcalico.org/v3 kind: GlobalNetworkPolicy metadata: name: deny-db-egress spec: order: 500 # after allow NP (200), before k8s NP (1000) namespaceSelector: db-egress-control == "enabled" # namespace rollout switch selector: all() # every pod inside those namespaces types: - Egress egress: # rule 1 - record what is about to be dropped (non-terminal: falls through) - action: Log destination: selector: kind == "db" && !has(projectcalico.org/namespace) # rule 2 - drop egress towards the DB ranges - action: Deny destination: selector: kind == "db" && !has(projectcalico.org/namespace) # rule 3 - everything else is none of this policy's business. Without this # line the default-flip (section 1.2) drops ALL egress of those # namespaces, DNS included. - action: Allow --- # --------------------------------------------------------------------------- # (d) Test namespace - created in its final state, with the control switch on. # No istio-injection label: the DB path is verified without a sidecar, # which matches production where DB ports bypass the sidecar anyway. # --------------------------------------------------------------------------- apiVersion: v1 kind: Namespace metadata: name: calico-egress-policy-test labels: db-egress-control: "enabled" # read by the namespaceSelector of the deny GNP --- # --------------------------------------------------------------------------- # (e) Allowed client - matched by spec.selector of the allow policy (g). # --------------------------------------------------------------------------- apiVersion: apps/v1 kind: Deployment metadata: name: db-client-allowed namespace: calico-egress-policy-test spec: replicas: 1 selector: matchLabels: app: db-client-allowed template: metadata: labels: app: db-client-allowed # pod label read by the allow policy spec: containers: - name: client image: nicolaka/netshoot:v0.14 # air-gapped: use the internal mirror command: ["sleep", "infinity"] resources: requests: cpu: 10m memory: 32Mi limits: cpu: 100m memory: 64Mi --- # --------------------------------------------------------------------------- # (f) Control group - identical to (e) except for the app label, so it matches # no allow rule at all. # --------------------------------------------------------------------------- apiVersion: apps/v1 kind: Deployment metadata: name: db-client-denied namespace: calico-egress-policy-test spec: replicas: 1 selector: matchLabels: app: db-client-denied template: metadata: labels: app: db-client-denied spec: containers: - name: client image: nicolaka/netshoot:v0.14 command: ["sleep", "infinity"] resources: requests: cpu: 10m memory: 32Mi limits: cpu: 100m memory: 64Mi --- # --------------------------------------------------------------------------- # (g) The exception - opens one set on one port for one workload. # order must stay below the deny GNP (500); Allow is terminal, so matching # traffic never reaches the deny rule. # WARNING: do not add an unconditional Allow or a Pass rule here. Either one # bypasses the guardrail entirely (section 2.3). # --------------------------------------------------------------------------- apiVersion: projectcalico.org/v3 kind: NetworkPolicy metadata: name: allow-db-mysql-pay namespace: calico-egress-policy-test spec: order: 200 # must come before the deny GNP (500) selector: app == "db-client-allowed" # variant B: all() to open the whole namespace types: - Egress egress: - action: Allow protocol: TCP destination: namespaceSelector: global() # evaluate against global resources (GNS) selector: db-set == "mysql-pay" ports: - 3306