From c8a81e805d8590b866624e07db409112d1db5554 Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Mon, 26 Oct 2026 23:25:28 +0100 Subject: [PATCH 1/7] cert-manager: NetworkPolicy --- cert-manager/networkpolicy.yaml | 73 +++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 cert-manager/networkpolicy.yaml diff --git a/cert-manager/networkpolicy.yaml b/cert-manager/networkpolicy.yaml new file mode 100644 index 0000000..5fec208 --- /dev/null +++ b/cert-manager/networkpolicy.yaml @@ -0,0 +1,73 @@ +# cert-manager's three pods reach each other, DNS and the API server (the +# node, port 16443), and take connections only from each other and the +# node: the API server calls the webhook from there and the kubelet probes +# come from there too (10.1.7.64 is the node's Calico VXLAN address, +# projectcalico.org/IPv4VXLANTunnelAddr). Nothing scrapes the metrics on +# 9402. The HTTP-01 solver pods run in each Certificate's own namespace +# (acme-http01-solver in /networkpolicy.yaml), not here. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: {} + - from: + - ipBlock: + cidr: 192.168.1.204/32 + - ipBlock: + cidr: 10.1.7.64/32 + egress: + - to: + - podSelector: {} + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - ipBlock: + cidr: 192.168.1.204/32 + ports: + - port: 16443 +--- +# The controller talks to Let's Encrypt (443), and before it lets Let's +# Encrypt check a challenge it fetches http:///.well-known/... itself +# (80). Every certificate's host has a public address, node001's or the +# home router's (checked 2026-10-26), so the LAN stays out. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: controller-to-internet +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: cert-manager + app.kubernetes.io/component: controller + policyTypes: + - Egress + egress: + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 10.0.0.0/8 + - 100.64.0.0/10 + - 169.254.0.0/16 + - 172.16.0.0/12 + - 192.168.0.0/16 + ports: + - port: 80 + - port: 443 From d4e99d28c7e08c9e07facd18b675fb8badd63cfd Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Mon, 26 Oct 2026 23:25:50 +0100 Subject: [PATCH 2/7] cnpg-system: NetworkPolicy, through a folder app of its own --- apps/templates/cloudnative-pg-folder.yaml | 19 ++++++++ cloudnative-pg/networkpolicy.yaml | 56 +++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 apps/templates/cloudnative-pg-folder.yaml create mode 100644 cloudnative-pg/networkpolicy.yaml diff --git a/apps/templates/cloudnative-pg-folder.yaml b/apps/templates/cloudnative-pg-folder.yaml new file mode 100644 index 0000000..f6e740c --- /dev/null +++ b/apps/templates/cloudnative-pg-folder.yaml @@ -0,0 +1,19 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: cloudnative-pg-folder + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://github.com/rubenhensen/k8scd.git + targetRevision: HEAD + path: cloudnative-pg + destination: + server: https://kubernetes.default.svc + namespace: cnpg-system + syncPolicy: + automated: + selfHeal: true diff --git a/cloudnative-pg/networkpolicy.yaml b/cloudnative-pg/networkpolicy.yaml new file mode 100644 index 0000000..eb822b1 --- /dev/null +++ b/cloudnative-pg/networkpolicy.yaml @@ -0,0 +1,56 @@ +# The CloudNativePG operator reaches DNS, the API server (the node, port +# 16443) and the PostgreSQL instances it runs in other namespaces: the +# instance manager's status port (8000) and PostgreSQL (5432), which the +# instances' own policies let in (postgres-cnpg in authentik and immich). +# Besides its own namespace it takes connections only from the node: the +# API server calls its webhook (9443) from there and the kubelet probes +# come from there too (10.1.7.64 is the node's Calico VXLAN address, +# projectcalico.org/IPv4VXLANTunnelAddr). The chart's Application +# (apps/templates/cloudnativepg-helm.yaml) only installs the chart, so +# this folder gets an Application of its own, cloudnative-pg-folder. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: {} + - from: + - ipBlock: + cidr: 192.168.1.204/32 + - ipBlock: + cidr: 10.1.7.64/32 + egress: + - to: + - podSelector: {} + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - ipBlock: + cidr: 192.168.1.204/32 + ports: + - port: 16443 + - to: + - namespaceSelector: {} + podSelector: + matchExpressions: + - key: cnpg.io/cluster + operator: Exists + ports: + - port: 8000 + - port: 5432 From fa33bf44aebcf72a631bacb77c1fa623dfe18bcb Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Mon, 26 Oct 2026 23:26:06 +0100 Subject: [PATCH 3/7] external-secrets: NetworkPolicy, through a folder app of its own --- apps/templates/external-secrets-folder.yaml | 19 ++++++ external-secrets/networkpolicy.yaml | 68 +++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 apps/templates/external-secrets-folder.yaml create mode 100644 external-secrets/networkpolicy.yaml diff --git a/apps/templates/external-secrets-folder.yaml b/apps/templates/external-secrets-folder.yaml new file mode 100644 index 0000000..f069ccd --- /dev/null +++ b/apps/templates/external-secrets-folder.yaml @@ -0,0 +1,19 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: external-secrets-folder + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://github.com/rubenhensen/k8scd.git + targetRevision: HEAD + path: external-secrets + destination: + server: https://kubernetes.default.svc + namespace: external-secrets + syncPolicy: + automated: + selfHeal: true diff --git a/external-secrets/networkpolicy.yaml b/external-secrets/networkpolicy.yaml new file mode 100644 index 0000000..2e92435 --- /dev/null +++ b/external-secrets/networkpolicy.yaml @@ -0,0 +1,68 @@ +# External Secrets' three pods reach each other, DNS and the API server +# (the node, port 16443); the controller also reads Vault +# (vault-active.vault.svc:8200, which vault/networkpolicy.yaml lets it +# into). Besides their own namespace they take connections only from the +# node: the API server calls the webhook from there and the kubelet probes +# come from there too (10.1.7.64 is the node's Calico VXLAN address, +# projectcalico.org/IPv4VXLANTunnelAddr). The Password generator behind +# gatus/backup-check-token needs no network. The chart's Application +# (apps/templates/external-secrets-helm.yaml) only installs the chart, so +# this folder gets an Application of its own, external-secrets-folder. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: {} + - from: + - ipBlock: + cidr: 192.168.1.204/32 + - ipBlock: + cidr: 10.1.7.64/32 + egress: + - to: + - podSelector: {} + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - ipBlock: + cidr: 192.168.1.204/32 + ports: + - port: 16443 +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: controller-to-vault +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: external-secrets + policyTypes: + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: vault + podSelector: + matchLabels: + app.kubernetes.io/name: vault + ports: + - port: 8200 From 8110a235a91d0762d2c7cba0fd230cb199f47f25 Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Mon, 26 Oct 2026 23:26:15 +0100 Subject: [PATCH 4/7] velero: NetworkPolicy --- velero/networkpolicy.yaml | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 velero/networkpolicy.yaml diff --git a/velero/networkpolicy.yaml b/velero/networkpolicy.yaml new file mode 100644 index 0000000..8d71c68 --- /dev/null +++ b/velero/networkpolicy.yaml @@ -0,0 +1,52 @@ +# Velero, its node agent, and the pods it starts here (the data movers of +# each pod volume backup or restore, kopia's maintenance jobs, the chart's +# CRD upgrade job) reach each other, DNS, the API server (the node, port +# 16443) and Garage on the NAS (192.168.1.99:3900, nas/garage). The node +# agent reads volumes through its hostPath mount, not the network. Besides +# their own namespace they take connections only from the node (kubelet +# probes; 10.1.7.64 is the node's Calico VXLAN address, +# projectcalico.org/IPv4VXLANTunnelAddr); nothing scrapes the metrics on +# 8085. MinIO (the NAS, port 9000) is not allowed: going back to it means +# adding its port here as well. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: {} + - from: + - ipBlock: + cidr: 192.168.1.204/32 + - ipBlock: + cidr: 10.1.7.64/32 + egress: + - to: + - podSelector: {} + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - ipBlock: + cidr: 192.168.1.204/32 + ports: + - port: 16443 + - to: + - ipBlock: + cidr: 192.168.1.99/32 + ports: + - port: 3900 From 3834f7a3546c758718f02085fe26408ccc785949 Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Mon, 26 Oct 2026 23:26:25 +0100 Subject: [PATCH 5/7] mariadb-system: NetworkPolicy --- mariadb-operator/kustomization.yaml | 1 + mariadb-operator/networkpolicy.yaml | 54 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 mariadb-operator/networkpolicy.yaml diff --git a/mariadb-operator/kustomization.yaml b/mariadb-operator/kustomization.yaml index b34e723..44e59d1 100644 --- a/mariadb-operator/kustomization.yaml +++ b/mariadb-operator/kustomization.yaml @@ -3,3 +3,4 @@ kind: Kustomization resources: - operator-crds-helm.yaml - operator-helm.yaml + - networkpolicy.yaml diff --git a/mariadb-operator/networkpolicy.yaml b/mariadb-operator/networkpolicy.yaml new file mode 100644 index 0000000..85c957e --- /dev/null +++ b/mariadb-operator/networkpolicy.yaml @@ -0,0 +1,54 @@ +# The mariadb-operator's three pods reach each other, DNS and the API +# server (the node, port 16443); the operator also connects to MariaDB on +# 3306 for the databases, users and grants it manages (mbgwp's +# mariadb-from-operator lets it in). Besides their own namespace they take +# connections only from the node: the API server calls the webhook from +# there and the kubelet probes come from there too (10.1.7.64 is the node's +# Calico VXLAN address, projectcalico.org/IPv4VXLANTunnelAddr). This +# folder's Application deploys into argocd (the operator's own +# Applications), hence the namespace here. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default + namespace: mariadb-system +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: + - from: + - podSelector: {} + - from: + - ipBlock: + cidr: 192.168.1.204/32 + - ipBlock: + cidr: 10.1.7.64/32 + egress: + - to: + - podSelector: {} + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - ipBlock: + cidr: 192.168.1.204/32 + ports: + - port: 16443 + - to: + - namespaceSelector: {} + podSelector: + matchLabels: + app.kubernetes.io/name: mariadb + ports: + - port: 3306 From 13b92fff54d992d73d3fcc6b14fc062a20fa1034 Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Mon, 26 Oct 2026 23:26:38 +0100 Subject: [PATCH 6/7] dns: NetworkPolicy --- dns/kustomization.yaml | 1 + dns/networkpolicy.yaml | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 dns/networkpolicy.yaml diff --git a/dns/kustomization.yaml b/dns/kustomization.yaml index f41d991..41a0ac8 100644 --- a/dns/kustomization.yaml +++ b/dns/kustomization.yaml @@ -7,6 +7,7 @@ resources: - external-secret.yaml - rbac.yaml - cronjob.yaml + - networkpolicy.yaml configMapGenerator: - name: dns-sync-script diff --git a/dns/networkpolicy.yaml b/dns/networkpolicy.yaml new file mode 100644 index 0000000..802b28a --- /dev/null +++ b/dns/networkpolicy.yaml @@ -0,0 +1,37 @@ +# The only pod here is the dns-sync Job (a PostSync hook): it installs its +# Python packages from PyPI and pushes domains/ to TransIP's API, so it +# reaches DNS and the internet (not the LAN, the API server or other +# namespaces). Nothing connects to it. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + k8s-app: kube-dns + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + - to: + - ipBlock: + cidr: 0.0.0.0/0 + except: + - 10.0.0.0/8 + - 100.64.0.0/10 + - 169.254.0.0/16 + - 172.16.0.0/12 + - 192.168.0.0/16 + ports: + - port: 443 From c384ae921f9bd51bd54780db0dcbb7ae7d59df56 Mon Sep 17 00:00:00 2001 From: Ruben Hensen Date: Mon, 26 Oct 2026 23:26:47 +0100 Subject: [PATCH 7/7] default: deny all, through a folder app of its own --- apps/templates/default-folder.yaml | 19 +++++++++++++++++++ default/networkpolicy.yaml | 14 ++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 apps/templates/default-folder.yaml create mode 100644 default/networkpolicy.yaml diff --git a/apps/templates/default-folder.yaml b/apps/templates/default-folder.yaml new file mode 100644 index 0000000..fd55287 --- /dev/null +++ b/apps/templates/default-folder.yaml @@ -0,0 +1,19 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: default-folder + namespace: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + source: + repoURL: https://github.com/rubenhensen/k8scd.git + targetRevision: HEAD + path: default + destination: + server: https://kubernetes.default.svc + namespace: default + syncPolicy: + automated: + selfHeal: true diff --git a/default/networkpolicy.yaml b/default/networkpolicy.yaml new file mode 100644 index 0000000..5a7ae2a --- /dev/null +++ b/default/networkpolicy.yaml @@ -0,0 +1,14 @@ +# Nothing runs in default (2026-10-26: only the kubernetes Service, whose +# endpoint is the API server on the node, which this doesn't touch). A pod +# started here, by `kubectl run` or a manifest without a namespace, gets +# no network at all, instead of reaching the LAN, Vault and every app. +# Anything that needs a network gets a namespace and a policy of its own. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress