-
Notifications
You must be signed in to change notification settings - Fork 32
feat(chart): add opt-in NetworkPolicy for the Topograph release #393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| {{- if .Values.networkPolicy.enabled }} | ||
| apiVersion: networking.k8s.io/v1 | ||
| kind: NetworkPolicy | ||
| metadata: | ||
| name: {{ include "topograph.fullname" . }} | ||
| namespace: {{ .Release.Namespace }} | ||
| labels: | ||
| {{- include "topograph.labels" . | nindent 4 }} | ||
| spec: | ||
| # Select every Topograph component in this release (API server, node-observer, | ||
| # node-data-broker) via the shared instance label, so the policy protects the | ||
| # whole release rather than just the API server. | ||
| podSelector: | ||
| matchLabels: | ||
| app.kubernetes.io/instance: {{ .Release.Name }} | ||
| policyTypes: | ||
| - Ingress | ||
| {{- if .Values.networkPolicy.extraEgress }} | ||
| - Egress | ||
| {{- end }} | ||
| ingress: | ||
| # Intra-release traffic: the node-observer calls the API server to trigger | ||
| # regeneration. Allowing release pods to reach each other covers it without | ||
| # naming ports. | ||
| - from: | ||
| - podSelector: | ||
| matchLabels: | ||
| app.kubernetes.io/instance: {{ .Release.Name }} | ||
| {{- if .Values.serviceMonitor.enabled }} | ||
| # Prometheus scrapes the API server /metrics on the service port. | ||
| - from: | ||
| - namespaceSelector: | ||
| matchLabels: | ||
| kubernetes.io/metadata.name: {{ .Values.serviceMonitor.namespace }} | ||
| ports: | ||
| - protocol: TCP | ||
| port: {{ .Values.service.port }} | ||
| {{- end }} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When ingress.enabled or gatewayAPI.enabled is used, requests normally originate from an ingress/gateway controller in another namespace. This policy permits only same-release pods and, conditionally, the ServiceMonitor namespace, so enabling networkPolicy makes those chart-provided routes unreachable unless the operator happens to add the correct extraIngress rule. Since the controller selector is cluster-specific, please at least document this required extraIngress configuration beside the Ingress/HTTPRoute guidance and add combination tests; otherwise two supported chart features silently conflict. |
||
| {{- with .Values.networkPolicy.extraIngress }} | ||
| {{- toYaml . | nindent 4 }} | ||
| {{- end }} | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| {{- if .Values.networkPolicy.extraEgress }} | ||
| egress: | ||
| # DNS resolution to CoreDNS, required before any component can reach a named | ||
| # endpoint. Scoped to the kube-dns pods in kube-system. The pod label | ||
| # defaults to the standard `k8s-app: kube-dns`; clusters whose CoreDNS | ||
| # carries a different label (e.g. `app.kubernetes.io/name: coredns`) can | ||
| # override it via `networkPolicy.dnsPodSelector`. | ||
| - to: | ||
| - namespaceSelector: | ||
| matchLabels: | ||
| kubernetes.io/metadata.name: kube-system | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| podSelector: | ||
| matchLabels: | ||
| {{- toYaml (.Values.networkPolicy.dnsPodSelector | default (dict "k8s-app" "kube-dns")) | nindent 14 }} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ports: | ||
| - protocol: UDP | ||
| port: 53 | ||
| - protocol: TCP | ||
| port: 53 | ||
| # Intra-release traffic: the node-observer reaching the API server Service. | ||
| - to: | ||
| - podSelector: | ||
| matchLabels: | ||
| app.kubernetes.io/instance: {{ .Release.Name }} | ||
| # Operator-supplied egress. Under a default-deny-egress cluster this MUST | ||
| # include the Kubernetes API server (all three components call it) and the | ||
| # provider's topology source (e.g. the BCM/NetQ endpoint) -- those addresses | ||
| # are cluster-specific and cannot be templated portably. See the | ||
| # networkPolicy.extraEgress comments in values.yaml for examples. | ||
| {{- toYaml .Values.networkPolicy.extraEgress | nindent 4 }} | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| {{- end }} | ||
| {{- end }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| suite: topograph networkpolicy | ||
| templates: | ||
| - templates/networkpolicy.yaml | ||
| release: | ||
| name: chart-ci | ||
| namespace: topograph | ||
| tests: | ||
| - it: renders nothing by default | ||
| asserts: | ||
| - hasDocuments: | ||
| count: 0 | ||
|
|
||
| - it: renders a NetworkPolicy covering the whole release when enabled | ||
| set: | ||
| networkPolicy: | ||
| enabled: true | ||
| asserts: | ||
| - isKind: | ||
| of: NetworkPolicy | ||
| - equal: | ||
| path: spec.podSelector.matchLabels["app.kubernetes.io/instance"] | ||
| value: chart-ci | ||
| - contains: | ||
| path: spec.policyTypes | ||
| content: Ingress | ||
| - notContains: | ||
| path: spec.policyTypes | ||
| content: Egress | ||
| # Intra-release ingress so the node-observer can reach the API server. | ||
| - equal: | ||
| path: spec.ingress[0].from[0].podSelector.matchLabels["app.kubernetes.io/instance"] | ||
| value: chart-ci | ||
|
|
||
| - it: adds a Prometheus ingress rule when serviceMonitor is enabled | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two coverage gaps beyond dmitsh's notes: the extraIngress append branch has no test (only extraEgress is exercised, so deleting that template block leaves every case green), and this Prometheus rule asserts the source namespace but never the port — a regression on service.port would pass. An extraIngress case, ideally combined with serviceMonitor.enabled, would also cover the combination test dmitsh asked for. |
||
| set: | ||
| networkPolicy: | ||
| enabled: true | ||
| serviceMonitor: | ||
| enabled: true | ||
| namespace: monitoring | ||
| asserts: | ||
| - lengthEqual: | ||
| path: spec.ingress | ||
| count: 2 | ||
| - equal: | ||
| path: spec.ingress[1].from[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"] | ||
| value: monitoring | ||
|
|
||
| - it: adds Egress with DNS and intra-release allows when extraEgress is set | ||
| set: | ||
| networkPolicy: | ||
| enabled: true | ||
| extraEgress: | ||
| - to: | ||
| - ipBlock: | ||
| cidr: 10.1.2.0/24 | ||
| ports: | ||
| - protocol: TCP | ||
| port: 8081 | ||
| asserts: | ||
| - contains: | ||
| path: spec.policyTypes | ||
| content: Egress | ||
| # egress[0] = DNS (scoped to kube-dns pods in kube-system), | ||
| # egress[1] = intra-release, egress[2] = operator-supplied | ||
| - equal: | ||
| path: spec.egress[0].to[0].namespaceSelector.matchLabels["kubernetes.io/metadata.name"] | ||
| value: kube-system | ||
| - equal: | ||
| path: spec.egress[0].to[0].podSelector.matchLabels["k8s-app"] | ||
| value: kube-dns | ||
| - equal: | ||
| path: spec.egress[0].ports[0].port | ||
| value: 53 | ||
| - equal: | ||
| path: spec.egress[1].to[0].podSelector.matchLabels["app.kubernetes.io/instance"] | ||
| value: chart-ci | ||
| - equal: | ||
| path: spec.egress[2].to[0].ipBlock.cidr | ||
| value: 10.1.2.0/24 | ||
|
|
||
| - it: scopes the DNS egress rule to a custom dnsPodSelector | ||
| set: | ||
| networkPolicy: | ||
| enabled: true | ||
| dnsPodSelector: | ||
| app.kubernetes.io/name: coredns | ||
| extraEgress: | ||
| - to: | ||
| - ipBlock: | ||
| cidr: 10.1.2.0/24 | ||
| asserts: | ||
| - equal: | ||
| path: spec.egress[0].to[0].podSelector.matchLabels["app.kubernetes.io/name"] | ||
| value: coredns | ||
| - notExists: | ||
| path: spec.egress[0].to[0].podSelector.matchLabels["k8s-app"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -184,6 +184,25 @@ | |
| } | ||
| } | ||
| }, | ||
| "networkPolicy": { | ||
| "type": "object", | ||
| "description": "Opt-in NetworkPolicy for the whole release. extraEgress, when non-empty, adds an Egress policyType plus a DNS allow rule. extraIngress/extraEgress items must be non-empty objects — an empty {} rule matches all traffic.", | ||
| "properties": { | ||
| "enabled": { "type": "boolean" }, | ||
| "dnsPodSelector": { | ||
| "type": "object", | ||
| "description": "Pod label selector matching the cluster's DNS server, used to scope the DNS egress allow rule. Defaults to { k8s-app: kube-dns }; override for CoreDNS deployments carrying a different label." | ||
| }, | ||
| "extraIngress": { | ||
| "type": "array", | ||
| "items": { "type": "object", "minProperties": 1 } | ||
| }, | ||
| "extraEgress": { | ||
| "type": "array", | ||
| "items": { "type": "object", "minProperties": 1 } | ||
|
Comment on lines
+198
to
+202
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+198
to
+202
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
| } | ||
| }, | ||
| "tests": { | ||
| "type": "object", | ||
| "properties": { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -212,6 +212,55 @@ serviceMonitor: | |
| interval: 15s | ||
| scheme: http | ||
|
|
||
| # Opt-in NetworkPolicy covering all Topograph components (API server, | ||
| # node-observer, node-data-broker), selected by the release instance label. | ||
| # When enabled, ingress is denied except: intra-release traffic (so the | ||
| # node-observer can reach the API server) and, when serviceMonitor.enabled, the | ||
| # Prometheus scrape namespace (serviceMonitor.namespace). | ||
| # | ||
| # Egress is only constrained when `extraEgress` is set -- this keeps enabling | ||
| # the policy from breaking egress on clusters that do not run a default-deny. | ||
| # When `extraEgress` is set, an Egress policyType is added with DNS and | ||
| # intra-release allows, then your rules. On a default-deny-egress cluster you | ||
| # MUST include the Kubernetes API server (all three components call it) and the | ||
| # provider's topology source (e.g. the BCM/NetQ endpoint); those addresses are | ||
| # cluster-specific and cannot be templated portably. `extraIngress` appends | ||
| # custom ingress rules. Note: an empty rule object (`{}`) in extraIngress / | ||
| # extraEgress matches ALL traffic in that direction -- always give explicit | ||
| # `to`/`from`/`ports`. | ||
| # | ||
| # NetworkPolicy egress cannot target hostnames, so provider endpoints must be | ||
| # given as ipBlock CIDRs. See docs/engines/k8s.md "Per-provider egress" for a | ||
| # per-provider table (netq, CSP/IMDS caveat, dra/infiniband need none). | ||
| networkPolicy: | ||
| enabled: false | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every other opt-in networking feature (ingress.enabled, gatewayAPI.enabled, nodeDataBroker.enabled) emits a NOTES.txt hint, but networkPolicy emits nothing. A conditional NOTE here would surface at install time exactly the silent footguns this introduces: egress needs extraEgress on default-deny clusters, ingress/gateway routes and external /v1/generate callers need extraIngress, and the policy is a no-op without an enforcing CNI. |
||
| # Pod label selector for the cluster's DNS server, used to scope the DNS | ||
| # egress allow rule (only rendered when extraEgress is set). Leave empty to use | ||
| # the standard kube-dns/CoreDNS label `{ k8s-app: kube-dns }`. Set this to | ||
| # replace that default for clusters whose CoreDNS carries a different label, | ||
| # e.g. `{ app.kubernetes.io/name: coredns }`. (Kept empty by default so a Helm | ||
| # value override replaces it cleanly rather than deep-merging both labels, | ||
| # which would AND them and match no DNS pod.) | ||
| dnsPodSelector: {} | ||
| extraIngress: [] | ||
| extraEgress: [] | ||
| # # Kubernetes API server (all providers under default-deny; adjust to your | ||
| # # cluster -- `kubectl get endpoints kubernetes -n default`): | ||
| # - to: | ||
| # - ipBlock: | ||
| # cidr: 10.96.0.1/32 | ||
| # ports: | ||
| # - protocol: TCP | ||
| # port: 6443 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example combines the Kubernetes Service IP with the usual control-plane endpoint port. In-cluster client-go uses KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORT, normally the Service IP on 443; kubectl get endpoints returns an endpoint IP normally on 6443. Depending on where the CNI evaluates policy relative to DNAT, operators may need either Service-IP:443 or endpoint-IP:6443, but Service-IP:6443 matches neither path and can leave all three components unable to reach the API after Egress isolation is enabled. Please correct this example and explain the CNI-dependent alternative consistently with the docs example/table. |
||
| # # Provider topology source, e.g. netq apiUrl host or the BCM endpoint | ||
| # # (resolve the hostname to an ipBlock): | ||
| # - to: | ||
| # - ipBlock: | ||
| # cidr: 10.0.0.0/24 | ||
| # ports: | ||
| # - protocol: TCP | ||
| # port: 8081 | ||
|
|
||
| tests: | ||
| # `helm test` hook pods (templates/tests/) probe the Topograph API's | ||
| # /healthz and /metrics endpoints via the in-cluster Service. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
serviceMonitor.namespace controls where the ServiceMonitor object is created, not necessarily the namespace of the Prometheus pod that performs the scrape. A central Prometheus can watch ServiceMonitors in other namespaces, so this selector can still block metrics even though serviceMonitor.enabled is correctly configured. Please use a separately configurable scraper namespace/selector (or require extraIngress and document it) rather than treating the ServiceMonitor resource namespace as the traffic source.