Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ spec:
runAsUser: 1000
runAsGroup: 1010
fsGroup: 1010
# Defense in depth: enforce non-root + seccomp at the pod
# level. runAsUser=1000 already guarantees non-root; setting
# runAsNonRoot=true makes the constraint explicit (kubelet
# will reject the pod if the image's USER somehow goes back
# to root). seccompProfile=RuntimeDefault blocks the
# ~40 syscalls Docker's default seccomp profile blocks
# (clock_settime, modify_ldt, …) — safe for any normal Go
# operator.
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName }}
{{- end}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# NetworkPolicy hardening for the nvca-operator pod itself.
#
# By default, in clusters with a default-deny CNI (Calico, Cilium with
# enforcement, etc.) operator egress is wide open inside the cluster.
# This template adds explicit allow rules for the three legitimate
# egress targets:
#
# 1. kube-apiserver — for CRD watches, leader-election leases,
# child-resource create/update, status writes. CIDR-based rule
# because the apiserver IP is cluster-specific; default
# 0.0.0.0/0 - cluster-internal-CIDRs covers the public apiserver
# endpoint without leaking to other pods.
#
# 2. nvsnap-server (default nvsnap-system/nvsnap-server, port 8080) —
# for L2 promote-state polls and audit calls. Namespace + label
# selector keeps it tight.
#
# 3. kube-dns — for service name resolution. Without this rule the
# operator can't resolve nvsnap-server.nvsnap-system.svc anyway.
#
# Ingress: container port 8000 (health/metrics) and 8002 (auxiliary).
# Allowed from any namespace by default (Prometheus scraping +
# kubelet probes can come from anywhere). Operators can tighten by
# overriding ingressFrom.
#
# Opt-in via .Values.networkPolicy.operator.enabled (default false)
# so existing deployments keep working unchanged. Turn on after
# verifying the rule set fits your cluster.
#
# Note: K8s NetworkPolicy semantics are "deny by default IF any
# NetworkPolicy selects the pod." Creating this policy WITHOUT a
# matching cluster CNI may have no effect (e.g. some kindnet
# configs); test in your environment before relying on it.
{{- if and (hasKey .Values "networkPolicy") (hasKey .Values.networkPolicy "operator") .Values.networkPolicy.operator.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "nvcaop.fullname" . }}-operator-egress
namespace: {{ .Release.Namespace }}
labels:
{{- include "nvcaop.labels" . | nindent 4 }}
annotations:
nvca.io/purpose: |
Default-deny operator egress + explicit allows for
kube-apiserver, nvsnap-server, and kube-dns. Tighten further by
overriding .Values.networkPolicy.operator.* — see the template
header for the rule shape.
spec:
podSelector:
matchLabels:
{{- include "nvcaop.baseSelectorLabels" . | nindent 6 }}
policyTypes:
- Egress
- Ingress
egress:
# 1. Kube-DNS — needed for nvsnap-server.nvsnap-system.svc resolution.
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# 2. nvsnap-server (L2 promote-state, audit log writes).
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ .Values.networkPolicy.operator.nvsnapServerNamespace | default "nvsnap-system" }}
podSelector:
matchLabels:
app: {{ .Values.networkPolicy.operator.nvsnapServerPodLabel | default "nvsnap-server" }}
ports:
- protocol: TCP
port: {{ .Values.networkPolicy.operator.nvsnapServerPort | default 8080 }}
# 3. Kube-apiserver. K8s doesn't expose the apiserver via a
# Service selector inside this NetworkPolicy syntax (kubernetes.default
# isn't a "podSelector" target — it's a virtual endpoint). The cleanest
# portable shape is an IP-based egress to the apiserver port.
# Operators with a known apiserver CIDR can pin it; default 0.0.0.0/0:443
# allows the apiserver but ALSO any other 443 target, which is
# accepted because this rule list is additive (deny-by-default
# elsewhere blocks anything not explicitly allowed).
{{- with .Values.networkPolicy.operator.apiServerCIDRs }}
- to:
{{- range . }}
- ipBlock:
cidr: {{ . | quote }}
{{- end }}
ports:
- protocol: TCP
port: {{ $.Values.networkPolicy.operator.apiServerPort | default 443 }}
{{- end }}
# 4. Operator-defined extra egress rules (private registries,
# OTLP collectors, anything cluster-specific). Render verbatim.
{{- with .Values.networkPolicy.operator.extraEgress }}
{{- toYaml . | nindent 4 }}
{{- end }}
ingress:
# Allow scraping + probes from anywhere by default. Tighten via
# .Values.networkPolicy.operator.ingressFrom (list of NetworkPolicyPeer).
{{- with .Values.networkPolicy.operator.ingressFrom }}
- from:
{{- toYaml . | nindent 8 }}
ports:
- protocol: TCP
port: 8000
- protocol: TCP
port: 8002
{{- else }}
- ports:
- protocol: TCP
port: 8000
- protocol: TCP
port: 8002
{{- end }}
{{- end }}
17 changes: 17 additions & 0 deletions deploy/helm/nvca-operator/nvca-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,23 @@ gracefulShutdown:
networkPolicy:
clusterNetworkCIDRs: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/12"]
customPolicies: []
## @param networkPolicy.operator.enabled Apply a default-deny NetworkPolicy to the operator pod itself, with explicit allows for kube-dns, nvsnap-server, and the kube-apiserver. Opt-in: false by default so existing deployments are unchanged.
## @param networkPolicy.operator.nvsnapServerNamespace Namespace where nvsnap-server runs (egress target for L2 promote-state polls + audit writes)
## @param networkPolicy.operator.nvsnapServerPodLabel app= label value matched against pods in the nvsnap-server namespace
## @param networkPolicy.operator.nvsnapServerPort nvsnap-server REST port
## @param networkPolicy.operator.apiServerCIDRs CIDR list containing the kube-apiserver. Empty = no apiserver allow (operator loses K8s API access). Pin to your cluster's apiserver range.
## @param networkPolicy.operator.apiServerPort kube-apiserver port
## @param networkPolicy.operator.ingressFrom Optional NetworkPolicyPeer list for ingress (Prometheus, kubelet probes). Empty = allow from anywhere.
## @param networkPolicy.operator.extraEgress Additional egress rule entries appended verbatim (private registries, OTLP collector, …)
operator:
enabled: false
nvsnapServerNamespace: "nvsnap-system"
nvsnapServerPodLabel: "nvsnap-server"
nvsnapServerPort: 8080
apiServerCIDRs: []
apiServerPort: 443
ingressFrom: []
extraEgress: []
## @section Cluster Validator Configuration

