Skip to content

Commit 140a1fa

Browse files
Merge pull request #388 from racheljpg/vap-2996
OCPCLOUD-2996: Prevent changing .spec.authoritativeAPI on MAPI when Phase Provisioning or is being deleted
2 parents aa6f9a1 + 2f88977 commit 140a1fa

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

manifests/0000_30_cluster-api_09_admission-policies.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,40 @@ data:
236236
policyName: openshift-only-create-mapi-machine-if-authoritative-api-capi
237237
validationActions:
238238
- Deny
239+
---
240+
apiVersion: admissionregistration.k8s.io/v1
241+
kind: ValidatingAdmissionPolicy
242+
metadata:
243+
name: openshift-prevent-migration-when-machine-updating
244+
spec:
245+
failurePolicy: Fail
246+
247+
matchConstraints:
248+
resourceRules:
249+
- apiGroups: ["machine.openshift.io"]
250+
apiVersions: ["*"]
251+
operations: ["UPDATE"]
252+
resources: ["machines"]
253+
254+
# All validations must evaluate to true
255+
validations:
256+
- expression: '!(has(object.status) && has(object.status.phase) && object.status.phase == "Provisioning" && (oldObject.spec.authoritativeAPI != object.spec.authoritativeAPI))'
257+
message: 'Cannot update .spec.authoritativeAPI when machine is in Provisioning phase'
258+
- expression: '!(has(object.metadata.deletionTimestamp) && (oldObject.spec.authoritativeAPI != object.spec.authoritativeAPI))'
259+
message: 'Cannot update .spec.authoritativeAPI when machine has a non-zero deletion timestamp'
260+
---
261+
apiVersion: admissionregistration.k8s.io/v1
262+
kind: ValidatingAdmissionPolicyBinding
263+
metadata:
264+
name: openshift-prevent-migration-when-machine-updating
265+
spec:
266+
matchResources:
267+
namespaceSelector:
268+
matchLabels:
269+
kubernetes.io/metadata.name: openshift-machine-api
270+
policyName: openshift-prevent-migration-when-machine-updating
271+
validationActions:
272+
- Deny
239273
---
240274
apiVersion: v1
241275
kind: ConfigMap

pkg/controllers/machinesync/machine_sync_controller_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,6 +1253,117 @@ var _ = Describe("With a running MachineSync Reconciler", func() {
12531253

12541254
})
12551255

