...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/gc/gc_admission.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/gc

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package gc
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  
    24  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    25  	"k8s.io/apimachinery/pkg/api/meta"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"k8s.io/apiserver/pkg/admission"
    31  	"k8s.io/apiserver/pkg/authentication/user"
    32  	"k8s.io/apiserver/pkg/authorization/authorizer"
    33  )
    34  
    35  // PluginName indicates name of admission plugin.
    36  const PluginName = "OwnerReferencesPermissionEnforcement"
    37  
    38  // Register registers a plugin
    39  func Register(plugins *admission.Plugins) {
    40  	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
    41  		// the pods/status endpoint is ignored by this plugin since old kubelets
    42  		// corrupt them.  the pod status strategy ensures status updates cannot mutate
    43  		// ownerRef.
    44  		whiteList := []whiteListItem{
    45  			{
    46  				groupResource: schema.GroupResource{Resource: "pods"},
    47  				subresource:   "status",
    48  			},
    49  		}
    50  		return &gcPermissionsEnforcement{
    51  			Handler:   admission.NewHandler(admission.Create, admission.Update),
    52  			whiteList: whiteList,
    53  		}, nil
    54  	})
    55  }
    56  
    57  // gcPermissionsEnforcement is an implementation of admission.Interface.
    58  type gcPermissionsEnforcement struct {
    59  	*admission.Handler
    60  
    61  	authorizer authorizer.Authorizer
    62  
    63  	restMapper meta.RESTMapper
    64  
    65  	// items in this whitelist are ignored upon admission.
    66  	// any item in this list must protect against ownerRef mutations
    67  	// via strategy enforcement.
    68  	whiteList []whiteListItem
    69  }
    70  
    71  var _ admission.ValidationInterface = &gcPermissionsEnforcement{}
    72  
    73  // whiteListItem describes an entry in a whitelist ignored by gc permission enforcement.
    74  type whiteListItem struct {
    75  	groupResource schema.GroupResource
    76  	subresource   string
    77  }
    78  
    79  // isWhiteListed returns true if the specified item is in the whitelist.
    80  func (a *gcPermissionsEnforcement) isWhiteListed(groupResource schema.GroupResource, subresource string) bool {
    81  	for _, item := range a.whiteList {
    82  		if item.groupResource == groupResource && item.subresource == subresource {
    83  			return true
    84  		}
    85  	}
    86  	return false
    87  }
    88  
    89  func (a *gcPermissionsEnforcement) Validate(ctx context.Context, attributes admission.Attributes, o admission.ObjectInterfaces) (err error) {
    90  	// // if the request is in the whitelist, we skip mutation checks for this resource.
    91  	if a.isWhiteListed(attributes.GetResource().GroupResource(), attributes.GetSubresource()) {
    92  		return nil
    93  	}
    94  
    95  	// if we aren't changing owner references, then the edit is always allowed
    96  	if !isChangingOwnerReference(attributes.GetObject(), attributes.GetOldObject()) {
    97  		return nil
    98  	}
    99  
   100  	// if you are creating a thing, you should always be allowed to set an owner ref since you logically had the power
   101  	// to never create it.  We still need to check block owner deletion below, because the power to delete does not
   102  	// imply the power to prevent deletion on other resources.
   103  	if attributes.GetOperation() != admission.Create {
   104  		deleteAttributes := authorizer.AttributesRecord{
   105  			User:            attributes.GetUserInfo(),
   106  			Verb:            "delete",
   107  			Namespace:       attributes.GetNamespace(),
   108  			APIGroup:        attributes.GetResource().Group,
   109  			APIVersion:      attributes.GetResource().Version,
   110  			Resource:        attributes.GetResource().Resource,
   111  			Subresource:     attributes.GetSubresource(),
   112  			Name:            attributes.GetName(),
   113  			ResourceRequest: true,
   114  			Path:            "",
   115  		}
   116  		decision, reason, err := a.authorizer.Authorize(ctx, deleteAttributes)
   117  		if decision != authorizer.DecisionAllow {
   118  			return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err))
   119  		}
   120  	}
   121  
   122  	// Further check if the user is setting ownerReference.blockOwnerDeletion to
   123  	// true. If so, only allows the change if the user has delete permission of
   124  	// the _OWNER_
   125  	newBlockingRefs := newBlockingOwnerDeletionRefs(attributes.GetObject(), attributes.GetOldObject())
   126  	if len(newBlockingRefs) == 0 {
   127  		return nil
   128  	}
   129  
   130  	// There can be a case where a restMapper tries to hit discovery endpoints and times out if the network is inaccessible.
   131  	// 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.
   132  	// Because the timeout is wrapper on admission/request, we can run a single check to see if the user can finalize any
   133  	// possible resource.
   134  	if decision, _, _ := a.authorizer.Authorize(ctx, finalizeAnythingRecord(attributes.GetUserInfo())); decision == authorizer.DecisionAllow {
   135  		return nil
   136  	}
   137  
   138  	for _, ref := range newBlockingRefs {
   139  		records, err := a.ownerRefToDeleteAttributeRecords(ref, attributes)
   140  		if err != nil {
   141  			return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion in this case because cannot find RESTMapping for APIVersion %s Kind %s: %v", ref.APIVersion, ref.Kind, err))
   142  		}
   143  		// Multiple records are returned if ref.Kind could map to multiple
   144  		// resources. User needs to have delete permission on all the
   145  		// matched Resources.
   146  		for _, record := range records {
   147  			decision, reason, err := a.authorizer.Authorize(ctx, record)
   148  			if decision != authorizer.DecisionAllow {
   149  				return admission.NewForbidden(attributes, fmt.Errorf("cannot set blockOwnerDeletion if an ownerReference refers to a resource you can't set finalizers on: %v, %v", reason, err))
   150  			}
   151  		}
   152  	}
   153  
   154  	return nil
   155  
   156  }
   157  
   158  func isChangingOwnerReference(newObj, oldObj runtime.Object) bool {
   159  	newMeta, err := meta.Accessor(newObj)
   160  	if err != nil {
   161  		// if we don't have objectmeta, we don't have the object reference
   162  		return false
   163  	}
   164  
   165  	if oldObj == nil {
   166  		return len(newMeta.GetOwnerReferences()) > 0
   167  	}
   168  	oldMeta, err := meta.Accessor(oldObj)
   169  	if err != nil {
   170  		// if we don't have objectmeta, we don't have the object reference
   171  		return false
   172  	}
   173  
   174  	// compare the old and new.  If they aren't the same, then we're trying to change an ownerRef
   175  	oldOwners := oldMeta.GetOwnerReferences()
   176  	newOwners := newMeta.GetOwnerReferences()
   177  	if len(oldOwners) != len(newOwners) {
   178  		return true
   179  	}
   180  	for i := range oldOwners {
   181  		if !apiequality.Semantic.DeepEqual(oldOwners[i], newOwners[i]) {
   182  			return true
   183  		}
   184  	}
   185  
   186  	return false
   187  }
   188  
   189  func finalizeAnythingRecord(userInfo user.Info) authorizer.AttributesRecord {
   190  	return authorizer.AttributesRecord{
   191  		User:            userInfo,
   192  		Verb:            "update",
   193  		APIGroup:        "*",
   194  		APIVersion:      "*",
   195  		Resource:        "*",
   196  		Subresource:     "finalizers",
   197  		Name:            "*",
   198  		ResourceRequest: true,
   199  		Path:            "",
   200  	}
   201  }
   202  
   203  // Translates ref to a DeleteAttribute deleting the object referred by the ref.
   204  // OwnerReference only records the object kind, which might map to multiple
   205  // resources, so multiple DeleteAttribute might be returned.
   206  func (a *gcPermissionsEnforcement) ownerRefToDeleteAttributeRecords(ref metav1.OwnerReference, attributes admission.Attributes) ([]authorizer.AttributesRecord, error) {
   207  	var ret []authorizer.AttributesRecord
   208  	groupVersion, err := schema.ParseGroupVersion(ref.APIVersion)
   209  	if err != nil {
   210  		return ret, err
   211  	}
   212  	mappings, err := a.restMapper.RESTMappings(schema.GroupKind{Group: groupVersion.Group, Kind: ref.Kind}, groupVersion.Version)
   213  	if err != nil {
   214  		return ret, err
   215  	}
   216  	for _, mapping := range mappings {
   217  		ar := authorizer.AttributesRecord{
   218  			User:            attributes.GetUserInfo(),
   219  			Verb:            "update",
   220  			APIGroup:        mapping.Resource.Group,
   221  			APIVersion:      mapping.Resource.Version,
   222  			Resource:        mapping.Resource.Resource,
   223  			Subresource:     "finalizers",
   224  			Name:            ref.Name,
   225  			ResourceRequest: true,
   226  			Path:            "",
   227  		}
   228  		if mapping.Scope.Name() == meta.RESTScopeNameNamespace {
   229  			// if the owner is namespaced, it must be in the same namespace as the dependent is.
   230  			ar.Namespace = attributes.GetNamespace()
   231  		}
   232  		ret = append(ret, ar)
   233  	}
   234  	return ret, nil
   235  }
   236  
   237  // only keeps the blocking refs
   238  func blockingOwnerRefs(refs []metav1.OwnerReference) []metav1.OwnerReference {
   239  	var ret []metav1.OwnerReference
   240  	for _, ref := range refs {
   241  		if ref.BlockOwnerDeletion != nil && *ref.BlockOwnerDeletion {
   242  			ret = append(ret, ref)
   243  		}
   244  	}
   245  	return ret
   246  }
   247  
   248  func indexByUID(refs []metav1.OwnerReference) map[types.UID]metav1.OwnerReference {
   249  	ret := make(map[types.UID]metav1.OwnerReference)
   250  	for _, ref := range refs {
   251  		ret[ref.UID] = ref
   252  	}
   253  	return ret
   254  }
   255  
   256  // Returns new blocking ownerReferences, and references whose blockOwnerDeletion
   257  // field is changed from nil or false to true.
   258  func newBlockingOwnerDeletionRefs(newObj, oldObj runtime.Object) []metav1.OwnerReference {
   259  	newMeta, err := meta.Accessor(newObj)
   260  	if err != nil {
   261  		// if we don't have objectmeta, we don't have the object reference
   262  		return nil
   263  	}
   264  	newRefs := newMeta.GetOwnerReferences()
   265  	blockingNewRefs := blockingOwnerRefs(newRefs)
   266  	if len(blockingNewRefs) == 0 {
   267  		return nil
   268  	}
   269  
   270  	if oldObj == nil {
   271  		return blockingNewRefs
   272  	}
   273  	oldMeta, err := meta.Accessor(oldObj)
   274  	if err != nil {
   275  		// if we don't have objectmeta, treat it as if all the ownerReference are newly created
   276  		return blockingNewRefs
   277  	}
   278  
   279  	var ret []metav1.OwnerReference
   280  	indexedOldRefs := indexByUID(oldMeta.GetOwnerReferences())
   281  	for _, ref := range blockingNewRefs {
   282  		oldRef, ok := indexedOldRefs[ref.UID]
   283  		if !ok {
   284  			// if ref is newly added, and it's blocking, then returns it.
   285  			ret = append(ret, ref)
   286  			continue
   287  		}
   288  		wasNotBlocking := oldRef.BlockOwnerDeletion == nil || *oldRef.BlockOwnerDeletion == false
   289  		if wasNotBlocking {
   290  			ret = append(ret, ref)
   291  		}
   292  	}
   293  	return ret
   294  }
   295  
   296  func (a *gcPermissionsEnforcement) SetAuthorizer(authorizer authorizer.Authorizer) {
   297  	a.authorizer = authorizer
   298  }
   299  
   300  func (a *gcPermissionsEnforcement) SetRESTMapper(restMapper meta.RESTMapper) {
   301  	a.restMapper = restMapper
   302  }
   303  
   304  func (a *gcPermissionsEnforcement) ValidateInitialization() error {
   305  	if a.authorizer == nil {
   306  		return fmt.Errorf("missing authorizer")
   307  	}
   308  	if a.restMapper == nil {
   309  		return fmt.Errorf("missing restMapper")
   310  	}
   311  	return nil
   312  }
   313  

View as plain text