mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 05:27:21 +00:00
Merge pull request #98147 from deads2k/system-masters-delete
add check to gc_admission to allow super users to skip RESTMapping
This commit is contained in:
commit
3667e0e9f7
@ -18,6 +18,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||||
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -31,6 +32,7 @@ go_test(
|
|||||||
"//pkg/kubeapiserver/admission:go_default_library",
|
"//pkg/kubeapiserver/admission:go_default_library",
|
||||||
"//staging/src/k8s.io/api/apps/v1:go_default_library",
|
"//staging/src/k8s.io/api/apps/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -122,6 +123,18 @@ func (a *gcPermissionsEnforcement) Validate(ctx context.Context, attributes admi
|
|||||||
// true. If so, only allows the change if the user has delete permission of
|
// true. If so, only allows the change if the user has delete permission of
|
||||||
// the _OWNER_
|
// the _OWNER_
|
||||||
newBlockingRefs := newBlockingOwnerDeletionRefs(attributes.GetObject(), attributes.GetOldObject())
|
newBlockingRefs := newBlockingOwnerDeletionRefs(attributes.GetObject(), attributes.GetOldObject())
|
||||||
|
if len(newBlockingRefs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// There can be a case where a restMapper tries to hit discovery endpoints and times out if the network is inaccessible.
|
||||||
|
// This can prevent creating the pod to run the network to be able to do discovery and it appears as a timeout, not a rejection.
|
||||||
|
// Because the timeout is wrapper on admission/request, we can run a single check to see if the user can finalize any
|
||||||
|
// possible resource.
|
||||||
|
if decision, _, _ := a.authorizer.Authorize(ctx, finalizeAnythingRecord(attributes.GetUserInfo())); decision == authorizer.DecisionAllow {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for _, ref := range newBlockingRefs {
|
for _, ref := range newBlockingRefs {
|
||||||
records, err := a.ownerRefToDeleteAttributeRecords(ref, attributes)
|
records, err := a.ownerRefToDeleteAttributeRecords(ref, attributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -173,6 +186,20 @@ func isChangingOwnerReference(newObj, oldObj runtime.Object) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func finalizeAnythingRecord(userInfo user.Info) authorizer.AttributesRecord {
|
||||||
|
return authorizer.AttributesRecord{
|
||||||
|
User: userInfo,
|
||||||
|
Verb: "update",
|
||||||
|
APIGroup: "*",
|
||||||
|
APIVersion: "*",
|
||||||
|
Resource: "*",
|
||||||
|
Subresource: "finalizers",
|
||||||
|
Name: "*",
|
||||||
|
ResourceRequest: true,
|
||||||
|
Path: "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Translates ref to a DeleteAttribute deleting the object referred by the ref.
|
// Translates ref to a DeleteAttribute deleting the object referred by the ref.
|
||||||
// OwnerReference only records the object kind, which might map to multiple
|
// OwnerReference only records the object kind, which might map to multiple
|
||||||
// resources, so multiple DeleteAttribute might be returned.
|
// resources, so multiple DeleteAttribute might be returned.
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
@ -50,6 +51,9 @@ func (fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (a
|
|||||||
if a.GetVerb() == "update" && a.GetSubresource() == "finalizers" {
|
if a.GetVerb() == "update" && a.GetSubresource() == "finalizers" {
|
||||||
return authorizer.DecisionNoOpinion, "", nil
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
}
|
}
|
||||||
|
if a.GetAPIGroup() == "*" && a.GetResource() == "*" { // this user does not have full rights
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}
|
||||||
return authorizer.DecisionAllow, "", nil
|
return authorizer.DecisionAllow, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +64,9 @@ func (fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (a
|
|||||||
if a.GetVerb() == "update" && a.GetResource() == "pods" && a.GetSubresource() == "finalizers" {
|
if a.GetVerb() == "update" && a.GetResource() == "pods" && a.GetSubresource() == "finalizers" {
|
||||||
return authorizer.DecisionNoOpinion, "", nil
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
}
|
}
|
||||||
|
if a.GetAPIGroup() == "*" && a.GetResource() == "*" { // this user does not have full rights
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}
|
||||||
return authorizer.DecisionAllow, "", nil
|
return authorizer.DecisionAllow, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,6 +77,9 @@ func (fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (a
|
|||||||
if a.GetVerb() == "update" && a.GetResource() == "replicationcontrollers" && a.GetSubresource() == "finalizers" {
|
if a.GetVerb() == "update" && a.GetResource() == "replicationcontrollers" && a.GetSubresource() == "finalizers" {
|
||||||
return authorizer.DecisionNoOpinion, "", nil
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
}
|
}
|
||||||
|
if a.GetAPIGroup() == "*" && a.GetResource() == "*" { // this user does not have full rights
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}
|
||||||
return authorizer.DecisionAllow, "", nil
|
return authorizer.DecisionAllow, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,8 +90,12 @@ func (fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (a
|
|||||||
if a.GetVerb() == "update" && a.GetResource() == "nodes" && a.GetSubresource() == "finalizers" {
|
if a.GetVerb() == "update" && a.GetResource() == "nodes" && a.GetSubresource() == "finalizers" {
|
||||||
return authorizer.DecisionNoOpinion, "", nil
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
}
|
}
|
||||||
|
if a.GetAPIGroup() == "*" && a.GetResource() == "*" { // this user does not have full rights
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}
|
||||||
return authorizer.DecisionAllow, "", nil
|
return authorizer.DecisionAllow, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return authorizer.DecisionAllow, "", nil
|
return authorizer.DecisionAllow, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +148,39 @@ func newGCPermissionsEnforcement() (*gcPermissionsEnforcement, error) {
|
|||||||
return gcAdmit, nil
|
return gcAdmit, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type neverReturningRESTMapper struct{}
|
||||||
|
|
||||||
|
var _ meta.RESTMapper = &neverReturningRESTMapper{}
|
||||||
|
|
||||||
|
func (r *neverReturningRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
|
||||||
|
// this ok because if the test works, this method should never be called.
|
||||||
|
panic("test failed")
|
||||||
|
}
|
||||||
|
func (r *neverReturningRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
|
||||||
|
// this ok because if the test works, this method should never be called.
|
||||||
|
panic("test failed")
|
||||||
|
}
|
||||||
|
func (r *neverReturningRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
|
||||||
|
// this ok because if the test works, this method should never be called.
|
||||||
|
panic("test failed")
|
||||||
|
}
|
||||||
|
func (r *neverReturningRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
|
||||||
|
// this ok because if the test works, this method should never be called.
|
||||||
|
panic("test failed")
|
||||||
|
}
|
||||||
|
func (r *neverReturningRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
|
||||||
|
// this ok because if the test works, this method should never be called.
|
||||||
|
panic("test failed")
|
||||||
|
}
|
||||||
|
func (r *neverReturningRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
|
||||||
|
// this ok because if the test works, this method should never be called.
|
||||||
|
panic("test failed")
|
||||||
|
}
|
||||||
|
func (r *neverReturningRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
|
||||||
|
// this ok because if the test works, this method should never be called.
|
||||||
|
panic("test failed")
|
||||||
|
}
|
||||||
|
|
||||||
func TestGCAdmission(t *testing.T) {
|
func TestGCAdmission(t *testing.T) {
|
||||||
expectNoError := func(err error) bool {
|
expectNoError := func(err error) bool {
|
||||||
return err == nil
|
return err == nil
|
||||||
@ -414,12 +461,13 @@ func TestBlockOwnerDeletionAdmission(t *testing.T) {
|
|||||||
return strings.Contains(err.Error(), "cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on")
|
return strings.Contains(err.Error(), "cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on")
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
username string
|
username string
|
||||||
resource schema.GroupVersionResource
|
resource schema.GroupVersionResource
|
||||||
subresource string
|
subresource string
|
||||||
oldObj runtime.Object
|
oldObj runtime.Object
|
||||||
newObj runtime.Object
|
newObj runtime.Object
|
||||||
|
restMapperOverride meta.RESTMapper
|
||||||
|
|
||||||
checkError func(error) bool
|
checkError func(error) bool
|
||||||
}{
|
}{
|
||||||
@ -445,6 +493,14 @@ func TestBlockOwnerDeletionAdmission(t *testing.T) {
|
|||||||
newObj: podWithOwnerRefs(blockRC1, blockRC2, blockNode),
|
newObj: podWithOwnerRefs(blockRC1, blockRC2, blockNode),
|
||||||
checkError: expectNoError,
|
checkError: expectNoError,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "super-user, create, some ownerReferences have blockOwnerDeletion=true, hangingRESTMapper",
|
||||||
|
username: "super",
|
||||||
|
resource: api.SchemeGroupVersion.WithResource("pods"),
|
||||||
|
newObj: podWithOwnerRefs(blockRC1, blockRC2, blockNode),
|
||||||
|
restMapperOverride: &neverReturningRESTMapper{},
|
||||||
|
checkError: expectNoError,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "non-rc-deleter, create, no ownerReferences",
|
name: "non-rc-deleter, create, no ownerReferences",
|
||||||
username: "non-rc-deleter",
|
username: "non-rc-deleter",
|
||||||
@ -601,24 +657,30 @@ func TestBlockOwnerDeletionAdmission(t *testing.T) {
|
|||||||
checkError: expectCantSetBlockOwnerDeletionError,
|
checkError: expectCantSetBlockOwnerDeletionError,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
gcAdmit, err := newGCPermissionsEnforcement()
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
operation := admission.Create
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var options runtime.Object = &metav1.CreateOptions{}
|
gcAdmit, err := newGCPermissionsEnforcement()
|
||||||
if tc.oldObj != nil {
|
if err != nil {
|
||||||
operation = admission.Update
|
t.Fatal(err)
|
||||||
options = &metav1.UpdateOptions{}
|
}
|
||||||
}
|
if tc.restMapperOverride != nil {
|
||||||
user := &user.DefaultInfo{Name: tc.username}
|
gcAdmit.restMapper = tc.restMapperOverride
|
||||||
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, options, false, user)
|
}
|
||||||
|
|
||||||
err := gcAdmit.Validate(context.TODO(), attributes, nil)
|
operation := admission.Create
|
||||||
if !tc.checkError(err) {
|
var options runtime.Object = &metav1.CreateOptions{}
|
||||||
t.Errorf("%v: unexpected err: %v", tc.name, err)
|
if tc.oldObj != nil {
|
||||||
}
|
operation = admission.Update
|
||||||
|
options = &metav1.UpdateOptions{}
|
||||||
|
}
|
||||||
|
user := &user.DefaultInfo{Name: tc.username}
|
||||||
|
attributes := admission.NewAttributesRecord(tc.newObj, tc.oldObj, schema.GroupVersionKind{}, metav1.NamespaceDefault, "foo", tc.resource, tc.subresource, operation, options, false, user)
|
||||||
|
|
||||||
|
err = gcAdmit.Validate(context.TODO(), attributes, nil)
|
||||||
|
if !tc.checkError(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user