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:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user