## @param clusterValidator.enabled Enable the cluster-validator CronJob and init container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ spec:
runAsUser: 1000
runAsGroup: 1010
fsGroup: 1010
# Defense in depth: enforce non-root + seccomp at the pod
# level. runAsUser=1000 already guarantees non-root; setting
# runAsNonRoot=true makes the constraint explicit (kubelet
# will reject the pod if the image's USER somehow goes back
# to root). seccompProfile=RuntimeDefault blocks the
# ~40 syscalls Docker's default seccomp profile blocks
# (clock_settime, modify_ldt, …) — safe for any normal Go
# operator.
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
{{- if .Values.priorityClassName }}
priorityClassName: {{ .Values.priorityClassName }}
{{- end}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# NetworkPolicy hardening for the nvca-operator pod itself.
#
# By default, in clusters with a default-deny CNI (Calico, Cilium with
# enforcement, etc.) operator egress is wide open inside the cluster.
# This template adds explicit allow rules for the three legitimate
# egress targets:
#
# 1. kube-apiserver — for CRD watches, leader-election leases,
# child-resource create/update, status writes. CIDR-based rule
# because the apiserver IP is cluster-specific; default
# 0.0.0.0/0 - cluster-internal-CIDRs covers the public apiserver
# endpoint without leaking to other pods.
#
# 2. nvsnap-server (default nvsnap-system/nvsnap-server, port 8080) —
# for L2 promote-state polls and audit calls. Namespace + label
# selector keeps it tight.
#
# 3. kube-dns — for service name resolution. Without this rule the
# operator can't resolve nvsnap-server.nvsnap-system.svc anyway.
#
# Ingress: container port 8000 (health/metrics) and 8002 (auxiliary).
# Allowed from any namespace by default (Prometheus scraping +
# kubelet probes can come from anywhere). Operators can tighten by
# overriding ingressFrom.
#
# Opt-in via .Values.networkPolicy.operator.enabled (default false)
# so existing deployments keep working unchanged. Turn on after
# verifying the rule set fits your cluster.
#
# Note: K8s NetworkPolicy semantics are "deny by default IF any
# NetworkPolicy selects the pod." Creating this policy WITHOUT a
# matching cluster CNI may have no effect (e.g. some kindnet
# configs); test in your environment before relying on it.
{{- if and (hasKey .Values "networkPolicy") (hasKey .Values.networkPolicy "operator") .Values.networkPolicy.operator.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ include "nvcaop.fullname" . }}-operator-egress
namespace: {{ .Release.Namespace }}
labels:
{{- include "nvcaop.labels" . | nindent 4 }}
annotations:
nvca.io/purpose: |
Default-deny operator egress + explicit allows for
kube-apiserver, nvsnap-server, and kube-dns. Tighten further by
overriding .Values.networkPolicy.operator.* — see the template
header for the rule shape.
spec:
podSelector:
matchLabels:
{{- include "nvcaop.baseSelectorLabels" . | nindent 6 }}
policyTypes:
- Egress
- Ingress
egress:
# 1. Kube-DNS — needed for nvsnap-server.nvsnap-system.svc resolution.
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
# 2. nvsnap-server (L2 promote-state, audit log writes).
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ .Values.networkPolicy.operator.nvsnapServerNamespace | default "nvsnap-system" }}
podSelector:
matchLabels:
app: {{ .Values.networkPolicy.operator.nvsnapServerPodLabel | default "nvsnap-server" }}
ports:
- protocol: TCP
port: {{ .Values.networkPolicy.operator.nvsnapServerPort | default 8080 }}
# 3. Kube-apiserver. K8s doesn't expose the apiserver via a
# Service selector inside this NetworkPolicy syntax (kubernetes.default
# isn't a "podSelector" target — it's a virtual endpoint). The cleanest
# portable shape is an IP-based egress to the apiserver port.
# Operators with a known apiserver CIDR can pin it; default 0.0.0.0/0:443
# allows the apiserver but ALSO any other 443 target, which is
# accepted because this rule list is additive (deny-by-default
# elsewhere blocks anything not explicitly allowed).
{{- with .Values.networkPolicy.operator.apiServerCIDRs }}
- to:
{{- range . }}
- ipBlock:
cidr: {{ . | quote }}
{{- end }}
ports:
- protocol: TCP
port: {{ $.Values.networkPolicy.operator.apiServerPort | default 443 }}
{{- end }}
# 4. Operator-defined extra egress rules (private registries,
# OTLP collectors, anything cluster-specific). Render verbatim.
{{- with .Values.networkPolicy.operator.extraEgress }}
{{- toYaml . | nindent 4 }}
{{- end }}
ingress:
# Allow scraping + probes from anywhere by default. Tighten via
# .Values.networkPolicy.operator.ingressFrom (list of NetworkPolicyPeer).
{{- with .Values.networkPolicy.operator.ingressFrom }}
- from:
{{- toYaml . | nindent 8 }}
ports:
- protocol: TCP
port: 8000
- protocol: TCP
port: 8002
{{- else }}
- ports:
- protocol: TCP
port: 8000
- protocol: TCP
port: 8002
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,24 @@ networkPolicy:
clusterNetworkCIDRs: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "100.64.0.0/12"]
customPolicies: []