1256+
Context("Prevent updates to MAPI machine if migrating would be unpredictable", func() {
1257+
BeforeEach(func() {
1258+
By("Waiting for VAP to be ready")
1259+
machineVap = &admissionregistrationv1.ValidatingAdmissionPolicy{}
1260+
Eventually(k8sClient.Get(ctx, client.ObjectKey{Name: "openshift-prevent-migration-when-machine-updating"}, machineVap), timeout).Should(Succeed())
1261+
Eventually(k.Update(machineVap, func() {
1262+
machineVap.Spec.Validations = append(machineVap.Spec.Validations, admissionregistrationv1.Validation{
1263+
Expression: "!(has(object.metadata.labels) && \"test-sentinel\" in object.metadata.labels)",
1264+
Message: "policy in place",
1265+
})
1266+
})).Should(Succeed())
1267+
1268+
Eventually(k.Object(machineVap), timeout).Should(
1269+
HaveField("Status.ObservedGeneration", BeNumerically(">=", 2)),
1270+
)
1271+
1272+
By("Updating the VAP binding")
1273+
policyBinding = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{}
1274+
Eventually(k8sClient.Get(ctx, client.ObjectKey{
1275+
Name: "openshift-prevent-migration-when-machine-updating"}, policyBinding), timeout).Should(Succeed())
1276+
1277+
Eventually(k.Update(policyBinding, func() {
1278+
policyBinding.Spec.MatchResources.NamespaceSelector.MatchLabels = map[string]string{
1279+
"kubernetes.io/metadata.name": mapiNamespace.GetName(),
1280+
}
1281+
}), timeout).Should(Succeed())
1282+
1283+
// Wait until the binding shows the patched values
1284+
Eventually(k.Object(policyBinding), timeout).Should(
1285+
SatisfyAll(
1286+
HaveField("Spec.MatchResources.NamespaceSelector.MatchLabels",
1287+
HaveKeyWithValue("kubernetes.io/metadata.name",
1288+
mapiNamespace.GetName())),
1289+
),
1290+
)
1291+
1292+
By("Creating a throwaway MAPI machine")
1293+
sentinelMachine := mapiMachineBuilder.WithName("sentinel-machine").WithAuthoritativeAPI(mapiv1beta1.MachineAuthorityClusterAPI).Build()
1294+
Eventually(k8sClient.Create(ctx, sentinelMachine), timeout).Should(Succeed())
1295+
1296+
capiSentinelMachine := clusterv1resourcebuilder.Machine().WithName("sentinel-machine").WithNamespace(capiNamespace.Name).Build()
1297+
Expect(k8sClient.Create(ctx, capiSentinelMachine)).To(Succeed())
1298+
1299+
Eventually(k.Get(capiSentinelMachine)).Should(Succeed())
1300+
1301+
Eventually(k.Update(sentinelMachine, func() {
1302+
sentinelMachine.ObjectMeta.Labels = map[string]string{"test-sentinel": "fubar"}
1303+
}), timeout).Should(MatchError(ContainSubstring("policy in place")))
1304+
})
1305+
1306+
It("denies updating the AuthoritativeAPI when the machine is in Provisioning", func() {
1307+
By("Updating the MAPI machine phase to be provisioning")
1308+
Eventually(k.UpdateStatus(mapiMachine, func() {
1309+
provisioningPhase := mapiv1beta1.PhaseProvisioning
1310+
mapiMachine.Status.Phase = &provisioningPhase
1311+
})).Should(Succeed())
1312+
1313+
By("Attempting to update the authoritativeAPI should be blocked")
1314+
Eventually(k.Update(mapiMachine, func() {
1315+
mapiMachine.Spec.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI
1316+
}), timeout).Should(MatchError(ContainSubstring("Cannot update .spec.authoritativeAPI when machine is in Provisioning phase")))
1317+
})
1318+
1319+
It("denies updating the AuthoritativeAPI when the machine has a non-zero deletion timestamp", func() {
1320+
By("Adding a finalizer to prevent actual deletion")
1321+
Eventually(k.Update(mapiMachine, func() {
1322+
mapiMachine.Finalizers = append(mapiMachine.Finalizers, "test-finalizer")
1323+
})).Should(Succeed())
1324+
1325+
By("Deleting the MAPI machine to set deletion timestamp")
1326+
Eventually(k8sClient.Delete(ctx, mapiMachine)).Should(Succeed())
1327+
1328+
By("Waiting for deletion timestamp to be set")
1329+
Eventually(k.Object(mapiMachine)).Should(SatisfyAll(
1330+
HaveField("DeletionTimestamp", Not(BeNil())),
1331+
))
1332+
1333+
By("Attempting to update the authoritativeAPI should be blocked")
1334+
Eventually(k.Update(mapiMachine, func() {
1335+
mapiMachine.Spec.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI
1336+
}), timeout).Should(MatchError(ContainSubstring("Cannot update .spec.authoritativeAPI when machine has a non-zero deletion timestamp")))
1337+
})
1338+
1339+
It("allows updating the AuthoritativeAPI when the machine is in Running phase", func() {
1340+
By("Updating the MAPI machine phase to be running")
1341+
Eventually(k.UpdateStatus(mapiMachine, func() {
1342+
runningPhase := mapiv1beta1.PhaseRunning
1343+
mapiMachine.Status.Phase = &runningPhase
1344+
})).Should(Succeed())
1345+
1346+
By("Attempting to update the authoritativeAPI should succeed")
1347+
Eventually(k.Update(mapiMachine, func() {
1348+
mapiMachine.Spec.AuthoritativeAPI = mapiv1beta1.MachineAuthorityClusterAPI
1349+
}), timeout).Should(Succeed())
1350+
})
1351+
1352+
It("allows updating labels when the machine is in Provisioning phase but not changing AuthoritativeAPI", func() {
1353+
By("Updating the MAPI machine phase to be provisioning")
1354+
Eventually(k.UpdateStatus(mapiMachine, func() {
1355+
provisioningPhase := mapiv1beta1.PhaseProvisioning
1356+
mapiMachine.Status.Phase = &provisioningPhase
1357+
})).Should(Succeed())
1358+
1359+
By("Attempting to update labels should succeed")
1360+
Eventually(k.Update(mapiMachine, func() {
1361+
mapiMachine.ObjectMeta.Labels = map[string]string{"test-label": "fubar"}
1362+
}), timeout).Should(Succeed())
1363+
})
1364+
1365+
})
1366+
12561367
})
12571368
})
12581369

0 commit comments

Comments
 (0)