From 30578abaafac60ec1fff775423525fdf2b878bf6 Mon Sep 17 00:00:00 2001 From: Thore Cimbal Date: Tue, 28 Jul 2026 15:16:37 +0200 Subject: [PATCH] feat: Default-deny ingress NetworkPolicy for matrix namespace Ingress-only default-deny plus per-component allow rules, using named container ports throughout (not Service ports) after the authentik port-mismatch incident. Traefik -> element-web/element-admin/haproxy (fronts both matrix.axion1337.chat and well-known)/MAS; synapse <- haproxy+MAS; postgres <- synapse+MAS; RTC signalling via Traefik, RTC media NodePorts left open to the internet by design; monitoring scrape allows for synapse/postgres/rtc-sfu metrics; ACME solver allow. coturn (hostNetwork) needs nothing - NetworkPolicy doesn't apply to it. Part of issue #10. --- apps/production/kustomization.yaml | 3 +- apps/production/networkpolicy.yaml | 290 +++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+), 1 deletion(-) mode change 100644 => 100755 apps/production/kustomization.yaml create mode 100644 apps/production/networkpolicy.yaml diff --git a/apps/production/kustomization.yaml b/apps/production/kustomization.yaml old mode 100644 new mode 100755 index d3c7a9b..c2b4202 --- a/apps/production/kustomization.yaml +++ b/apps/production/kustomization.yaml @@ -29,4 +29,5 @@ resources: # HelmRelease (muss ganz unten stehen, damit die ConfigMaps vorher da sind!) - element-server-suite.yaml # Custom Apex Ingress für Element Web + Well-Known auf axion1337.chat - - apex-ingress.yaml # Custom Apex Ingress für Element Web + Well-Known auf axion1337.chat \ No newline at end of file + - apex-ingress.yaml # Custom Apex Ingress für Element Web + Well-Known auf axion1337.chat + - networkpolicy.yaml \ No newline at end of file diff --git a/apps/production/networkpolicy.yaml b/apps/production/networkpolicy.yaml new file mode 100644 index 0000000..7b4aa44 --- /dev/null +++ b/apps/production/networkpolicy.yaml @@ -0,0 +1,290 @@ +# Default-deny ingress for the matrix namespace, with explicit allow rules per component. +# Egress is intentionally untouched (federation to arbitrary Matrix servers, ACME, SMTP, +# DNS all stay unrestricted). +# +# Lesson learned deploying the authentik namespace's equivalent policy: NetworkPolicy +# filters on the pod's actual container port, not the Service's external port (e.g. +# authentik-server's Service maps 80->9000). Wherever a Service here uses a *named* +# targetPort, this file references that name directly instead of guessing a number - +# Kubernetes resolves it from the pod spec, which is safer than a hardcoded port. +# +# matrix-stack-postgres already effectively has no dedicated chart NetworkPolicy of its +# own (unlike authentik-postgresql's Bitnami one) - the rules below are the only gate. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny-ingress + namespace: matrix +spec: + podSelector: {} + policyTypes: + - Ingress +--- +# axion1337.chat (root) -> Element Web +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-element-web + namespace: matrix +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: element-web + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: element +--- +# admin.axion1337.chat -> Element Admin +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-element-admin + namespace: matrix +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: element-admin + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: http +--- +# axion1337.chat/docs/setup -> Element desktop setup docs (our own nginx) +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-element-web-docs + namespace: matrix +spec: + podSelector: + matchLabels: + app: element-web-docs + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: 8080 +--- +# matrix.axion1337.chat AND the well-known delegation both front through haproxy +# (matrix-stack-synapse and matrix-stack-well-known Services both target haproxy's +# named ports, not synapse-main directly). +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-haproxy + namespace: matrix +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: haproxy + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: haproxy-synapse + - protocol: TCP + port: haproxy-403 + - protocol: TCP + port: haproxy-wkd +--- +# account.axion1337.chat (Traefik) + matrix.axion1337.chat (also routes to MAS for some +# paths) + synapse-main calling MAS's internal port for session/token introspection. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-mas + namespace: matrix +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: matrix-authentication-service + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: 8080 + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: synapse-main + ports: + - protocol: TCP + port: 8081 +--- +# Synapse itself: reached via haproxy (same namespace), calls from MAS (provisioning), +# metrics scraped by Alloy (monitoring namespace). +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-synapse + namespace: matrix +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: synapse-main + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: haproxy + - podSelector: + matchLabels: + app.kubernetes.io/name: matrix-authentication-service + ports: + - protocol: TCP + port: synapse-http + - protocol: TCP + port: synapse-health + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - protocol: TCP + port: synapse-metrics +--- +# mrtc.axion1337.chat (Traefik) for the auth handshake, plus Alloy scraping metrics. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-rtc-authorisation-service + namespace: matrix +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: matrix-rtc-authorisation-service + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + - podSelector: + matchLabels: + app.kubernetes.io/name: matrix-rtc-sfu + ports: + - protocol: TCP + port: http +--- +# The SFU: mrtc.axion1337.chat (Traefik) for signalling, Alloy for metrics, and the +# NodePort-exposed WebRTC media ports need to stay open to the internet by design - +# that's the actual point of a TURN/SFU media relay, not a mistake. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-rtc-sfu + namespace: matrix +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: matrix-rtc-sfu + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: http + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - protocol: TCP + port: metrics + - from: + - ipBlock: + cidr: 0.0.0.0/0 + ports: + - protocol: TCP + port: 30001 + - protocol: UDP + port: 30002 +--- +# Postgres: only Synapse and MAS need data access; Alloy scrapes the exporter. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-postgres + namespace: matrix +spec: + podSelector: + matchLabels: + app.kubernetes.io/name: postgres + policyTypes: + - Ingress + ingress: + - from: + - podSelector: + matchLabels: + app.kubernetes.io/name: synapse-main + - podSelector: + matchLabels: + app.kubernetes.io/name: matrix-authentication-service + ports: + - protocol: TCP + port: 5432 + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: monitoring + ports: + - protocol: TCP + port: 9187 +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: allow-ingress-acme-solver + namespace: matrix +spec: + podSelector: + matchLabels: + acme.cert-manager.io/http01-solver: "true" + policyTypes: + - Ingress + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: TCP + port: 8089 + +# Note: coturn runs with hostNetwork: true, so NetworkPolicy does not apply to it at all - +# it's already gated by the Hetzner Cloud Firewall instead. Nothing to write here.