Merge pull request #116254 from pohly/dra-node-authorizer
node authorizer: limit kubelet access to ResourceClaim objects
This commit is contained in:
		| @@ -288,6 +288,9 @@ func (p *Plugin) admitPodStatus(nodeName string, a admission.Attributes) error { | ||||
| 		if !labels.Equals(oldPod.Labels, newPod.Labels) { | ||||
| 			return admission.NewForbidden(a, fmt.Errorf("node %q cannot update labels through pod status", nodeName)) | ||||
| 		} | ||||
| 		if !resourceClaimStatusesEqual(oldPod.Status.ResourceClaimStatuses, newPod.Status.ResourceClaimStatuses) { | ||||
| 			return admission.NewForbidden(a, fmt.Errorf("node %q cannot update resource claim statues", nodeName)) | ||||
| 		} | ||||
| 		return nil | ||||
|  | ||||
| 	default: | ||||
| @@ -295,6 +298,29 @@ func (p *Plugin) admitPodStatus(nodeName string, a admission.Attributes) error { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func resourceClaimStatusesEqual(statusA, statusB []api.PodResourceClaimStatus) bool { | ||||
| 	if len(statusA) != len(statusB) { | ||||
| 		return false | ||||
| 	} | ||||
| 	// In most cases, status entries only get added once and not modified. | ||||
| 	// But this cannot be guaranteed, so for the sake of correctness in all | ||||
| 	// cases this code here has to check. | ||||
| 	for i := range statusA { | ||||
| 		if statusA[i].Name != statusB[i].Name { | ||||
| 			return false | ||||
| 		} | ||||
| 		claimNameA := statusA[i].ResourceClaimName | ||||
| 		claimNameB := statusB[i].ResourceClaimName | ||||
| 		if (claimNameA == nil) != (claimNameB == nil) { | ||||
| 			return false | ||||
| 		} | ||||
| 		if claimNameA != nil && *claimNameA != *claimNameB { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // admitPodEviction allows to evict a pod if it is assigned to the current node. | ||||
| func (p *Plugin) admitPodEviction(nodeName string, a admission.Attributes) error { | ||||
| 	switch a.GetOperation() { | ||||
|   | ||||
| @@ -22,6 +22,7 @@ import ( | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/component-helpers/storage/ephemeral" | ||||
| 	"k8s.io/dynamic-resource-allocation/resourceclaim" | ||||
| 	pvutil "k8s.io/kubernetes/pkg/api/v1/persistentvolume" | ||||
| 	podutil "k8s.io/kubernetes/pkg/api/v1/pod" | ||||
| 	"k8s.io/kubernetes/third_party/forked/gonum/graph" | ||||
| @@ -117,6 +118,7 @@ const ( | ||||
| 	podVertexType | ||||
| 	pvcVertexType | ||||
| 	pvVertexType | ||||
| 	resourceClaimVertexType | ||||
| 	secretVertexType | ||||
| 	vaVertexType | ||||
| 	serviceAccountVertexType | ||||
| @@ -128,6 +130,7 @@ var vertexTypes = map[vertexType]string{ | ||||
| 	podVertexType:            "pod", | ||||
| 	pvcVertexType:            "pvc", | ||||
| 	pvVertexType:             "pv", | ||||
| 	resourceClaimVertexType:  "resourceclaim", | ||||
| 	secretVertexType:         "secret", | ||||
| 	vaVertexType:             "volumeattachment", | ||||
| 	serviceAccountVertexType: "serviceAccount", | ||||
| @@ -393,6 +396,20 @@ func (g *Graph) AddPod(pod *corev1.Pod) { | ||||
| 			g.addEdgeToDestinationIndex_locked(e) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for _, podResourceClaim := range pod.Spec.ResourceClaims { | ||||
| 		claimName, _, err := resourceclaim.Name(pod, &podResourceClaim) | ||||
| 		// Do we have a valid claim name? If yes, add an edge that grants | ||||
| 		// kubelet access to that claim. An error indicates that a claim | ||||
| 		// still needs to be created, nil that intentionally no claim | ||||
| 		// was created and never will be because it isn't needed. | ||||
| 		if err == nil && claimName != nil { | ||||
| 			claimVertex := g.getOrCreateVertex_locked(resourceClaimVertexType, pod.Namespace, *claimName) | ||||
| 			e := newDestinationEdge(claimVertex, podVertex, nodeVertex) | ||||
| 			g.graph.SetEdge(e) | ||||
| 			g.addEdgeToDestinationIndex_locked(e) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| func (g *Graph) DeletePod(name, namespace string) { | ||||
| 	start := time.Now() | ||||
|   | ||||
| @@ -78,8 +78,9 @@ func (g *graphPopulator) updatePod(oldObj, obj interface{}) { | ||||
| 		return | ||||
| 	} | ||||
| 	if oldPod, ok := oldObj.(*corev1.Pod); ok && oldPod != nil { | ||||
| 		if (pod.Spec.NodeName == oldPod.Spec.NodeName) && (pod.UID == oldPod.UID) { | ||||
| 			// Node and uid are unchanged, all object references in the pod spec are immutable | ||||
| 		if (pod.Spec.NodeName == oldPod.Spec.NodeName) && (pod.UID == oldPod.UID) && | ||||
| 			resourceClaimStatusesEqual(oldPod.Status.ResourceClaimStatuses, pod.Status.ResourceClaimStatuses) { | ||||
| 			// Node and uid are unchanged, all object references in the pod spec are immutable respectively unmodified (claim statuses). | ||||
| 			klog.V(5).Infof("updatePod %s/%s, node unchanged", pod.Namespace, pod.Name) | ||||
| 			return | ||||
| 		} | ||||
| @@ -91,6 +92,29 @@ func (g *graphPopulator) updatePod(oldObj, obj interface{}) { | ||||
| 	klog.V(5).Infof("updatePod %s/%s for node %s completed in %v", pod.Namespace, pod.Name, pod.Spec.NodeName, time.Since(startTime)) | ||||
| } | ||||
|  | ||||
| func resourceClaimStatusesEqual(statusA, statusB []corev1.PodResourceClaimStatus) bool { | ||||
| 	if len(statusA) != len(statusB) { | ||||
| 		return false | ||||
| 	} | ||||
| 	// In most cases, status entries only get added once and not modified. | ||||
| 	// But this cannot be guaranteed, so for the sake of correctness in all | ||||
| 	// cases this code here has to check. | ||||
| 	for i := range statusA { | ||||
| 		if statusA[i].Name != statusB[i].Name { | ||||
| 			return false | ||||
| 		} | ||||
| 		claimNameA := statusA[i].ResourceClaimName | ||||
| 		claimNameB := statusB[i].ResourceClaimName | ||||
| 		if (claimNameA == nil) != (claimNameB == nil) { | ||||
| 			return false | ||||
| 		} | ||||
| 		if claimNameA != nil && *claimNameA != *claimNameB { | ||||
| 			return false | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (g *graphPopulator) deletePod(obj interface{}) { | ||||
| 	if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok { | ||||
| 		obj = tombstone.Obj | ||||
|   | ||||
| @@ -30,6 +30,7 @@ import ( | ||||
| 	"k8s.io/component-base/featuregate" | ||||
| 	coordapi "k8s.io/kubernetes/pkg/apis/coordination" | ||||
| 	api "k8s.io/kubernetes/pkg/apis/core" | ||||
| 	resourceapi "k8s.io/kubernetes/pkg/apis/resource" | ||||
| 	storageapi "k8s.io/kubernetes/pkg/apis/storage" | ||||
| 	"k8s.io/kubernetes/pkg/auth/nodeidentifier" | ||||
| 	"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac" | ||||
| @@ -40,7 +41,7 @@ import ( | ||||
| // NodeAuthorizer authorizes requests from kubelets, with the following logic: | ||||
| //  1. If a request is not from a node (NodeIdentity() returns isNode=false), reject | ||||
| //  2. If a specific node cannot be identified (NodeIdentity() returns nodeName=""), reject | ||||
| //  3. If a request is for a secret, configmap, persistent volume or persistent volume claim, reject unless the verb is get, and the requested object is related to the requesting node: | ||||
| //  3. If a request is for a secret, configmap, persistent volume, resource claim, or persistent volume claim, reject unless the verb is get, and the requested object is related to the requesting node: | ||||
| //     node <- configmap | ||||
| //     node <- pod | ||||
| //     node <- pod <- secret | ||||
| @@ -48,6 +49,7 @@ import ( | ||||
| //     node <- pod <- pvc | ||||
| //     node <- pod <- pvc <- pv | ||||
| //     node <- pod <- pvc <- pv <- secret | ||||
| //     node <- pod <- ResourceClaim | ||||
| //  4. For other resources, authorize all nodes uniformly using statically defined rules | ||||
| type NodeAuthorizer struct { | ||||
| 	graph      *Graph | ||||
| @@ -72,14 +74,15 @@ func NewAuthorizer(graph *Graph, identifier nodeidentifier.NodeIdentifier, rules | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	configMapResource = api.Resource("configmaps") | ||||
| 	secretResource    = api.Resource("secrets") | ||||
| 	pvcResource       = api.Resource("persistentvolumeclaims") | ||||
| 	pvResource        = api.Resource("persistentvolumes") | ||||
| 	vaResource        = storageapi.Resource("volumeattachments") | ||||
| 	svcAcctResource   = api.Resource("serviceaccounts") | ||||
| 	leaseResource     = coordapi.Resource("leases") | ||||
| 	csiNodeResource   = storageapi.Resource("csinodes") | ||||
| 	configMapResource     = api.Resource("configmaps") | ||||
| 	secretResource        = api.Resource("secrets") | ||||
| 	pvcResource           = api.Resource("persistentvolumeclaims") | ||||
| 	pvResource            = api.Resource("persistentvolumes") | ||||
| 	resourceClaimResource = resourceapi.Resource("resourceclaims") | ||||
| 	vaResource            = storageapi.Resource("volumeattachments") | ||||
| 	svcAcctResource       = api.Resource("serviceaccounts") | ||||
| 	leaseResource         = coordapi.Resource("leases") | ||||
| 	csiNodeResource       = storageapi.Resource("csinodes") | ||||
| ) | ||||
|  | ||||
| func (r *NodeAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) { | ||||
| @@ -117,6 +120,8 @@ func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attribu | ||||
| 			return r.authorizeGet(nodeName, pvcVertexType, attrs) | ||||
| 		case pvResource: | ||||
| 			return r.authorizeGet(nodeName, pvVertexType, attrs) | ||||
| 		case resourceClaimResource: | ||||
| 			return r.authorizeGet(nodeName, resourceClaimVertexType, attrs) | ||||
| 		case vaResource: | ||||
| 			return r.authorizeGet(nodeName, vaVertexType, attrs) | ||||
| 		case svcAcctResource: | ||||
|   | ||||
| @@ -42,16 +42,19 @@ func TestAuthorizer(t *testing.T) { | ||||
| 	g := NewGraph() | ||||
|  | ||||
| 	opts := &sampleDataOpts{ | ||||
| 		nodes:                  2, | ||||
| 		namespaces:             2, | ||||
| 		podsPerNode:            2, | ||||
| 		attachmentsPerNode:     1, | ||||
| 		sharedConfigMapsPerPod: 0, | ||||
| 		uniqueConfigMapsPerPod: 1, | ||||
| 		sharedSecretsPerPod:    1, | ||||
| 		uniqueSecretsPerPod:    1, | ||||
| 		sharedPVCsPerPod:       0, | ||||
| 		uniquePVCsPerPod:       1, | ||||
| 		nodes:                              2, | ||||
| 		namespaces:                         2, | ||||
| 		podsPerNode:                        2, | ||||
| 		attachmentsPerNode:                 1, | ||||
| 		sharedConfigMapsPerPod:             0, | ||||
| 		uniqueConfigMapsPerPod:             1, | ||||
| 		sharedSecretsPerPod:                1, | ||||
| 		uniqueSecretsPerPod:                1, | ||||
| 		sharedPVCsPerPod:                   0, | ||||
| 		uniquePVCsPerPod:                   1, | ||||
| 		uniqueResourceClaimsPerPod:         1, | ||||
| 		uniqueResourceClaimTemplatesPerPod: 1, | ||||
| 		uniqueResourceClaimTemplatesWithClaimPerPod: 1, | ||||
| 	} | ||||
| 	nodes, pods, pvs, attachments := generate(opts) | ||||
| 	populate(g, nodes, pods, pvs, attachments) | ||||
| @@ -117,6 +120,16 @@ func TestAuthorizer(t *testing.T) { | ||||
| 			attrs:  authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node0", Namespace: "ns0"}, | ||||
| 			expect: authorizer.DecisionAllow, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:   "allowed resource claim", | ||||
| 			attrs:  authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "claim0-pod0-node0-ns0", Namespace: "ns0"}, | ||||
| 			expect: authorizer.DecisionAllow, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:   "allowed resource claim with template", | ||||
| 			attrs:  authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "generated-claim-pod0-node0-ns0-0", Namespace: "ns0"}, | ||||
| 			expect: authorizer.DecisionAllow, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:   "allowed pv", | ||||
| 			attrs:  authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node0-ns0", Namespace: ""}, | ||||
| @@ -142,6 +155,16 @@ func TestAuthorizer(t *testing.T) { | ||||
| 			attrs:  authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumeclaims", Name: "pvc0-pod0-node1", Namespace: "ns0"}, | ||||
| 			expect: authorizer.DecisionNoOpinion, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:   "disallowed resource claim, other node", | ||||
| 			attrs:  authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "claim0-pod0-node1-ns0", Namespace: "ns0"}, | ||||
| 			expect: authorizer.DecisionNoOpinion, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:   "disallowed resource claim with template", | ||||
| 			attrs:  authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceclaims", APIGroup: "resource.k8s.io", Name: "pod0-node1-claimtemplate0", Namespace: "ns0"}, | ||||
| 			expect: authorizer.DecisionNoOpinion, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:   "disallowed pv", | ||||
| 			attrs:  authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "persistentvolumes", Name: "pv0-pod0-node1-ns0", Namespace: ""}, | ||||
| @@ -468,9 +491,12 @@ type sampleDataOpts struct { | ||||
| 	sharedSecretsPerPod    int | ||||
| 	sharedPVCsPerPod       int | ||||
|  | ||||
| 	uniqueSecretsPerPod    int | ||||
| 	uniqueConfigMapsPerPod int | ||||
| 	uniquePVCsPerPod       int | ||||
| 	uniqueSecretsPerPod                         int | ||||
| 	uniqueConfigMapsPerPod                      int | ||||
| 	uniquePVCsPerPod                            int | ||||
| 	uniqueResourceClaimsPerPod                  int | ||||
| 	uniqueResourceClaimTemplatesPerPod          int | ||||
| 	uniqueResourceClaimTemplatesWithClaimPerPod int | ||||
| } | ||||
|  | ||||
| func BenchmarkPopulationAllocation(b *testing.B) { | ||||
| @@ -845,6 +871,40 @@ func generatePod(name, namespace, nodeName, svcAccountName string, opts *sampleD | ||||
| 			PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: pv.Spec.ClaimRef.Name}, | ||||
| 		}}) | ||||
| 	} | ||||
| 	for i := 0; i < opts.uniqueResourceClaimsPerPod; i++ { | ||||
| 		claimName := fmt.Sprintf("claim%d-%s-%s", i, pod.Name, pod.Namespace) | ||||
| 		pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{ | ||||
| 			Name: fmt.Sprintf("claim%d", i), | ||||
| 			Source: corev1.ClaimSource{ | ||||
| 				ResourceClaimName: &claimName, | ||||
| 			}, | ||||
| 		}) | ||||
| 	} | ||||
| 	for i := 0; i < opts.uniqueResourceClaimTemplatesPerPod; i++ { | ||||
| 		claimTemplateName := fmt.Sprintf("claimtemplate%d-%s-%s", i, pod.Name, pod.Namespace) | ||||
| 		podClaimName := fmt.Sprintf("claimtemplate%d", i) | ||||
| 		pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{ | ||||
| 			Name: podClaimName, | ||||
| 			Source: corev1.ClaimSource{ | ||||
| 				ResourceClaimTemplateName: &claimTemplateName, | ||||
| 			}, | ||||
| 		}) | ||||
| 	} | ||||
| 	for i := 0; i < opts.uniqueResourceClaimTemplatesWithClaimPerPod; i++ { | ||||
| 		claimTemplateName := fmt.Sprintf("claimtemplate%d-%s-%s", i, pod.Name, pod.Namespace) | ||||
| 		podClaimName := fmt.Sprintf("claimtemplate-with-claim%d", i) | ||||
| 		claimName := fmt.Sprintf("generated-claim-%s-%s-%d", pod.Name, pod.Namespace, i) | ||||
| 		pod.Spec.ResourceClaims = append(pod.Spec.ResourceClaims, corev1.PodResourceClaim{ | ||||
| 			Name: podClaimName, | ||||
| 			Source: corev1.ClaimSource{ | ||||
| 				ResourceClaimTemplateName: &claimTemplateName, | ||||
| 			}, | ||||
| 		}) | ||||
| 		pod.Status.ResourceClaimStatuses = append(pod.Status.ResourceClaimStatuses, corev1.PodResourceClaimStatus{ | ||||
| 			Name:              podClaimName, | ||||
| 			ResourceClaimName: &claimName, | ||||
| 		}) | ||||
| 	} | ||||
| 	// Choose shared pvcs randomly from shared pvcs in a namespace. | ||||
| 	subset = randomSubset(opts.sharedPVCsPerPod, opts.sharedPVCsPerNamespace) | ||||
| 	for _, i := range subset { | ||||
|   | ||||
| @@ -27,14 +27,18 @@ import ( | ||||
| 	coordination "k8s.io/api/coordination/v1" | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	policy "k8s.io/api/policy/v1" | ||||
| 	"k8s.io/api/resource/v1alpha2" | ||||
| 	storagev1 "k8s.io/api/storage/v1" | ||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	"k8s.io/apimachinery/pkg/api/resource" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||
| 	clientset "k8s.io/client-go/kubernetes" | ||||
| 	featuregatetesting "k8s.io/component-base/featuregate/testing" | ||||
| 	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" | ||||
| 	"k8s.io/kubernetes/pkg/features" | ||||
| 	"k8s.io/kubernetes/test/integration/framework" | ||||
| 	"k8s.io/utils/pointer" | ||||
| ) | ||||
| @@ -61,7 +65,10 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 	}, "\n")) | ||||
| 	tokenFile.Close() | ||||
|  | ||||
| 	defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DynamicResourceAllocation, true)() | ||||
|  | ||||
| 	server := kubeapiservertesting.StartTestServerOrDie(t, nil, []string{ | ||||
| 		"--runtime-config=api/all=true", | ||||
| 		"--authorization-mode", "Node,RBAC", | ||||
| 		"--token-auth-file", tokenFile.Name(), | ||||
| 		"--enable-admission-plugins", "NodeRestriction", | ||||
| @@ -100,6 +107,13 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 	if _, err := superuserClient.CoreV1().ConfigMaps("ns").Create(context.TODO(), &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: "myconfigmap"}}, metav1.CreateOptions{}); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if _, err := superuserClient.ResourceV1alpha2().ResourceClaims("ns").Create(context.TODO(), &v1alpha2.ResourceClaim{ObjectMeta: metav1.ObjectMeta{Name: "mynamedresourceclaim"}, Spec: v1alpha2.ResourceClaimSpec{ResourceClassName: "example.com"}}, metav1.CreateOptions{}); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	if _, err := superuserClient.ResourceV1alpha2().ResourceClaims("ns").Create(context.TODO(), &v1alpha2.ResourceClaim{ObjectMeta: metav1.ObjectMeta{Name: "mytemplatizedresourceclaim"}, Spec: v1alpha2.ResourceClaimSpec{ResourceClassName: "example.com"}}, metav1.CreateOptions{}); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	pvName := "mypv" | ||||
| 	if _, err := superuserClientExternal.StorageV1().VolumeAttachments().Create(context.TODO(), &storagev1.VolumeAttachment{ | ||||
| 		ObjectMeta: metav1.ObjectMeta{Name: "myattachment"}, | ||||
| @@ -169,6 +183,34 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	getResourceClaim := func(client clientset.Interface) func() error { | ||||
| 		return func() error { | ||||
| 			_, err := client.ResourceV1alpha2().ResourceClaims("ns").Get(context.TODO(), "mynamedresourceclaim", metav1.GetOptions{}) | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	getResourceClaimTemplate := func(client clientset.Interface) func() error { | ||||
| 		return func() error { | ||||
| 			_, err := client.ResourceV1alpha2().ResourceClaims("ns").Get(context.TODO(), "mytemplatizedresourceclaim", metav1.GetOptions{}) | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	addResourceClaimTemplateReference := func(client clientset.Interface) func() error { | ||||
| 		return func() error { | ||||
| 			_, err := client.CoreV1().Pods("ns").Patch(context.TODO(), "node2normalpod", types.MergePatchType, | ||||
| 				[]byte(`{"status":{"resourceClaimStatuses":[{"name":"templateclaim","resourceClaimName":"mytemplatizedresourceclaim"}]}}`), | ||||
| 				metav1.PatchOptions{}, "status") | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	removeResourceClaimReference := func(client clientset.Interface) func() error { | ||||
| 		return func() error { | ||||
| 			_, err := client.CoreV1().Pods("ns").Patch(context.TODO(), "node2normalpod", types.MergePatchType, | ||||
| 				[]byte(`{"status":{"resourceClaimStatuses":null}}`), | ||||
| 				metav1.PatchOptions{}, "status") | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	createNode2NormalPod := func(client clientset.Interface) func() error { | ||||
| 		return func() error { | ||||
| @@ -182,6 +224,10 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 						{Name: "cm", VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{LocalObjectReference: corev1.LocalObjectReference{Name: "myconfigmap"}}}}, | ||||
| 						{Name: "pvc", VolumeSource: corev1.VolumeSource{PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ClaimName: "mypvc"}}}, | ||||
| 					}, | ||||
| 					ResourceClaims: []corev1.PodResourceClaim{ | ||||
| 						{Name: "namedclaim", Source: corev1.ClaimSource{ResourceClaimName: pointer.String("mynamedresourceclaim")}}, | ||||
| 						{Name: "templateclaim", Source: corev1.ClaimSource{ResourceClaimTemplateName: pointer.String("myresourceclaimtemplate")}}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, metav1.CreateOptions{}) | ||||
| 			return err | ||||
| @@ -428,6 +474,8 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 	expectForbidden(t, getConfigMap(nodeanonClient)) | ||||
| 	expectForbidden(t, getPVC(nodeanonClient)) | ||||
| 	expectForbidden(t, getPV(nodeanonClient)) | ||||
| 	expectForbidden(t, getResourceClaim(nodeanonClient)) | ||||
| 	expectForbidden(t, getResourceClaimTemplate(nodeanonClient)) | ||||
| 	expectForbidden(t, createNode2NormalPod(nodeanonClient)) | ||||
| 	expectForbidden(t, deleteNode2NormalPod(nodeanonClient)) | ||||
| 	expectForbidden(t, createNode2MirrorPodEviction(nodeanonClient)) | ||||
| @@ -440,6 +488,8 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 	expectForbidden(t, getConfigMap(node1Client)) | ||||
| 	expectForbidden(t, getPVC(node1Client)) | ||||
| 	expectForbidden(t, getPV(node1Client)) | ||||
| 	expectForbidden(t, getResourceClaim(node1Client)) | ||||
| 	expectForbidden(t, getResourceClaimTemplate(node1Client)) | ||||
| 	expectForbidden(t, createNode2NormalPod(nodeanonClient)) | ||||
| 	expectNotFound(t, createNode2MirrorPodEviction(node1Client)) | ||||
| 	expectForbidden(t, createNode2(node1Client)) | ||||
| @@ -452,6 +502,8 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 	expectForbidden(t, getConfigMap(node2Client)) | ||||
| 	expectForbidden(t, getPVC(node2Client)) | ||||
| 	expectForbidden(t, getPV(node2Client)) | ||||
| 	expectForbidden(t, getResourceClaim(node2Client)) | ||||
| 	expectForbidden(t, getResourceClaimTemplate(node2Client)) | ||||
|  | ||||
| 	expectForbidden(t, createNode2NormalPod(nodeanonClient)) | ||||
| 	// mirror pod and self node lifecycle is allowed | ||||
| @@ -479,6 +531,8 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 	expectForbidden(t, getConfigMap(nodeanonClient)) | ||||
| 	expectForbidden(t, getPVC(nodeanonClient)) | ||||
| 	expectForbidden(t, getPV(nodeanonClient)) | ||||
| 	expectForbidden(t, getResourceClaim(nodeanonClient)) | ||||
| 	expectForbidden(t, getResourceClaimTemplate(nodeanonClient)) | ||||
| 	expectForbidden(t, createNode2NormalPod(nodeanonClient)) | ||||
| 	expectForbidden(t, updateNode2NormalPodStatus(nodeanonClient)) | ||||
| 	expectForbidden(t, deleteNode2NormalPod(nodeanonClient)) | ||||
| @@ -492,6 +546,8 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 	expectForbidden(t, getConfigMap(node1Client)) | ||||
| 	expectForbidden(t, getPVC(node1Client)) | ||||
| 	expectForbidden(t, getPV(node1Client)) | ||||
| 	expectForbidden(t, getResourceClaim(node1Client)) | ||||
| 	expectForbidden(t, getResourceClaimTemplate(node1Client)) | ||||
| 	expectForbidden(t, createNode2NormalPod(node1Client)) | ||||
| 	expectForbidden(t, updateNode2NormalPodStatus(node1Client)) | ||||
| 	expectForbidden(t, deleteNode2NormalPod(node1Client)) | ||||
| @@ -507,6 +563,26 @@ func TestNodeAuthorizer(t *testing.T) { | ||||
| 	expectAllowed(t, getPVC(node2Client)) | ||||
| 	expectAllowed(t, getPV(node2Client)) | ||||
|  | ||||
| 	// node2 can only get direct claim references | ||||
| 	expectAllowed(t, getResourceClaim(node2Client)) | ||||
| 	expectForbidden(t, getResourceClaimTemplate(node2Client)) | ||||
|  | ||||
| 	// node cannot add a claim reference | ||||
| 	expectForbidden(t, addResourceClaimTemplateReference(node2Client)) | ||||
| 	// superuser can add a claim reference | ||||
| 	expectAllowed(t, addResourceClaimTemplateReference(superuserClient)) | ||||
| 	// node can get direct and template claim references | ||||
| 	expectAllowed(t, getResourceClaim(node2Client)) | ||||
| 	expectAllowed(t, getResourceClaimTemplate(node2Client)) | ||||
|  | ||||
| 	// node cannot remove a claim reference | ||||
| 	expectForbidden(t, removeResourceClaimReference(node2Client)) | ||||
| 	// superuser can remove a claim reference | ||||
| 	expectAllowed(t, removeResourceClaimReference(superuserClient)) | ||||
| 	// node2 can only get direct claim references | ||||
| 	expectAllowed(t, getResourceClaim(node2Client)) | ||||
| 	expectForbidden(t, getResourceClaimTemplate(node2Client)) | ||||
|  | ||||
| 	expectForbidden(t, createNode2NormalPod(node2Client)) | ||||
| 	expectAllowed(t, updateNode2NormalPodStatus(node2Client)) | ||||
| 	expectAllowed(t, deleteNode2NormalPod(node2Client)) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Kubernetes Prow Robot
					Kubernetes Prow Robot