{{- /* Two NetworkPolicies, one per workload (Gitea #5). THIS CHART RENDERS TWO POD TEMPLATES AND THEY NEED DIFFERENT RULES, which is why this file is written out rather than generated from one shape like most of the sweep. A single policy selecting both would have to be the UNION of what each needs - Synapse's federation egress granted to the delegation server, and the delegation server's ports granted to Synapse - and a policy that is the union of two workloads' needs is strictly weaker than two policies, while looking like one decision. --networkpolicy the Synapse StatefulSet --delegation-networkpolicy the delegation nginx Deployment The split matters most on egress. Synapse federates: it opens connections to other people's homeservers, to push gateways and to identity servers, so its egress rule is necessarily wide. The delegation server serves two static JSON documents under /.well-known/matrix and calls NOTHING - so its egress is DNS and nothing else, which is as tight as this object gets. Merging them would have handed Synapse's reach to an nginx whose entire job is to return a constant. BOTH WORKLOADS SIT BEHIND THE SAME INGRESS, on different paths (/ to Synapse, /.well-known/matrix to the delegation server), so both carry the ingress controller in their restricted branch - and both would go dark together if it were wrong. AND BOTH PROBE PORTS STAY OPEN BY DEFAULT, because all three probes of each workload address its own HTTP port. The kubelet probes from the IP of the worker node running the pod, which no chart can know; narrowing is networkPolicy.probeSources, and it applies to both policies at once. WHAT THESE POLICIES DO NOT DO: - EGRESS IS BY PORT, NEVER BY DESTINATION. A NetworkPolicy matches CIDRs and selectors and never hostnames, so "only the homeservers we federate with" cannot be expressed - and with Canal (Flannel plus Calico) there is no FQDN rule to reach for. Federation therefore means 443 and 8448 to any address, which is the honest description: Synapse may contact any homeserver on the internet, because that is what federation is. - THE DATABASE PORT IS A GUESS. Synapse's Postgres connection lives inside secrets.configSecretRef, a 1Password item this chart never parses, so 5432 is the standard port and not the configured one. - A POLICY IS ONLY A BOUNDARY WHERE THE CNI ENFORCES ONE. */ -}} {{- if .Values.networkPolicy.enabled }} {{- $np := .Values.networkPolicy }} {{- $ns := .Release.Namespace }} {{- $probeSources := $np.probeSources | default list }} {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy spec: {{- /* Selects the Synapse StatefulSet's pods - NOT the delegation Deployment's, which has its own policy below. ci/run-networkpolicy-test.sh reports which pod templates each selector matches, so a label renamed in one of the two workloads shows up as a finding rather than as a policy that filters nothing. */}} podSelector: matchLabels: app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs policyTypes: - Ingress - Egress ingress: {{- /* 8008 - Synapse's client and federation API, the port the Service forwards 80 to, and the port all three probes address. The probe path. */}} - ports: - protocol: TCP port: 8008 {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes come from the IP of the worker node running the pod, so that address has to be in the list or the pod never becomes Ready - and a pod that is never Ready reads as a broken application rather than as a policy. MIND THE FEDERATION SIDE OF THIS RULE. Inbound federation traffic arrives through the Ingress, so the controller entry below is what keeps other homeservers able to reach this one. Restricting this rule without it does not merely hide a web UI: it stops federation inbound, which looks like other servers being unreachable rather than like a local policy. */}} from: - namespaceSelector: matchLabels: {{- /* Set by Kubernetes itself on every namespace (>= 1.21), so this needs no labelling work on a namespace this chart does not own. */}} kubernetes.io/metadata.name: {{ $ns }} {{- if and $routed $ic.namespace }} {{- /* ONE from-entry with TWO selectors, which is an AND: pods carrying these labels IN that namespace. Written as two list entries it would be an OR, and would open the port to every pod in kube-system. */}} - namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ $ic.namespace | quote }} {{- with $ic.podLabels }} podSelector: matchLabels: {{- toYaml . | nindent 14 }} {{- end }} {{- end }} {{- range $allowedNs }} - namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ . | quote }} {{- end }} {{- range $probeSources }} - ipBlock: cidr: {{ . | quote }} {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the cluster, because this port carries no NodePort. The alternative was to guess the worker node CIDRs, and a wrong guess blocks every kubelet probe: the pod stays un-Ready, the rollout times out, and the cause looks like the application. An empty "from: []" would be worse than either - it allows everything too, but while reading as a restriction. */}} {{- end }} egress: {{- /* DNS, unconditionally. There is no value to switch this off, and that is deliberate: a knob whose only other setting breaks every name lookup in the pod is a trap and not a feature. Synapse needs it more than most - every federated server is resolved by name, including its SRV and .well-known delegation lookups. */}} - ports: - protocol: UDP port: 53 - protocol: TCP port: 53 {{- /* PostgreSQL on the STANDARD port. The connection lives inside secrets.configSecretRef, a 1Password item this chart never parses, so this is the conventional port and not the configured one - a Postgres elsewhere is blocked and belongs in networkPolicy.egress.extraPorts. */}} - ports: - protocol: TCP port: 5432 {{- /* FEDERATION, and this is the rule that cannot be narrowed. 443 and 8448 to ANY destination: 8448 is the Matrix federation port, 443 is where a delegating homeserver puts its federation API instead, and both are also what Synapse uses to fetch remote media, to reach a push gateway and to reach an identity server. "Any destination" is not laziness here, it is the specification: a federating homeserver may contact any other homeserver on the internet, and this object cannot match a hostname in any case. What the cap still buys is real - Synapse cannot open a connection on any OTHER port, so a compromised instance reaches no database, no SSH and no mail server elsewhere in the cluster. */}} - ports: - protocol: TCP port: 443 - protocol: TCP port: 8448 {{- with $np.egress }} {{- with .extraPorts }} {{- /* Ports only, to any destination - see the header. The operator's escape hatch from the egress cap, and the only alternative to switching the whole policy off. */}} - ports: {{- range . }} - protocol: TCP port: {{ int . }} {{- end }} {{- end }} {{- end }} --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-networkpolicy labels: app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-networkpolicy spec: {{- /* Selects the delegation nginx Deployment's pods only. */}} podSelector: matchLabels: app: {{ .Release.Name }}-{{ .Chart.Name }}-delegation-nginx-deployment policyTypes: - Ingress - Egress ingress: {{- /* 8080 - the only port this pod declares, the port the delegation Service forwards 80 to, and the port all three probes address. The probe path. */}} - ports: - protocol: TCP port: 8080 {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources - the same value governs both policies in this chart. The kubelet probes from the node's own IP, so it has to be in the list or this pod never becomes Ready. AND THE CONSUMERS OF THIS ONE ARE OTHER PEOPLE'S HOMESERVERS. The delegation documents under /.well-known/matrix are fetched by every remote server that wants to federate with this one, through the Ingress. Restrict this rule without the controller entry and federation stops being discoverable - a failure that shows up on OTHER servers, not here. */}} from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ $ns }} {{- if and $routed $ic.namespace }} - namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ $ic.namespace | quote }} {{- with $ic.podLabels }} podSelector: matchLabels: {{- toYaml . | nindent 14 }} {{- end }} {{- end }} {{- range $allowedNs }} - namespaceSelector: matchLabels: kubernetes.io/metadata.name: {{ . | quote }} {{- end }} {{- range $probeSources }} - ipBlock: cidr: {{ . | quote }} {{- end }} {{- else }} {{- /* Open inside the cluster, as above - the probe path. */}} {{- end }} egress: {{- /* DNS AND NOTHING ELSE, and this is the tightest egress rule in the chart on purpose. This nginx serves two static JSON documents and calls nobody; it does not touch the database, it does not federate, it has no upstream. Even DNS is only here because the container resolves at start. This is exactly what the two-policy split buys. Folded into Synapse's policy, this pod would have been permitted 443, 8448 and 5432 - full federation reach and the database - for a workload whose entire job is to return a constant. networkPolicy.egress.extraPorts deliberately does NOT apply here: an entry meant for Synapse's database has no business widening this. */}} - ports: - protocol: UDP port: 53 - protocol: TCP port: 53 {{- end }}