## @param networkPolicy.operator.enabled Apply a default-deny NetworkPolicy to the operator pod itself, with explicit allows for kube-dns, nvsnap-server, and the kube-apiserver. Opt-in: false by default so existing deployments are unchanged.
## @param networkPolicy.operator.nvsnapServerNamespace Namespace where nvsnap-server runs (egress target for L2 promote-state polls + audit writes)
## @param networkPolicy.operator.nvsnapServerPodLabel app= label value matched against pods in the nvsnap-server namespace
## @param networkPolicy.operator.nvsnapServerPort nvsnap-server REST port
## @param networkPolicy.operator.apiServerCIDRs CIDR list containing the kube-apiserver. Empty = no apiserver allow (operator loses K8s API access). Pin to your cluster's apiserver range.
## @param networkPolicy.operator.apiServerPort kube-apiserver port
## @param networkPolicy.operator.ingressFrom Optional NetworkPolicyPeer list for ingress (Prometheus, kubelet probes). Empty = allow from anywhere.
## @param networkPolicy.operator.extraEgress Additional egress rule entries appended verbatim (private registries, OTLP collector, …)
operator:
enabled: false
nvsnapServerNamespace: "nvsnap-system"
nvsnapServerPodLabel: "nvsnap-server"
nvsnapServerPort: 8080
apiServerCIDRs: []
apiServerPort: 443
ingressFrom: []
extraEgress: []

## @section Cluster Validator Configuration

## @param clusterValidator.enabled Enable the cluster-validator CronJob and init container
Expand Down
Loading
Loading