DRA e2e: update VAP for a kubelet plugin

This fixes the message (node name and "cluster-scoped" were switched) and
simplifies the VAP:
- a single matchCondition short circuits completely unless they're a user
  we care about
- variables to extract the userNodeName and objectNodeName once
  (using optionals to gracefully turn missing claims and fields into empty strings)
- leaves very tiny concise validations

Co-authored-by: Jordan Liggitt <liggitt@google.com>
This commit is contained in:
Patrick Ohly 2024-07-19 09:27:17 +02:00
parent 9f36c8d718
commit 357a2926a1
2 changed files with 31 additions and 26 deletions

View File

@ -1197,7 +1197,16 @@ var _ = framework.SIGDescribe("node")("DRA", feature.DynamicResourceAllocation,
})
// Messages from test-driver/deploy/example/plugin-permissions.yaml
matchVAPDeniedError := gomega.MatchError(gomega.ContainSubstring("may only modify resourceslices that belong to the node the pod is running on"))
matchVAPDeniedError := func(nodeName string, slice *resourceapi.ResourceSlice) types.GomegaMatcher {
subStr := fmt.Sprintf("this user running on node '%s' may not modify ", nodeName)
switch {
case slice.Spec.NodeName != "":
subStr += fmt.Sprintf("resourceslices on node '%s'", slice.Spec.NodeName)
default:
subStr += "cluster resourceslices"
}
return gomega.MatchError(gomega.ContainSubstring(subStr))
}
mustCreate := func(clientSet kubernetes.Interface, clientName string, slice *resourceapi.ResourceSlice) *resourceapi.ResourceSlice {
ginkgo.GinkgoHelper()
slice, err := clientSet.ResourceV1alpha3().ResourceSlices().Create(ctx, slice, metav1.CreateOptions{})
@ -1237,17 +1246,17 @@ var _ = framework.SIGDescribe("node")("DRA", feature.DynamicResourceAllocation,
}
// Create with different clients, keep it in the end.
mustFailToCreate(realNodeClient, "real plugin", fictionalNodeSlice, matchVAPDeniedError)
mustFailToCreate(realNodeClient, "real plugin", fictionalNodeSlice, matchVAPDeniedError(realNodeName, fictionalNodeSlice))
mustCreateAndDelete(fictionalNodeClient, "fictional plugin", fictionalNodeSlice)
createdFictionalNodeSlice := mustCreate(f.ClientSet, "admin", fictionalNodeSlice)
// Update with different clients.
mustFailToUpdate(realNodeClient, "real plugin", createdFictionalNodeSlice, matchVAPDeniedError)
mustFailToUpdate(realNodeClient, "real plugin", createdFictionalNodeSlice, matchVAPDeniedError(realNodeName, createdFictionalNodeSlice))
createdFictionalNodeSlice = mustUpdate(fictionalNodeClient, "fictional plugin", createdFictionalNodeSlice)
createdFictionalNodeSlice = mustUpdate(f.ClientSet, "admin", createdFictionalNodeSlice)
// Delete with different clients.
mustFailToDelete(realNodeClient, "real plugin", createdFictionalNodeSlice, matchVAPDeniedError)
mustFailToDelete(realNodeClient, "real plugin", createdFictionalNodeSlice, matchVAPDeniedError(realNodeName, createdFictionalNodeSlice))
mustDelete(fictionalNodeClient, "fictional plugin", createdFictionalNodeSlice)
// Now the same for a slice which is not associated with a node.
@ -1272,18 +1281,18 @@ var _ = framework.SIGDescribe("node")("DRA", feature.DynamicResourceAllocation,
})
// Create with different clients, keep it in the end.
mustFailToCreate(realNodeClient, "real plugin", clusterSlice, matchVAPDeniedError)
mustFailToCreate(fictionalNodeClient, "fictional plugin", clusterSlice, matchVAPDeniedError)
mustFailToCreate(realNodeClient, "real plugin", clusterSlice, matchVAPDeniedError(realNodeName, clusterSlice))
mustFailToCreate(fictionalNodeClient, "fictional plugin", clusterSlice, matchVAPDeniedError(fictionalNodeName, clusterSlice))
createdClusterSlice := mustCreate(f.ClientSet, "admin", clusterSlice)
// Update with different clients.
mustFailToUpdate(realNodeClient, "real plugin", createdClusterSlice, matchVAPDeniedError)
mustFailToUpdate(fictionalNodeClient, "fictional plugin", createdClusterSlice, matchVAPDeniedError)
mustFailToUpdate(realNodeClient, "real plugin", createdClusterSlice, matchVAPDeniedError(realNodeName, createdClusterSlice))
mustFailToUpdate(fictionalNodeClient, "fictional plugin", createdClusterSlice, matchVAPDeniedError(fictionalNodeName, createdClusterSlice))
createdClusterSlice = mustUpdate(f.ClientSet, "admin", createdClusterSlice)
// Delete with different clients.
mustFailToDelete(realNodeClient, "real plugin", createdClusterSlice, matchVAPDeniedError)
mustFailToDelete(fictionalNodeClient, "fictional plugin", createdClusterSlice, matchVAPDeniedError)
mustFailToDelete(realNodeClient, "real plugin", createdClusterSlice, matchVAPDeniedError(realNodeName, createdClusterSlice))
mustFailToDelete(fictionalNodeClient, "fictional plugin", createdClusterSlice, matchVAPDeniedError(fictionalNodeName, createdClusterSlice))
mustDelete(f.ClientSet, "admin", createdClusterSlice)
})

View File

@ -50,29 +50,25 @@ spec:
apiVersions: ["v1alpha3"]
operations: ["CREATE", "UPDATE", "DELETE"]
resources: ["resourceslices"]
variables:
- name: hasNodeName
expression: >-
"authentication.kubernetes.io/node-name" in request.userInfo.extra
- name: isKubeletPlugin
matchConditions:
- name: isRestrictedUser
expression: >-
request.userInfo.username == "system:serviceaccount:dra-kubelet-plugin-namespace:dra-kubelet-plugin-service-account"
variables:
- name: userNodeName
expression: >-
request.userInfo.extra[?'authentication.kubernetes.io/node-name'][0].orValue('')
- name: objectNodeName
expression: >-
(request.operation == "DELETE" ? oldObject : object).spec.?nodeName.orValue("")
validations:
- expression: >-
!variables.isKubeletPlugin || variables.hasNodeName
message: This user must have a "authentication.kubernetes.io/node-name" claim. ServiceAccountTokenNodeBindingValidation must be enabled in the cluster.
- expression: >-
!variables.isKubeletPlugin || !variables.hasNodeName ||
variables.objectNodeName == request.userInfo.extra["authentication.kubernetes.io/node-name"][0]
message: This DRA kubelet plugin may only modify resourceslices that belong to the node the pod is running on.
# This is useful for debugging. Can be dropped in a production deployment.
- expression: variables.userNodeName != ""
message: >-
no node association found for user, this user must run in a pod on a node and ServiceAccountTokenPodNodeInfo must be enabled
- expression: variables.userNodeName == variables.objectNodeName
messageExpression: >-
"The DRA kubelet plugin on node " + request.userInfo.extra["authentication.kubernetes.io/node-name"][0] +
" may only modify resourceslices that belong to the node the pod is running on, not " +
(variables.objectNodeName == "" ? variables.objectNodeName : "a cluster-scoped slice") + "."
"this user running on node '"+variables.userNodeName+"' may not modify " +
(variables.objectNodeName == "" ?"cluster resourceslices" : "resourceslices on node '"+variables.objectNodeName+"'")
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding