Merge pull request #125163 from pohly/dra-kubelet-api-version-independent-no-rest-proxy

DRA: make kubelet independent of the resource.k8s.io API version
This commit is contained in:
Kubernetes Prow Robot
2024-07-18 17:47:48 -07:00
committed by GitHub
25 changed files with 1660 additions and 1618 deletions

View File

@@ -641,8 +641,14 @@ func (p *Plugin) admitCSINode(nodeName string, a admission.Attributes) error {
func (p *Plugin) admitResourceSlice(nodeName string, a admission.Attributes) error {
// The create request must come from a node with the same name as the NodeName field.
// Other requests gets checked by the node authorizer.
if a.GetOperation() == admission.Create {
// Same when deleting an object.
//
// Other requests get checked by the node authorizer. The checks here are necessary
// because the node authorizer does not know the object content for a create request
// and not each deleted object in a DeleteCollection. DeleteCollection checks each
// individual object.
switch a.GetOperation() {
case admission.Create:
slice, ok := a.GetObject().(*resource.ResourceSlice)
if !ok {
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
@@ -651,6 +657,15 @@ func (p *Plugin) admitResourceSlice(nodeName string, a admission.Attributes) err
if slice.NodeName != nodeName {
return admission.NewForbidden(a, errors.New("can only create ResourceSlice with the same NodeName as the requesting node"))
}
case admission.Delete:
slice, ok := a.GetOldObject().(*resource.ResourceSlice)
if !ok {
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
}
if slice.NodeName != nodeName {
return admission.NewForbidden(a, errors.New("can only delete ResourceSlice with the same NodeName as the requesting node"))
}
}
return nil

View File

@@ -1607,7 +1607,8 @@ func TestAdmitResourceSlice(t *testing.T) {
apiResource := resourceapi.SchemeGroupVersion.WithResource("resourceslices")
nodename := "mynode"
mynode := &user.DefaultInfo{Name: "system:node:" + nodename, Groups: []string{"system:nodes"}}
err := "can only create ResourceSlice with the same NodeName as the requesting node"
createErr := "can only create ResourceSlice with the same NodeName as the requesting node"
deleteErr := "can only delete ResourceSlice with the same NodeName as the requesting node"
sliceNode := &resourceapi.ResourceSlice{
ObjectMeta: metav1.ObjectMeta{
@@ -1624,53 +1625,88 @@ func TestAdmitResourceSlice(t *testing.T) {
tests := map[string]struct {
operation admission.Operation
obj runtime.Object
options runtime.Object
obj, oldObj runtime.Object
featureEnabled bool
expectError string
}{
"create allowed, enabled": {
operation: admission.Create,
options: &metav1.CreateOptions{},
obj: sliceNode,
featureEnabled: true,
expectError: "",
},
"create disallowed, enabled": {
operation: admission.Create,
options: &metav1.CreateOptions{},
obj: sliceOtherNode,
featureEnabled: true,
expectError: err,
expectError: createErr,
},
"create allowed, disabled": {
operation: admission.Create,
options: &metav1.CreateOptions{},
obj: sliceNode,
featureEnabled: false,
expectError: "",
},
"create disallowed, disabled": {
operation: admission.Create,
options: &metav1.CreateOptions{},
obj: sliceOtherNode,
featureEnabled: false,
expectError: err,
expectError: createErr,
},
"update allowed, same node": {
operation: admission.Update,
options: &metav1.UpdateOptions{},
obj: sliceNode,
featureEnabled: true,
expectError: "",
},
"update allowed, other node": {
operation: admission.Update,
options: &metav1.UpdateOptions{},
obj: sliceOtherNode,
featureEnabled: true,
expectError: "",
},
"delete allowed, enabled": {
operation: admission.Delete,
options: &metav1.DeleteOptions{},
oldObj: sliceNode,
featureEnabled: true,
expectError: "",
},
"delete disallowed, enabled": {
operation: admission.Delete,
options: &metav1.DeleteOptions{},
oldObj: sliceOtherNode,
featureEnabled: true,
expectError: deleteErr,
},
"delete allowed, disabled": {
operation: admission.Delete,
options: &metav1.DeleteOptions{},
oldObj: sliceNode,
featureEnabled: false,
expectError: "",
},
"delete disallowed, disabled": {
operation: admission.Delete,
options: &metav1.DeleteOptions{},
oldObj: sliceOtherNode,
featureEnabled: false,
expectError: deleteErr,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
attributes := admission.NewAttributesRecord(
test.obj, nil, schema.GroupVersionKind{},
"", "foo", apiResource, "", test.operation, &metav1.CreateOptions{}, false, mynode)
test.obj, test.oldObj, schema.GroupVersionKind{},
"", "foo", apiResource, "", test.operation, test.options, false, mynode)
featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, features.DynamicResourceAllocation, test.featureEnabled)
a := &admitTestCase{
name: name,

View File

@@ -309,30 +309,34 @@ func (r *NodeAuthorizer) authorizeResourceSlice(nodeName string, attrs authorize
return authorizer.DecisionNoOpinion, "cannot authorize ResourceSlice subresources", nil
}
// allowed verbs: get, create, update, patch, delete
// allowed verbs: get, create, update, patch, delete, watch, list, deletecollection
verb := attrs.GetVerb()
switch verb {
case "get", "create", "update", "patch", "delete":
// Okay, but check individual object permission below.
case "watch", "list":
// Okay. The kubelet is trusted to use a filter for its own objects.
case "create":
// The request must come from a node with the same name as the ResourceSlice.NodeName field.
//
// For create, the noderestriction admission plugin is performing this check.
// Here we don't have access to the content of the new object.
return authorizer.DecisionAllow, "", nil
case "get", "update", "patch", "delete":
// Checking the existing object must have established that access
// is allowed by recording a graph edge.
return r.authorize(nodeName, sliceVertexType, attrs)
case "watch", "list", "deletecollection":
// Okay. The kubelet is trusted to use a filter for its own objects in watch and list.
// The NodeRestriction admission plugin (plugin/pkg/admission/noderestriction)
// ensures that the node is not deleting some ResourceSlice belonging to
// some other node.
//
// TODO (https://github.com/kubernetes/kubernetes/issues/125355):
// Once https://github.com/kubernetes/enhancements/pull/4600 is implemented,
// this code needs to be extended to verify that the node filter is indeed set.
// Then the admission check can be removed.
return authorizer.DecisionAllow, "", nil
default:
klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "can only get, create, update, patch, or delete a ResourceSlice", nil
return authorizer.DecisionNoOpinion, "only the following verbs are allowed for a ResourceSlice: get, watch, list, create, update, patch, delete, deletecollection", nil
}
// The request must come from a node with the same name as the ResourceSlice.NodeName field.
//
// For create, the noderestriction admission plugin is performing this check.
// Here we don't have access to the content of the new object.
if verb == "create" {
return authorizer.DecisionAllow, "", nil
}
// For any other verb, checking the existing object must have established that access
// is allowed by recording a graph edge.
return r.authorize(nodeName, sliceVertexType, attrs)
}
// hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node

View File

@@ -181,6 +181,7 @@ func NodeRules() []rbacv1.PolicyRule {
// DRA Resource Claims
if utilfeature.DefaultFeatureGate.Enabled(features.DynamicResourceAllocation) {
nodePolicyRules = append(nodePolicyRules, rbacv1helpers.NewRule("get").Groups(resourceGroup).Resources("resourceclaims").RuleOrDie())
nodePolicyRules = append(nodePolicyRules, rbacv1helpers.NewRule("deletecollection").Groups(resourceGroup).Resources("resourceslices").RuleOrDie())
}
// Kubelet needs access to ClusterTrustBundles to support the pemTrustAnchors volume type.
if utilfeature.DefaultFeatureGate.Enabled(features.ClusterTrustBundle) {