Skip to content

Commit 4a54413

Browse files
committed
Prevent changes to default-network pod annotation
Adds a ValidatingAdmissionPolicy to make the 'v1.multus-cni.io/default-network' annotation immutable after a pod is created. Signed-off-by: Patryk Diak <[email protected]>
1 parent a54f168 commit 4a54413

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

dist/images/daemonset.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,7 @@ ovn_route_advertisements_enable=${ovn_route_advertisements_enable} \
10751075
ovn_network_segmentation_enable=${ovn_network_segmentation_enable} \
10761076
ovn_enable_dnsnameresolver=${ovn_enable_dnsnameresolver} \
10771077
ovn_route_advertisements_enable=${ovn_route_advertisements_enable} \
1078+
ovn_pre_conf_udn_addr_enable=${ovn_pre_conf_udn_addr_enable} \
10781079
jinjanate ../templates/rbac-ovnkube-master.yaml.j2 -o ${output_dir}/rbac-ovnkube-master.yaml
10791080

10801081
cp ../templates/rbac-ovnkube-identity.yaml.j2 ${output_dir}/rbac-ovnkube-identity.yaml

dist/templates/rbac-ovnkube-master.yaml.j2

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,42 @@ spec:
191191
apiVersions: ["v1"]
192192
operations: ["UPDATE"]
193193
resources: ["namespaces"]
194+
195+
{% if ovn_pre_conf_udn_addr_enable == "true" -%}
196+
---
197+
apiVersion: admissionregistration.k8s.io/v1
198+
kind: ValidatingAdmissionPolicy
199+
metadata:
200+
name: default-network-annotation
201+
spec:
202+
matchConstraints:
203+
resourceRules:
204+
- apiGroups: [""]
205+
apiVersions: ["v1"]
206+
operations: ["UPDATE"]
207+
resources: ["pods"]
208+
failurePolicy: Fail
209+
validations:
210+
# Prevent any changes to the default-network annotation after pod creation:
211+
# - If annotation exists in old pod: new pod must have same annotation with identical value
212+
# - If annotation doesn't exist in old pod: new pod must also not have it
213+
- expression: >
214+
('v1.multus-cni.io/default-network' in oldObject.metadata.annotations)
215+
? ('v1.multus-cni.io/default-network' in object.metadata.annotations) && oldObject.metadata.annotations['v1.multus-cni.io/default-network'] == object.metadata.annotations['v1.multus-cni.io/default-network']
216+
: !('v1.multus-cni.io/default-network' in object.metadata.annotations)
217+
message: "The 'v1.multus-cni.io/default-network' annotation cannot be changed after the pod was created"
218+
---
219+
apiVersion: admissionregistration.k8s.io/v1
220+
kind: ValidatingAdmissionPolicyBinding
221+
metadata:
222+
name: default-network-annotation-binding
223+
spec:
224+
policyName: default-network-annotation
225+
validationActions: [Deny]
226+
matchResources:
227+
resourceRules:
228+
- apiGroups: [""]
229+
apiVersions: ["v1"]
230+
operations: ["UPDATE"]
231+
resources: ["pods"]
232+
{%- endif %}

