Skip to content

Commit cac534c

Browse files
committed
[SPARK-57934] Support opt-in NetworkPolicy for operator pod in Helm chart
### What changes were proposed in this pull request? This PR aims to support an opt-in `NetworkPolicy` for the operator pod in the Helm chart. When `operatorDeployment.networkPolicy.enable` is `true` (default: `false`), all ingress to the operator pod is denied except the health probe port (open to any source, for kubelet probes) and the metrics port, which is reachable only from the peers listed in `operatorDeployment.networkPolicy.metricsIngress`. ```yaml operatorDeployment: networkPolicy: enable: true metricsIngress: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: "monitoring" ``` ### Why are the changes needed? The operator serves unauthenticated plain-HTTP probe and metrics endpoints, and the chart provided no way to restrict in-cluster access to them. ### Does this PR introduce _any_ user-facing change? No, because this is disabled by default. ### How was this patch tested? Pass the CIs. ### Was this patch authored or co-authored using generative AI tooling? Generated-by: Claude Fable 5 Closes #742 from dongjoon-hyun/SPARK-57934. Authored-by: Dongjoon Hyun <dongjoon@apache.org> Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
1 parent 17410b0 commit cac534c

7 files changed

Lines changed: 223 additions & 0 deletions

File tree

.github/workflows/build_and_test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ jobs:
233233
- "1.36.0"
234234
test-group:
235235
- configmap-metadata
236+
- network-policy
236237
steps:
237238
- name: Checkout repository
238239
uses: actions/checkout@v6
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
{{- if .Values.operatorDeployment.networkPolicy.enable }}
17+
---
18+
apiVersion: networking.k8s.io/v1
19+
kind: NetworkPolicy
20+
metadata:
21+
name: {{ include "spark-operator.name" . }}
22+
namespace: {{ .Release.Namespace }}
23+
labels:
24+
{{- include "spark-operator.commonLabels" . | nindent 4 }}
25+
spec:
26+
podSelector:
27+
matchLabels:
28+
{{- include "spark-operator.deploymentSelectorLabels" . | nindent 6 }}
29+
policyTypes:
30+
- Ingress
31+
ingress:
32+
# Health probe port must stay reachable by the kubelet, whose traffic
33+
# originates from the node and cannot be selected by pod/namespace selectors.
34+
- ports:
35+
- port: {{ include "spark-operator.probePort" . }}
36+
protocol: TCP
37+
{{- with .Values.operatorDeployment.networkPolicy.metricsIngress }}
38+
- from:
39+
{{- toYaml . | nindent 8 }}
40+
ports:
41+
- port: {{ include "spark-operator.metricsPort" $ }}
42+
protocol: TCP
43+
{{- end }}
44+
{{- end }}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
{{- if .Values.operatorDeployment.networkPolicy.enable }}
17+
apiVersion: v1
18+
kind: Pod
19+
metadata:
20+
name: "{{ include "spark-operator.name" . }}-test-network-policy"
21+
namespace: {{ .Release.Namespace }}
22+
labels:
23+
{{- include "spark-operator.commonLabels" . | nindent 4 }}
24+
annotations:
25+
"helm.sh/hook": test
26+
"helm.sh/hook-delete-policy": before-hook-creation
27+
spec:
28+
containers:
29+
- name: kubectl
30+
image: bitnamisecure/kubectl:latest
31+
command: ['bash', '-c']
32+
args:
33+
- |
34+
set -e
35+
echo "Testing operator NetworkPolicy..."
36+
37+
NP_JSON=$(kubectl get networkpolicy {{ include "spark-operator.name" . }} -n {{ .Release.Namespace }} -o json)
38+
39+
echo "Validating policyTypes..."
40+
POLICY_TYPES=$(echo "$NP_JSON" | jq -r '.spec.policyTypes | join(",")')
41+
if [ "$POLICY_TYPES" != "Ingress" ]; then
42+
echo "ERROR: Expected policyTypes [Ingress], Got: $POLICY_TYPES"
43+
exit 1
44+
fi
45+
46+
echo "Validating podSelector..."
47+
SELECTOR_NAME=$(echo "$NP_JSON" | jq -r '.spec.podSelector.matchLabels["app.kubernetes.io/name"]')
48+
SELECTOR_COMPONENT=$(echo "$NP_JSON" | jq -r '.spec.podSelector.matchLabels["app.kubernetes.io/component"]')
49+
if [ "$SELECTOR_NAME" != "{{ include "spark-operator.name" . }}" ] || [ "$SELECTOR_COMPONENT" != "operator-deployment" ]; then
50+
echo "ERROR: podSelector does not match operator deployment labels. Got: $SELECTOR_NAME/$SELECTOR_COMPONENT"
51+
exit 1
52+
fi
53+
54+
echo "Validating probe port ingress rule..."
55+
PROBE_PORT=$(echo "$NP_JSON" | jq -r '[.spec.ingress[] | select(has("from") | not)][0].ports[0].port')
56+
if [ "$PROBE_PORT" != "{{ include "spark-operator.probePort" . }}" ]; then
57+
echo "ERROR: Expected probe port {{ include "spark-operator.probePort" . }} open to all sources, Got: $PROBE_PORT"
58+
exit 1
59+
fi
60+
61+
{{- if .Values.operatorDeployment.networkPolicy.metricsIngress }}
62+
echo "Validating metrics port ingress rule..."
63+
METRICS_PORT=$(echo "$NP_JSON" | jq -r '[.spec.ingress[] | select(has("from"))][0].ports[0].port')
64+
if [ "$METRICS_PORT" != "{{ include "spark-operator.metricsPort" . }}" ]; then
65+
echo "ERROR: Expected metrics port {{ include "spark-operator.metricsPort" . }} in the restricted rule, Got: $METRICS_PORT"
66+
exit 1
67+
fi
68+
FROM_MATCHES=$(echo "$NP_JSON" | jq --argjson expected '{{ toJson .Values.operatorDeployment.networkPolicy.metricsIngress }}' '[.spec.ingress[] | select(has("from"))][0].from == $expected')
69+
if [ "$FROM_MATCHES" != "true" ]; then
70+
echo "ERROR: Metrics ingress sources do not match values. Got: $(echo "$NP_JSON" | jq -c '[.spec.ingress[] | select(has("from"))][0].from')"
71+
exit 1
72+
fi
73+
{{- else }}
74+
echo "Validating that the metrics port is denied..."
75+
RESTRICTED_RULES=$(echo "$NP_JSON" | jq '[.spec.ingress[] | select(has("from"))] | length')
76+
if [ "$RESTRICTED_RULES" != "0" ]; then
77+
echo "ERROR: Expected no ingress rule with sources when metricsIngress is empty, Got: $RESTRICTED_RULES"
78+
exit 1
79+
fi
80+
{{- end }}
81+
82+
echo "All NetworkPolicy tests passed!"
83+
restartPolicy: Never
84+
serviceAccountName: {{ .Values.operatorRbac.serviceAccount.name }}
85+
{{- end }}

