...

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

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

     1  /*
     2  Copyright 2017 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 noderestriction
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"strings"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	v1 "k8s.io/api/core/v1"
    28  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    29  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    30  	"k8s.io/apimachinery/pkg/api/meta"
    31  	"k8s.io/apimachinery/pkg/labels"
    32  	"k8s.io/apimachinery/pkg/util/sets"
    33  	"k8s.io/apiserver/pkg/admission"
    34  	apiserveradmission "k8s.io/apiserver/pkg/admission/initializer"
    35  	"k8s.io/client-go/informers"
    36  	corev1lister "k8s.io/client-go/listers/core/v1"
    37  	"k8s.io/component-base/featuregate"
    38  	kubeletapis "k8s.io/kubelet/pkg/apis"
    39  	podutil "k8s.io/kubernetes/pkg/api/pod"
    40  	authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
    41  	coordapi "k8s.io/kubernetes/pkg/apis/coordination"
    42  	api "k8s.io/kubernetes/pkg/apis/core"
    43  	"k8s.io/kubernetes/pkg/apis/policy"
    44  	"k8s.io/kubernetes/pkg/apis/resource"
    45  	storage "k8s.io/kubernetes/pkg/apis/storage"
    46  	"k8s.io/kubernetes/pkg/auth/nodeidentifier"
    47  	"k8s.io/kubernetes/pkg/features"
    48  )
    49  
    50  // PluginName is a string with the name of the plugin
    51  const PluginName = "NodeRestriction"
    52  
    53  // Register registers a plugin
    54  func Register(plugins *admission.Plugins) {
    55  	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
    56  		return NewPlugin(nodeidentifier.NewDefaultNodeIdentifier()), nil
    57  	})
    58  }
    59  
    60  // NewPlugin creates a new NodeRestriction admission plugin.
    61  // This plugin identifies requests from nodes
    62  func NewPlugin(nodeIdentifier nodeidentifier.NodeIdentifier) *Plugin {
    63  	return &Plugin{
    64  		Handler:        admission.NewHandler(admission.Create, admission.Update, admission.Delete),
    65  		nodeIdentifier: nodeIdentifier,
    66  	}
    67  }
    68  
    69  // Plugin holds state for and implements the admission plugin.
    70  type Plugin struct {
    71  	*admission.Handler
    72  	nodeIdentifier nodeidentifier.NodeIdentifier
    73  	podsGetter     corev1lister.PodLister
    74  	nodesGetter    corev1lister.NodeLister
    75  
    76  	expansionRecoveryEnabled         bool
    77  	dynamicResourceAllocationEnabled bool
    78  }
    79  
    80  var (
    81  	_ admission.Interface                                 = &Plugin{}
    82  	_ apiserveradmission.WantsExternalKubeInformerFactory = &Plugin{}
    83  	_ apiserveradmission.WantsFeatures                    = &Plugin{}
    84  )
    85  
    86  // InspectFeatureGates allows setting bools without taking a dep on a global variable
    87  func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
    88  	p.expansionRecoveryEnabled = featureGates.Enabled(features.RecoverVolumeExpansionFailure)
    89  	p.dynamicResourceAllocationEnabled = featureGates.Enabled(features.DynamicResourceAllocation)
    90  }
    91  
    92  // SetExternalKubeInformerFactory registers an informer factory into Plugin
    93  func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
    94  	p.podsGetter = f.Core().V1().Pods().Lister()
    95  	p.nodesGetter = f.Core().V1().Nodes().Lister()
    96  }
    97  
    98  // ValidateInitialization validates the Plugin was initialized properly
    99  func (p *Plugin) ValidateInitialization() error {
   100  	if p.nodeIdentifier == nil {
   101  		return fmt.Errorf("%s requires a node identifier", PluginName)
   102  	}
   103  	if p.podsGetter == nil {
   104  		return fmt.Errorf("%s requires a pod getter", PluginName)
   105  	}
   106  	if p.nodesGetter == nil {
   107  		return fmt.Errorf("%s requires a node getter", PluginName)
   108  	}
   109  	return nil
   110  }
   111  
   112  var (
   113  	podResource           = api.Resource("pods")
   114  	nodeResource          = api.Resource("nodes")
   115  	pvcResource           = api.Resource("persistentvolumeclaims")
   116  	svcacctResource       = api.Resource("serviceaccounts")
   117  	leaseResource         = coordapi.Resource("leases")
   118  	csiNodeResource       = storage.Resource("csinodes")
   119  	resourceSliceResource = resource.Resource("resourceslices")
   120  )
   121  
   122  // Admit checks the admission policy and triggers corresponding actions
   123  func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
   124  	nodeName, isNode := p.nodeIdentifier.NodeIdentity(a.GetUserInfo())
   125  
   126  	// Our job is just to restrict nodes
   127  	if !isNode {
   128  		return nil
   129  	}
   130  
   131  	if len(nodeName) == 0 {
   132  		// disallow requests we cannot match to a particular node
   133  		return admission.NewForbidden(a, fmt.Errorf("could not determine node from user %q", a.GetUserInfo().GetName()))
   134  	}
   135  
   136  	// TODO: if node doesn't exist and this isn't a create node request, then reject.
   137  
   138  	switch a.GetResource().GroupResource() {
   139  	case podResource:
   140  		switch a.GetSubresource() {
   141  		case "":
   142  			return p.admitPod(nodeName, a)
   143  		case "status":
   144  			return p.admitPodStatus(nodeName, a)
   145  		case "eviction":
   146  			return p.admitPodEviction(nodeName, a)
   147  		default:
   148  			return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %q, only 'status' and 'eviction' are allowed", a.GetSubresource()))
   149  		}
   150  
   151  	case nodeResource:
   152  		return p.admitNode(nodeName, a)
   153  
   154  	case pvcResource:
   155  		switch a.GetSubresource() {
   156  		case "status":
   157  			return p.admitPVCStatus(nodeName, a)
   158  		default:
   159  			return admission.NewForbidden(a, fmt.Errorf("may only update PVC status"))
   160  		}
   161  
   162  	case svcacctResource:
   163  		return p.admitServiceAccount(nodeName, a)
   164  
   165  	case leaseResource:
   166  		return p.admitLease(nodeName, a)
   167  
   168  	case csiNodeResource:
   169  		return p.admitCSINode(nodeName, a)
   170  
   171  	case resourceSliceResource:
   172  		return p.admitResourceSlice(nodeName, a)
   173  
   174  	default:
   175  		return nil
   176  	}
   177  }
   178  
   179  // admitPod allows creating or deleting a pod if it is assigned to the
   180  // current node and fulfills related criteria.
   181  func (p *Plugin) admitPod(nodeName string, a admission.Attributes) error {
   182  	switch a.GetOperation() {
   183  	case admission.Create:
   184  		return p.admitPodCreate(nodeName, a)
   185  
   186  	case admission.Delete:
   187  		// get the existing pod
   188  		existingPod, err := p.podsGetter.Pods(a.GetNamespace()).Get(a.GetName())
   189  		if apierrors.IsNotFound(err) {
   190  			return err
   191  		}
   192  		if err != nil {
   193  			return admission.NewForbidden(a, err)
   194  		}
   195  		// only allow a node to delete a pod bound to itself
   196  		if existingPod.Spec.NodeName != nodeName {
   197  			return admission.NewForbidden(a, fmt.Errorf("node %q can only delete pods with spec.nodeName set to itself", nodeName))
   198  		}
   199  		return nil
   200  
   201  	default:
   202  		return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q, node %q can only create and delete mirror pods", a.GetOperation(), nodeName))
   203  	}
   204  }
   205  
   206  func (p *Plugin) admitPodCreate(nodeName string, a admission.Attributes) error {
   207  	// require a pod object
   208  	pod, ok := a.GetObject().(*api.Pod)
   209  	if !ok {
   210  		return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   211  	}
   212  
   213  	// only allow nodes to create mirror pods
   214  	if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; !isMirrorPod {
   215  		return admission.NewForbidden(a, fmt.Errorf("pod does not have %q annotation, node %q can only create mirror pods", api.MirrorPodAnnotationKey, nodeName))
   216  	}
   217  
   218  	// only allow nodes to create a pod bound to itself
   219  	if pod.Spec.NodeName != nodeName {
   220  		return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with spec.nodeName set to itself", nodeName))
   221  	}
   222  	if len(pod.OwnerReferences) > 1 {
   223  		return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with a single owner reference set to itself", nodeName))
   224  	}
   225  	if len(pod.OwnerReferences) == 0 {
   226  		return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with an owner reference set to itself", nodeName))
   227  	}
   228  	if len(pod.OwnerReferences) == 1 {
   229  		owner := pod.OwnerReferences[0]
   230  		if owner.APIVersion != v1.SchemeGroupVersion.String() ||
   231  			owner.Kind != "Node" ||
   232  			owner.Name != nodeName {
   233  			return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with an owner reference set to itself", nodeName))
   234  		}
   235  		if owner.Controller == nil || !*owner.Controller {
   236  			return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with a controller owner reference set to itself", nodeName))
   237  		}
   238  		if owner.BlockOwnerDeletion != nil && *owner.BlockOwnerDeletion {
   239  			return admission.NewForbidden(a, fmt.Errorf("node %q must not set blockOwnerDeletion on an owner reference", nodeName))
   240  		}
   241  
   242  		// Verify the node UID.
   243  		node, err := p.nodesGetter.Get(nodeName)
   244  		if apierrors.IsNotFound(err) {
   245  			return err
   246  		}
   247  		if err != nil {
   248  			return admission.NewForbidden(a, fmt.Errorf("error looking up node %s to verify uid: %v", nodeName, err))
   249  		}
   250  		if owner.UID != node.UID {
   251  			return admission.NewForbidden(a, fmt.Errorf("node %s UID mismatch: expected %s got %s", nodeName, owner.UID, node.UID))
   252  		}
   253  	}
   254  
   255  	// don't allow a node to create a pod that references any other API objects
   256  	if pod.Spec.ServiceAccountName != "" {
   257  		return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference a service account", nodeName))
   258  	}
   259  	hasSecrets := false
   260  	podutil.VisitPodSecretNames(pod, func(name string) (shouldContinue bool) { hasSecrets = true; return false }, podutil.AllContainers)
   261  	if hasSecrets {
   262  		return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference secrets", nodeName))
   263  	}
   264  	hasConfigMaps := false
   265  	podutil.VisitPodConfigmapNames(pod, func(name string) (shouldContinue bool) { hasConfigMaps = true; return false }, podutil.AllContainers)
   266  	if hasConfigMaps {
   267  		return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference configmaps", nodeName))
   268  	}
   269  
   270  	for _, vol := range pod.Spec.Volumes {
   271  		if vol.VolumeSource.Projected != nil {
   272  			for _, src := range vol.VolumeSource.Projected.Sources {
   273  				if src.ClusterTrustBundle != nil {
   274  					return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference clustertrustbundles", nodeName))
   275  				}
   276  			}
   277  		}
   278  	}
   279  
   280  	for _, v := range pod.Spec.Volumes {
   281  		if v.PersistentVolumeClaim != nil {
   282  			return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference persistentvolumeclaims", nodeName))
   283  		}
   284  	}
   285  
   286  	return nil
   287  }
   288  
   289  // admitPodStatus allows to update the status of a pod if it is
   290  // assigned to the current node.
   291  func (p *Plugin) admitPodStatus(nodeName string, a admission.Attributes) error {
   292  	switch a.GetOperation() {
   293  	case admission.Update:
   294  		// require an existing pod
   295  		oldPod, ok := a.GetOldObject().(*api.Pod)
   296  		if !ok {
   297  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
   298  		}
   299  		// only allow a node to update status of a pod bound to itself
   300  		if oldPod.Spec.NodeName != nodeName {
   301  			return admission.NewForbidden(a, fmt.Errorf("node %q can only update pod status for pods with spec.nodeName set to itself", nodeName))
   302  		}
   303  		newPod, ok := a.GetObject().(*api.Pod)
   304  		if !ok {
   305  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   306  		}
   307  		if !labels.Equals(oldPod.Labels, newPod.Labels) {
   308  			return admission.NewForbidden(a, fmt.Errorf("node %q cannot update labels through pod status", nodeName))
   309  		}
   310  		if !resourceClaimStatusesEqual(oldPod.Status.ResourceClaimStatuses, newPod.Status.ResourceClaimStatuses) {
   311  			return admission.NewForbidden(a, fmt.Errorf("node %q cannot update resource claim statues", nodeName))
   312  		}
   313  		return nil
   314  
   315  	default:
   316  		return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
   317  	}
   318  }
   319  
   320  func resourceClaimStatusesEqual(statusA, statusB []api.PodResourceClaimStatus) bool {
   321  	if len(statusA) != len(statusB) {
   322  		return false
   323  	}
   324  	// In most cases, status entries only get added once and not modified.
   325  	// But this cannot be guaranteed, so for the sake of correctness in all
   326  	// cases this code here has to check.
   327  	for i := range statusA {
   328  		if statusA[i].Name != statusB[i].Name {
   329  			return false
   330  		}
   331  		claimNameA := statusA[i].ResourceClaimName
   332  		claimNameB := statusB[i].ResourceClaimName
   333  		if (claimNameA == nil) != (claimNameB == nil) {
   334  			return false
   335  		}
   336  		if claimNameA != nil && *claimNameA != *claimNameB {
   337  			return false
   338  		}
   339  	}
   340  	return true
   341  }
   342  
   343  // admitPodEviction allows to evict a pod if it is assigned to the current node.
   344  func (p *Plugin) admitPodEviction(nodeName string, a admission.Attributes) error {
   345  	switch a.GetOperation() {
   346  	case admission.Create:
   347  		// require eviction to an existing pod object
   348  		eviction, ok := a.GetObject().(*policy.Eviction)
   349  		if !ok {
   350  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   351  		}
   352  		// use pod name from the admission attributes, if set, rather than from the submitted Eviction object
   353  		podName := a.GetName()
   354  		if len(podName) == 0 {
   355  			if len(eviction.Name) == 0 {
   356  				return admission.NewForbidden(a, fmt.Errorf("could not determine pod from request data"))
   357  			}
   358  			podName = eviction.Name
   359  		}
   360  		// get the existing pod
   361  		existingPod, err := p.podsGetter.Pods(a.GetNamespace()).Get(podName)
   362  		if apierrors.IsNotFound(err) {
   363  			return err
   364  		}
   365  		if err != nil {
   366  			return admission.NewForbidden(a, err)
   367  		}
   368  		// only allow a node to evict a pod bound to itself
   369  		if existingPod.Spec.NodeName != nodeName {
   370  			return admission.NewForbidden(a, fmt.Errorf("node %s can only evict pods with spec.nodeName set to itself", nodeName))
   371  		}
   372  		return nil
   373  
   374  	default:
   375  		return admission.NewForbidden(a, fmt.Errorf("unexpected operation %s", a.GetOperation()))
   376  	}
   377  }
   378  
   379  func (p *Plugin) admitPVCStatus(nodeName string, a admission.Attributes) error {
   380  	switch a.GetOperation() {
   381  	case admission.Update:
   382  		oldPVC, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
   383  		if !ok {
   384  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
   385  		}
   386  
   387  		newPVC, ok := a.GetObject().(*api.PersistentVolumeClaim)
   388  		if !ok {
   389  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   390  		}
   391  
   392  		// make copies for comparison
   393  		oldPVC = oldPVC.DeepCopy()
   394  		newPVC = newPVC.DeepCopy()
   395  
   396  		// zero out resourceVersion to avoid comparing differences,
   397  		// since the new object could leave it empty to indicate an unconditional update
   398  		oldPVC.ObjectMeta.ResourceVersion = ""
   399  		newPVC.ObjectMeta.ResourceVersion = ""
   400  
   401  		oldPVC.Status.Capacity = nil
   402  		newPVC.Status.Capacity = nil
   403  
   404  		oldPVC.Status.Conditions = nil
   405  		newPVC.Status.Conditions = nil
   406  
   407  		if p.expansionRecoveryEnabled {
   408  			oldPVC.Status.AllocatedResourceStatuses = nil
   409  			newPVC.Status.AllocatedResourceStatuses = nil
   410  
   411  			oldPVC.Status.AllocatedResources = nil
   412  			newPVC.Status.AllocatedResources = nil
   413  		}
   414  
   415  		// TODO(apelisse): We don't have a good mechanism to
   416  		// verify that only the things that should have changed
   417  		// have changed. Ignore it for now.
   418  		oldPVC.ObjectMeta.ManagedFields = nil
   419  		newPVC.ObjectMeta.ManagedFields = nil
   420  
   421  		// ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc
   422  		if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) {
   423  			return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update fields other than status.quantity and status.conditions: %v", nodeName, cmp.Diff(oldPVC, newPVC)))
   424  		}
   425  
   426  		return nil
   427  
   428  	default:
   429  		return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
   430  	}
   431  }
   432  
   433  func (p *Plugin) admitNode(nodeName string, a admission.Attributes) error {
   434  	requestedName := a.GetName()
   435  
   436  	if requestedName != nodeName {
   437  		return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to modify node %q", nodeName, requestedName))
   438  	}
   439  
   440  	if a.GetOperation() == admission.Create {
   441  		node, ok := a.GetObject().(*api.Node)
   442  		if !ok {
   443  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   444  		}
   445  
   446  		// Don't allow a node to create its Node API object with the config source set.
   447  		// We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
   448  		if node.Spec.ConfigSource != nil {
   449  			return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to create pods with a non-nil configSource", nodeName))
   450  		}
   451  
   452  		// Don't allow a node to register with labels outside the allowed set.
   453  		// This would allow a node to add or modify its labels in a way that would let it steer privileged workloads to itself.
   454  		modifiedLabels := getModifiedLabels(node.Labels, nil)
   455  		if forbiddenLabels := p.getForbiddenLabels(modifiedLabels); len(forbiddenLabels) > 0 {
   456  			return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to set the following labels: %s", nodeName, strings.Join(forbiddenLabels.List(), ", ")))
   457  		}
   458  	}
   459  
   460  	if a.GetOperation() == admission.Update {
   461  		node, ok := a.GetObject().(*api.Node)
   462  		if !ok {
   463  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   464  		}
   465  		oldNode, ok := a.GetOldObject().(*api.Node)
   466  		if !ok {
   467  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   468  		}
   469  
   470  		// Don't allow a node to update the config source on its Node API object.
   471  		// We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
   472  		// We only do the check if the new node's configSource is non-nil; old kubelets might drop the field during a status update.
   473  		if node.Spec.ConfigSource != nil && !apiequality.Semantic.DeepEqual(node.Spec.ConfigSource, oldNode.Spec.ConfigSource) {
   474  			return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update configSource to a new non-nil configSource", nodeName))
   475  		}
   476  
   477  		// Don't allow a node to update its own taints. This would allow a node to remove or modify its
   478  		// taints in a way that would let it steer disallowed workloads to itself.
   479  		if !apiequality.Semantic.DeepEqual(node.Spec.Taints, oldNode.Spec.Taints) {
   480  			return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to modify taints", nodeName))
   481  		}
   482  
   483  		// Don't allow a node to update labels outside the allowed set.
   484  		// This would allow a node to add or modify its labels in a way that would let it steer privileged workloads to itself.
   485  		modifiedLabels := getModifiedLabels(node.Labels, oldNode.Labels)
   486  		if forbiddenUpdateLabels := p.getForbiddenLabels(modifiedLabels); len(forbiddenUpdateLabels) > 0 {
   487  			return admission.NewForbidden(a, fmt.Errorf("is not allowed to modify labels: %s", strings.Join(forbiddenUpdateLabels.List(), ", ")))
   488  		}
   489  	}
   490  
   491  	return nil
   492  }
   493  
   494  // getModifiedLabels returns the set of label keys that are different between the two maps
   495  func getModifiedLabels(a, b map[string]string) sets.String {
   496  	modified := sets.NewString()
   497  	for k, v1 := range a {
   498  		if v2, ok := b[k]; !ok || v1 != v2 {
   499  			modified.Insert(k)
   500  		}
   501  	}
   502  	for k, v1 := range b {
   503  		if v2, ok := a[k]; !ok || v1 != v2 {
   504  			modified.Insert(k)
   505  		}
   506  	}
   507  	return modified
   508  }
   509  
   510  func isKubernetesLabel(key string) bool {
   511  	namespace := getLabelNamespace(key)
   512  	if namespace == "kubernetes.io" || strings.HasSuffix(namespace, ".kubernetes.io") {
   513  		return true
   514  	}
   515  	if namespace == "k8s.io" || strings.HasSuffix(namespace, ".k8s.io") {
   516  		return true
   517  	}
   518  	return false
   519  }
   520  
   521  func getLabelNamespace(key string) string {
   522  	if parts := strings.SplitN(key, "/", 2); len(parts) == 2 {
   523  		return parts[0]
   524  	}
   525  	return ""
   526  }
   527  
   528  // getForbiddenLabels returns the set of labels that may not be added, removed, or modified by the node on create or update.
   529  func (p *Plugin) getForbiddenLabels(modifiedLabels sets.String) sets.String {
   530  	if len(modifiedLabels) == 0 {
   531  		return nil
   532  	}
   533  
   534  	forbiddenLabels := sets.NewString()
   535  	for label := range modifiedLabels {
   536  		namespace := getLabelNamespace(label)
   537  		// forbid kubelets from setting node-restriction labels
   538  		if namespace == v1.LabelNamespaceNodeRestriction || strings.HasSuffix(namespace, "."+v1.LabelNamespaceNodeRestriction) {
   539  			forbiddenLabels.Insert(label)
   540  		}
   541  		// forbid kubelets from setting unknown kubernetes.io and k8s.io labels on update
   542  		if isKubernetesLabel(label) && !kubeletapis.IsKubeletLabel(label) {
   543  			// TODO: defer to label policy once available
   544  			forbiddenLabels.Insert(label)
   545  		}
   546  	}
   547  	return forbiddenLabels
   548  }
   549  
   550  func (p *Plugin) admitServiceAccount(nodeName string, a admission.Attributes) error {
   551  	if a.GetOperation() != admission.Create {
   552  		return nil
   553  	}
   554  	if a.GetSubresource() != "token" {
   555  		return nil
   556  	}
   557  	tr, ok := a.GetObject().(*authenticationapi.TokenRequest)
   558  	if !ok {
   559  		return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   560  	}
   561  
   562  	// TokenRequests from a node must have a pod binding. That pod must be
   563  	// scheduled on the node.
   564  	ref := tr.Spec.BoundObjectRef
   565  	if ref == nil ||
   566  		ref.APIVersion != "v1" ||
   567  		ref.Kind != "Pod" ||
   568  		ref.Name == "" {
   569  		return admission.NewForbidden(a, fmt.Errorf("node requested token not bound to a pod"))
   570  	}
   571  	if ref.UID == "" {
   572  		return admission.NewForbidden(a, fmt.Errorf("node requested token with a pod binding without a uid"))
   573  	}
   574  	pod, err := p.podsGetter.Pods(a.GetNamespace()).Get(ref.Name)
   575  	if apierrors.IsNotFound(err) {
   576  		return err
   577  	}
   578  	if err != nil {
   579  		return admission.NewForbidden(a, err)
   580  	}
   581  	if ref.UID != pod.UID {
   582  		return admission.NewForbidden(a, fmt.Errorf("the UID in the bound object reference (%s) does not match the UID in record. The object might have been deleted and then recreated", ref.UID))
   583  	}
   584  	if pod.Spec.NodeName != nodeName {
   585  		return admission.NewForbidden(a, fmt.Errorf("node requested token bound to a pod scheduled on a different node"))
   586  	}
   587  
   588  	// Note: A token may only be bound to one object at a time. By requiring
   589  	// the Pod binding, noderestriction eliminates the opportunity to spoof
   590  	// a Node binding. Instead, kube-apiserver automatically infers and sets
   591  	// the Node binding when it receives a Pod binding. See:
   592  	// https://github.com/kubernetes/kubernetes/issues/121723 for more info.
   593  
   594  	return nil
   595  }
   596  
   597  func (p *Plugin) admitLease(nodeName string, a admission.Attributes) error {
   598  	// the request must be against the system namespace reserved for node leases
   599  	if a.GetNamespace() != api.NamespaceNodeLease {
   600  		return admission.NewForbidden(a, fmt.Errorf("can only access leases in the %q system namespace", api.NamespaceNodeLease))
   601  	}
   602  
   603  	// the request must come from a node with the same name as the lease
   604  	if a.GetOperation() == admission.Create {
   605  		// a.GetName() won't return the name on create, so we drill down to the proposed object
   606  		lease, ok := a.GetObject().(*coordapi.Lease)
   607  		if !ok {
   608  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   609  		}
   610  		if lease.Name != nodeName {
   611  			return admission.NewForbidden(a, fmt.Errorf("can only access node lease with the same name as the requesting node"))
   612  		}
   613  	} else {
   614  		if a.GetName() != nodeName {
   615  			return admission.NewForbidden(a, fmt.Errorf("can only access node lease with the same name as the requesting node"))
   616  		}
   617  	}
   618  
   619  	return nil
   620  }
   621  
   622  func (p *Plugin) admitCSINode(nodeName string, a admission.Attributes) error {
   623  	// the request must come from a node with the same name as the CSINode object
   624  	if a.GetOperation() == admission.Create {
   625  		// a.GetName() won't return the name on create, so we drill down to the proposed object
   626  		accessor, err := meta.Accessor(a.GetObject())
   627  		if err != nil {
   628  			return admission.NewForbidden(a, fmt.Errorf("unable to access the object name"))
   629  		}
   630  		if accessor.GetName() != nodeName {
   631  			return admission.NewForbidden(a, fmt.Errorf("can only access CSINode with the same name as the requesting node"))
   632  		}
   633  	} else {
   634  		if a.GetName() != nodeName {
   635  			return admission.NewForbidden(a, fmt.Errorf("can only access CSINode with the same name as the requesting node"))
   636  		}
   637  	}
   638  
   639  	return nil
   640  }
   641  
   642  func (p *Plugin) admitResourceSlice(nodeName string, a admission.Attributes) error {
   643  	// The create request must come from a node with the same name as the NodeName field.
   644  	// Other requests gets checked by the node authorizer.
   645  	if a.GetOperation() == admission.Create {
   646  		slice, ok := a.GetObject().(*resource.ResourceSlice)
   647  		if !ok {
   648  			return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
   649  		}
   650  
   651  		if slice.NodeName != nodeName {
   652  			return admission.NewForbidden(a, errors.New("can only create ResourceSlice with the same NodeName as the requesting node"))
   653  		}
   654  	}
   655  
   656  	return nil
   657  }
   658  

View as plain text