test/e2e/network_segmentation_default_network_annotation.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,92 @@ var _ = Describe("Network Segmentation: Default network multus annotation", func
103103
mac: "02:A1:B2:C3:D4:E5",
104104
}),
105105
)
106+
107+
Context("ValidatingAdmissionPolicy protection", func() {
108+
It("should prevent adding, modifying and removing the default-network annotation on existing pods", func() {
109+
if !isPreConfiguredUdnAddressesEnabled() {
110+
Skip("ENABLE_PRE_CONF_UDN_ADDR not configured")
111+
}
112+
113+
namespace, err := f.CreateNamespace(context.TODO(), f.BaseName, map[string]string{
114+
"e2e-framework": f.BaseName,
115+
RequiredUDNNamespaceLabel: "",
116+
})
117+
Expect(err).NotTo(HaveOccurred(), "Should create namespace for test")
118+
f.Namespace = namespace
119+
120+
udnClient, err := udnclientset.NewForConfig(f.ClientConfig())
121+
Expect(err).NotTo(HaveOccurred(), "Should create UDN client")
122+
123+
// Create a UserDefinedNetwork for the test
124+
udn := &udnv1.UserDefinedNetwork{
125+
ObjectMeta: metav1.ObjectMeta{
126+
Name: "test-network",
127+
Namespace: f.Namespace.Name,
128+
},
129+
Spec: udnv1.UserDefinedNetworkSpec{
130+
Topology: udnv1.NetworkTopologyLayer2,
131+
Layer2: &udnv1.Layer2Config{
132+
Role: udnv1.NetworkRolePrimary,
133+
Subnets: filterDualStackCIDRs(f.ClientSet, []udnv1.CIDR{
134+
"103.0.0.0/16",
135+
"2014:100:200::0/60",
136+
}),
137+
},
138+
},
139+
}
140+
141+
By("Creating a UserDefinedNetwork")
142+
udn, err = udnClient.K8sV1().UserDefinedNetworks(f.Namespace.Name).Create(context.TODO(), udn, metav1.CreateOptions{})
143+
Expect(err).NotTo(HaveOccurred(), "Should create UserDefinedNetwork")
144+
Eventually(userDefinedNetworkReadyFunc(f.DynamicClient, udn.Namespace, udn.Name), 5*time.Second, time.Second).Should(Succeed())
145+
146+
By("Creating a pod without the default-network annotation")
147+
podWithoutAnnotation := e2epod.NewAgnhostPod(f.Namespace.Name, "pod-without-annotation", nil, nil, nil)
148+
podWithoutAnnotation.Spec.Containers[0].Command = []string{"sleep", "infinity"}
149+
podWithoutAnnotation = e2epod.NewPodClient(f).CreateSync(context.TODO(), podWithoutAnnotation)
150+
151+
By("Creating a pod with the default-network annotation")
152+
153+
nse := []nadapi.NetworkSelectionElement{{
154+
Name: "default",
155+
Namespace: "ovn-kubernetes",
156+
IPRequest: []string{"103.0.0.3/16", "2014:100:200::3/60"},
157+
MacRequest: "02:A1:B2:C3:D4:E5",
158+
}}
159+
marshalledNSE, err := json.Marshal(nse)
160+
Expect(err).NotTo(HaveOccurred(), "Should marshal network selection element")
161+
162+
podWithAnnotation := e2epod.NewAgnhostPod(f.Namespace.Name, "pod-with-annotation", nil, nil, nil)
163+
podWithAnnotation.Annotations = map[string]string{
164+
"v1.multus-cni.io/default-network": string(marshalledNSE),
165+
}
166+
podWithAnnotation.Spec.Containers[0].Command = []string{"sleep", "infinity"}
167+
podWithAnnotation = e2epod.NewPodClient(f).CreateSync(context.TODO(), podWithAnnotation)
168+
169+
By("Attempting to add the default-network annotation to the pod without annotation")
170+
podWithoutAnnotation.Annotations = map[string]string{
171+
"v1.multus-cni.io/default-network": string(marshalledNSE),
172+
}
173+
174+
_, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Update(context.TODO(), podWithoutAnnotation, metav1.UpdateOptions{})
175+
Expect(err).To(HaveOccurred(), "Should fail to add default-network annotation to existing pod")
176+
Expect(err).To(MatchError(ContainSubstring("The 'v1.multus-cni.io/default-network' annotation cannot be changed after the pod was created")))
177+
178+
By("Attempting to modify the default-network annotation from the pod with annotation")
179+
updatedPodWithAnnotation := podWithAnnotation.DeepCopy()
180+
updatedPodWithAnnotation.Annotations["v1.multus-cni.io/default-network"] = `[{}]`
181+
182+
_, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Update(context.TODO(), updatedPodWithAnnotation, metav1.UpdateOptions{})
183+
Expect(err).To(HaveOccurred(), "Should fail to modify default-network annotation from existing pod")
184+
Expect(err).To(MatchError(ContainSubstring("The 'v1.multus-cni.io/default-network' annotation cannot be changed after the pod was created")))
185+
186+
By("Attempting to remove the default-network annotation from the pod with annotation")
187+
delete(podWithAnnotation.Annotations, "v1.multus-cni.io/default-network")
188+
189+
_, err = f.ClientSet.CoreV1().Pods(f.Namespace.Name).Update(context.TODO(), podWithAnnotation, metav1.UpdateOptions{})
190+
Expect(err).To(HaveOccurred(), "Should fail to remove default-network annotation from existing pod")
191+
Expect(err).To(MatchError(ContainSubstring("The 'v1.multus-cni.io/default-network' annotation cannot be changed after the pod was created")))
192+
})
193+
})
106194
})

0 commit comments

Comments
 (0)