...

Source file src/k8s.io/kubernetes/plugin/pkg/auth/authorizer/node/node_authorizer.go

Documentation: k8s.io/kubernetes/plugin/pkg/auth/authorizer/node

     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 node
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"k8s.io/klog/v2"
    24  
    25  	rbacv1 "k8s.io/api/rbac/v1"
    26  	"k8s.io/apimachinery/pkg/runtime/schema"
    27  	"k8s.io/apiserver/pkg/authentication/user"
    28  	"k8s.io/apiserver/pkg/authorization/authorizer"
    29  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    30  	"k8s.io/component-base/featuregate"
    31  	coordapi "k8s.io/kubernetes/pkg/apis/coordination"
    32  	api "k8s.io/kubernetes/pkg/apis/core"
    33  	resourceapi "k8s.io/kubernetes/pkg/apis/resource"
    34  	storageapi "k8s.io/kubernetes/pkg/apis/storage"
    35  	"k8s.io/kubernetes/pkg/auth/nodeidentifier"
    36  	"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
    37  	"k8s.io/kubernetes/third_party/forked/gonum/graph"
    38  	"k8s.io/kubernetes/third_party/forked/gonum/graph/traverse"
    39  )
    40  
    41  // NodeAuthorizer authorizes requests from kubelets, with the following logic:
    42  //  1. If a request is not from a node (NodeIdentity() returns isNode=false), reject
    43  //  2. If a specific node cannot be identified (NodeIdentity() returns nodeName=""), reject
    44  //  3. If a request is for a secret, configmap, persistent volume, resource claim, or persistent volume claim, reject unless the verb is get, and the requested object is related to the requesting node:
    45  //     node <- configmap
    46  //     node <- pod
    47  //     node <- pod <- secret
    48  //     node <- pod <- configmap
    49  //     node <- pod <- pvc
    50  //     node <- pod <- pvc <- pv
    51  //     node <- pod <- pvc <- pv <- secret
    52  //     node <- pod <- ResourceClaim
    53  //  4. If a request is for a resourceslice, then authorize access if there is an
    54  //     edge from the existing slice object to the node, which is the case if the
    55  //     existing object has the node in its NodeName field. For create, the access gets
    56  //     granted because the noderestriction admission plugin checks that the NodeName
    57  //     is set to the node.
    58  //  5. For other resources, authorize all nodes uniformly using statically defined rules
    59  type NodeAuthorizer struct {
    60  	graph      *Graph
    61  	identifier nodeidentifier.NodeIdentifier
    62  	nodeRules  []rbacv1.PolicyRule
    63  
    64  	// allows overriding for testing
    65  	features featuregate.FeatureGate
    66  }
    67  
    68  var _ = authorizer.Authorizer(&NodeAuthorizer{})
    69  var _ = authorizer.RuleResolver(&NodeAuthorizer{})
    70  
    71  // NewAuthorizer returns a new node authorizer
    72  func NewAuthorizer(graph *Graph, identifier nodeidentifier.NodeIdentifier, rules []rbacv1.PolicyRule) *NodeAuthorizer {
    73  	return &NodeAuthorizer{
    74  		graph:      graph,
    75  		identifier: identifier,
    76  		nodeRules:  rules,
    77  		features:   utilfeature.DefaultFeatureGate,
    78  	}
    79  }
    80  
    81  var (
    82  	configMapResource     = api.Resource("configmaps")
    83  	secretResource        = api.Resource("secrets")
    84  	resourceSlice         = resourceapi.Resource("resourceslices")
    85  	pvcResource           = api.Resource("persistentvolumeclaims")
    86  	pvResource            = api.Resource("persistentvolumes")
    87  	resourceClaimResource = resourceapi.Resource("resourceclaims")
    88  	vaResource            = storageapi.Resource("volumeattachments")
    89  	svcAcctResource       = api.Resource("serviceaccounts")
    90  	leaseResource         = coordapi.Resource("leases")
    91  	csiNodeResource       = storageapi.Resource("csinodes")
    92  )
    93  
    94  func (r *NodeAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
    95  	if _, isNode := r.identifier.NodeIdentity(user); isNode {
    96  		// indicate nodes do not have fully enumerated permissions
    97  		return nil, nil, true, fmt.Errorf("node authorizer does not support user rule resolution")
    98  	}
    99  	return nil, nil, false, nil
   100  }
   101  
   102  func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   103  	nodeName, isNode := r.identifier.NodeIdentity(attrs.GetUser())
   104  	if !isNode {
   105  		// reject requests from non-nodes
   106  		return authorizer.DecisionNoOpinion, "", nil
   107  	}
   108  	if len(nodeName) == 0 {
   109  		// reject requests from unidentifiable nodes
   110  		klog.V(2).Infof("NODE DENY: unknown node for user %q", attrs.GetUser().GetName())
   111  		return authorizer.DecisionNoOpinion, fmt.Sprintf("unknown node for user %q", attrs.GetUser().GetName()), nil
   112  	}
   113  
   114  	// subdivide access to specific resources
   115  	if attrs.IsResourceRequest() {
   116  		requestResource := schema.GroupResource{Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()}
   117  		switch requestResource {
   118  		case secretResource:
   119  			return r.authorizeReadNamespacedObject(nodeName, secretVertexType, attrs)
   120  		case configMapResource:
   121  			return r.authorizeReadNamespacedObject(nodeName, configMapVertexType, attrs)
   122  		case pvcResource:
   123  			if attrs.GetSubresource() == "status" {
   124  				return r.authorizeStatusUpdate(nodeName, pvcVertexType, attrs)
   125  			}
   126  			return r.authorizeGet(nodeName, pvcVertexType, attrs)
   127  		case pvResource:
   128  			return r.authorizeGet(nodeName, pvVertexType, attrs)
   129  		case resourceClaimResource:
   130  			return r.authorizeGet(nodeName, resourceClaimVertexType, attrs)
   131  		case vaResource:
   132  			return r.authorizeGet(nodeName, vaVertexType, attrs)
   133  		case svcAcctResource:
   134  			return r.authorizeCreateToken(nodeName, serviceAccountVertexType, attrs)
   135  		case leaseResource:
   136  			return r.authorizeLease(nodeName, attrs)
   137  		case csiNodeResource:
   138  			return r.authorizeCSINode(nodeName, attrs)
   139  		case resourceSlice:
   140  			return r.authorizeResourceSlice(nodeName, attrs)
   141  		}
   142  
   143  	}
   144  
   145  	// Access to other resources is not subdivided, so just evaluate against the statically defined node rules
   146  	if rbac.RulesAllow(attrs, r.nodeRules...) {
   147  		return authorizer.DecisionAllow, "", nil
   148  	}
   149  	return authorizer.DecisionNoOpinion, "", nil
   150  }
   151  
   152  // authorizeStatusUpdate authorizes get/update/patch requests to status subresources of the specified type if they are related to the specified node
   153  func (r *NodeAuthorizer) authorizeStatusUpdate(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   154  	switch attrs.GetVerb() {
   155  	case "update", "patch":
   156  		// ok
   157  	default:
   158  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   159  		return authorizer.DecisionNoOpinion, "can only get/update/patch this type", nil
   160  	}
   161  
   162  	if attrs.GetSubresource() != "status" {
   163  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   164  		return authorizer.DecisionNoOpinion, "can only update status subresource", nil
   165  	}
   166  
   167  	return r.authorize(nodeName, startingType, attrs)
   168  }
   169  
   170  // authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node
   171  func (r *NodeAuthorizer) authorizeGet(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   172  	if attrs.GetVerb() != "get" {
   173  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   174  		return authorizer.DecisionNoOpinion, "can only get individual resources of this type", nil
   175  	}
   176  	if len(attrs.GetSubresource()) > 0 {
   177  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   178  		return authorizer.DecisionNoOpinion, "cannot get subresource", nil
   179  	}
   180  	return r.authorize(nodeName, startingType, attrs)
   181  }
   182  
   183  // authorizeReadNamespacedObject authorizes "get", "list" and "watch" requests to single objects of a
   184  // specified types if they are related to the specified node.
   185  func (r *NodeAuthorizer) authorizeReadNamespacedObject(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   186  	switch attrs.GetVerb() {
   187  	case "get", "list", "watch":
   188  		//ok
   189  	default:
   190  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   191  		return authorizer.DecisionNoOpinion, "can only read resources of this type", nil
   192  	}
   193  
   194  	if len(attrs.GetSubresource()) > 0 {
   195  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   196  		return authorizer.DecisionNoOpinion, "cannot read subresource", nil
   197  	}
   198  	if len(attrs.GetNamespace()) == 0 {
   199  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   200  		return authorizer.DecisionNoOpinion, "can only read namespaced object of this type", nil
   201  	}
   202  	return r.authorize(nodeName, startingType, attrs)
   203  }
   204  
   205  func (r *NodeAuthorizer) authorize(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   206  	if len(attrs.GetName()) == 0 {
   207  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   208  		return authorizer.DecisionNoOpinion, "No Object name found", nil
   209  	}
   210  
   211  	ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
   212  	if err != nil {
   213  		klog.V(2).InfoS("NODE DENY", "err", err)
   214  		return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node '%s' and this object", nodeName), nil
   215  	}
   216  	if !ok {
   217  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   218  		return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node '%s' and this object", nodeName), nil
   219  	}
   220  	return authorizer.DecisionAllow, "", nil
   221  }
   222  
   223  // authorizeCreateToken authorizes "create" requests to serviceaccounts 'token'
   224  // subresource of pods running on a node
   225  func (r *NodeAuthorizer) authorizeCreateToken(nodeName string, startingType vertexType, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   226  	if attrs.GetVerb() != "create" || len(attrs.GetName()) == 0 {
   227  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   228  		return authorizer.DecisionNoOpinion, "can only create tokens for individual service accounts", nil
   229  	}
   230  
   231  	if attrs.GetSubresource() != "token" {
   232  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   233  		return authorizer.DecisionNoOpinion, "can only create token subresource of serviceaccount", nil
   234  	}
   235  
   236  	ok, err := r.hasPathFrom(nodeName, startingType, attrs.GetNamespace(), attrs.GetName())
   237  	if err != nil {
   238  		klog.V(2).Infof("NODE DENY: %v", err)
   239  		return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node '%s' and this object", nodeName), nil
   240  	}
   241  	if !ok {
   242  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   243  		return authorizer.DecisionNoOpinion, fmt.Sprintf("no relationship found between node '%s' and this object", nodeName), nil
   244  	}
   245  	return authorizer.DecisionAllow, "", nil
   246  }
   247  
   248  // authorizeLease authorizes node requests to coordination.k8s.io/leases.
   249  func (r *NodeAuthorizer) authorizeLease(nodeName string, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   250  	// allowed verbs: get, create, update, patch, delete
   251  	verb := attrs.GetVerb()
   252  	switch verb {
   253  	case "get", "create", "update", "patch", "delete":
   254  		//ok
   255  	default:
   256  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   257  		return authorizer.DecisionNoOpinion, "can only get, create, update, patch, or delete a node lease", nil
   258  	}
   259  
   260  	// the request must be against the system namespace reserved for node leases
   261  	if attrs.GetNamespace() != api.NamespaceNodeLease {
   262  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   263  		return authorizer.DecisionNoOpinion, fmt.Sprintf("can only access leases in the %q system namespace", api.NamespaceNodeLease), nil
   264  	}
   265  
   266  	// the request must come from a node with the same name as the lease
   267  	// note we skip this check for create, since the authorizer doesn't know the name on create
   268  	// the noderestriction admission plugin is capable of performing this check at create time
   269  	if verb != "create" && attrs.GetName() != nodeName {
   270  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   271  		return authorizer.DecisionNoOpinion, "can only access node lease with the same name as the requesting node", nil
   272  	}
   273  
   274  	return authorizer.DecisionAllow, "", nil
   275  }
   276  
   277  // authorizeCSINode authorizes node requests to CSINode storage.k8s.io/csinodes
   278  func (r *NodeAuthorizer) authorizeCSINode(nodeName string, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   279  	// allowed verbs: get, create, update, patch, delete
   280  	verb := attrs.GetVerb()
   281  	switch verb {
   282  	case "get", "create", "update", "patch", "delete":
   283  		//ok
   284  	default:
   285  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   286  		return authorizer.DecisionNoOpinion, "can only get, create, update, patch, or delete a CSINode", nil
   287  	}
   288  
   289  	if len(attrs.GetSubresource()) > 0 {
   290  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   291  		return authorizer.DecisionNoOpinion, "cannot authorize CSINode subresources", nil
   292  	}
   293  
   294  	// the request must come from a node with the same name as the CSINode
   295  	// note we skip this check for create, since the authorizer doesn't know the name on create
   296  	// the noderestriction admission plugin is capable of performing this check at create time
   297  	if verb != "create" && attrs.GetName() != nodeName {
   298  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   299  		return authorizer.DecisionNoOpinion, "can only access CSINode with the same name as the requesting node", nil
   300  	}
   301  
   302  	return authorizer.DecisionAllow, "", nil
   303  }
   304  
   305  // authorizeResourceSlice authorizes node requests to ResourceSlice resource.k8s.io/resourceslices
   306  func (r *NodeAuthorizer) authorizeResourceSlice(nodeName string, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
   307  	if len(attrs.GetSubresource()) > 0 {
   308  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   309  		return authorizer.DecisionNoOpinion, "cannot authorize ResourceSlice subresources", nil
   310  	}
   311  
   312  	// allowed verbs: get, create, update, patch, delete
   313  	verb := attrs.GetVerb()
   314  	switch verb {
   315  	case "get", "create", "update", "patch", "delete":
   316  		// Okay, but check individual object permission below.
   317  	case "watch", "list":
   318  		// Okay. The kubelet is trusted to use a filter for its own objects.
   319  		return authorizer.DecisionAllow, "", nil
   320  	default:
   321  		klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
   322  		return authorizer.DecisionNoOpinion, "can only get, create, update, patch, or delete a ResourceSlice", nil
   323  	}
   324  
   325  	// The request must come from a node with the same name as the ResourceSlice.NodeName field.
   326  	//
   327  	// For create, the noderestriction admission plugin is performing this check.
   328  	// Here we don't have access to the content of the new object.
   329  	if verb == "create" {
   330  		return authorizer.DecisionAllow, "", nil
   331  	}
   332  
   333  	// For any other verb, checking the existing object must have established that access
   334  	// is allowed by recording a graph edge.
   335  	return r.authorize(nodeName, sliceVertexType, attrs)
   336  }
   337  
   338  // hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node
   339  func (r *NodeAuthorizer) hasPathFrom(nodeName string, startingType vertexType, startingNamespace, startingName string) (bool, error) {
   340  	r.graph.lock.RLock()
   341  	defer r.graph.lock.RUnlock()
   342  
   343  	nodeVertex, exists := r.graph.getVertex_rlocked(nodeVertexType, "", nodeName)
   344  	if !exists {
   345  		return false, fmt.Errorf("unknown node '%s' cannot get %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
   346  	}
   347  
   348  	startingVertex, exists := r.graph.getVertex_rlocked(startingType, startingNamespace, startingName)
   349  	if !exists {
   350  		return false, fmt.Errorf("node '%s' cannot get unknown %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
   351  	}
   352  
   353  	// Fast check to see if we know of a destination edge
   354  	if r.graph.destinationEdgeIndex[startingVertex.ID()].has(nodeVertex.ID()) {
   355  		return true, nil
   356  	}
   357  
   358  	found := false
   359  	traversal := &traverse.VisitingDepthFirst{
   360  		EdgeFilter: func(edge graph.Edge) bool {
   361  			if destinationEdge, ok := edge.(*destinationEdge); ok {
   362  				if destinationEdge.DestinationID() != nodeVertex.ID() {
   363  					// Don't follow edges leading to other nodes
   364  					return false
   365  				}
   366  				// We found an edge leading to the node we want
   367  				found = true
   368  			}
   369  			// Visit this edge
   370  			return true
   371  		},
   372  	}
   373  	traversal.Walk(r.graph.graph, startingVertex, func(n graph.Node) bool {
   374  		if n.ID() == nodeVertex.ID() {
   375  			// We found the node we want
   376  			found = true
   377  		}
   378  		// Stop visiting if we've found the node we want
   379  		return found
   380  	})
   381  	if !found {
   382  		return false, fmt.Errorf("node '%s' cannot get %s %s/%s, no relationship to this object was found in the node authorizer graph", nodeName, vertexTypes[startingType], startingNamespace, startingName)
   383  	}
   384  	return true, nil
   385  }
   386  

View as plain text