build-tools/helm/spark-kubernetes-operator/values.schema.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,27 @@
391391
"additionalProperties": true
392392
}
393393
}
394+
},
395+
"networkPolicy": {
396+
"type": "object",
397+
"description": "NetworkPolicy for the operator pod",
398+
"required": [
399+
"enable"
400+
],
401+
"properties": {
402+
"enable": {
403+
"type": "boolean",
404+
"description": "Whether to create a NetworkPolicy for the operator pod"
405+
},
406+
"metricsIngress": {
407+
"type": ["array", "null"],
408+
"description": "List of NetworkPolicyPeer(s) allowed to reach the metrics port; when empty, ingress to the metrics port is denied",
409+
"items": {
410+
"type": "object",
411+
"additionalProperties": true
412+
}
413+
}
414+
}
394415
}
395416
}
396417
},

build-tools/helm/spark-kubernetes-operator/values.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,19 @@ operatorDeployment:
8787
securityContext: { }
8888
dnsPolicy:
8989
dnsConfig:
90+
# When enabled, a NetworkPolicy is created for the operator pod that allows ingress only to
91+
# the health probe port (from any source, for kubelet probes) and to the metrics port from
92+
# the sources listed in {networkPolicy.metricsIngress}. All other ingress traffic to the
93+
# operator pod is denied. Note that this requires a CNI plugin with NetworkPolicy support,
94+
# and that egress traffic is not restricted.
95+
networkPolicy:
96+
enable: false
97+
# List of NetworkPolicyPeer(s) allowed to reach the metrics port, e.g. the Prometheus
98+
# scraper. When empty, ingress to the metrics port is denied.
99+
metricsIngress: [ ]
100+
# - namespaceSelector:
101+
# matchLabels:
102+
# kubernetes.io/metadata.name: "monitoring"
90103

91104
operatorRbac:
92105
serviceAccount:

docs/operations.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,37 @@ __Notice__: The pod resources should be set as your workload in different enviro
132132
achieve a matched K8s pod QoS. See
133133
also [Pod Quality of Service Classes](https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#quality-of-service-classes).
134134

135+
## Restricting Network Access to the Operator
136+
137+
The operator serves two plain-HTTP endpoints without authentication: the health probes
138+
(port 19091 by default) and the Prometheus metrics (port 19090 by default). To restrict
139+
in-cluster access to them, the chart can create a
140+
[NetworkPolicy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)
141+
for the operator pod:
142+
143+
```yaml
144+
operatorDeployment:
145+
networkPolicy:
146+
enable: true
147+
metricsIngress:
148+
- namespaceSelector:
149+
matchLabels:
150+
kubernetes.io/metadata.name: "monitoring"
151+
```
152+
153+
When enabled, all ingress traffic to the operator pod is denied except:
154+
155+
- the health probe port, reachable from any source — kubelet probe traffic originates from
156+
the node and cannot be matched by pod or namespace selectors, and
157+
- the metrics port, reachable only from the `NetworkPolicyPeer`(s) listed in
158+
`metricsIngress`. When the list is empty, ingress to the metrics port is denied, so make
159+
sure your metrics scraper is listed before enabling this in an environment that collects
160+
operator metrics.
161+
162+
Note that this requires a CNI plugin that enforces NetworkPolicy; on clusters without such
163+
a plugin the policy is silently ignored. Egress traffic of the operator (Kubernetes API
164+
server, DNS) is not restricted by this policy.
165+
135166
## Operator Health(Liveness) Probe with Sentinel Resource
136167

137168
Learning
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one or more
2+
# contributor license agreements. See the NOTICE file distributed with
3+
# this work for additional information regarding copyright ownership.
4+
# The ASF licenses this file to You under the Apache License, Version 2.0
5+
# (the "License"); you may not use this file except in compliance with
6+
# the License. You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
# Test values for validating the operator NetworkPolicy feature
17+
#
18+
# Usage:
19+
# helm install spark-operator . -f tests/e2e/helm/helm-test-values/network-policy/values.yaml
20+
# helm test spark-operator
21+
22+
operatorDeployment:
23+
networkPolicy:
24+
enable: true
25+
metricsIngress:
26+
- namespaceSelector:
27+
matchLabels:
28+
kubernetes.io/metadata.name: "monitoring"

0 commit comments

Comments
 (0)