2017-05-30 15:15:38 -04:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package node
import (
2019-09-24 10:06:32 -04:00
"context"
2017-05-30 15:15:38 -04:00
"fmt"
2020-04-17 15:25:06 -04:00
"k8s.io/klog/v2"
2017-05-30 15:15:38 -04:00
2018-04-19 07:57:45 -04:00
rbacv1 "k8s.io/api/rbac/v1"
2017-05-30 15:15:38 -04:00
"k8s.io/apimachinery/pkg/runtime/schema"
2024-06-27 00:13:09 -04:00
"k8s.io/apimachinery/pkg/selection"
2020-05-12 13:40:25 -04:00
"k8s.io/apiserver/pkg/authentication/user"
2017-05-30 15:15:38 -04:00
"k8s.io/apiserver/pkg/authorization/authorizer"
2017-11-20 12:57:24 -05:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
2019-03-04 12:46:52 -05:00
"k8s.io/component-base/featuregate"
2024-08-30 03:47:15 -04:00
certsapi "k8s.io/kubernetes/pkg/apis/certificates"
2018-06-21 17:24:59 -04:00
coordapi "k8s.io/kubernetes/pkg/apis/coordination"
2017-11-08 17:34:54 -05:00
api "k8s.io/kubernetes/pkg/apis/core"
2023-03-03 07:33:20 -05:00
resourceapi "k8s.io/kubernetes/pkg/apis/resource"
2018-01-16 23:39:11 -05:00
storageapi "k8s.io/kubernetes/pkg/apis/storage"
2017-05-30 15:15:38 -04:00
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
2024-06-27 00:13:09 -04:00
"k8s.io/kubernetes/pkg/features"
2017-05-30 15:15:38 -04:00
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
"k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/traverse"
)
// NodeAuthorizer authorizes requests from kubelets, with the following logic:
2022-07-19 20:54:13 -04:00
// 1. If a request is not from a node (NodeIdentity() returns isNode=false), reject
// 2. If a specific node cannot be identified (NodeIdentity() returns nodeName=""), reject
2023-03-03 07:33:20 -05:00
// 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:
2022-07-19 20:54:13 -04:00
// node <- configmap
// node <- pod
// node <- pod <- secret
// node <- pod <- configmap
// node <- pod <- pvc
// node <- pod <- pvc <- pv
// node <- pod <- pvc <- pv <- secret
2023-03-03 07:33:20 -05:00
// node <- pod <- ResourceClaim
2024-08-30 03:47:15 -04:00
// node <- pcr
2024-03-07 04:14:11 -05:00
// 4. If a request is for a resourceslice, then authorize access if there is an
2024-01-24 11:51:40 -05:00
// edge from the existing slice object to the node, which is the case if the
// existing object has the node in its NodeName field. For create, the access gets
// granted because the noderestriction admission plugin checks that the NodeName
// is set to the node.
// 5. For other resources, authorize all nodes uniformly using statically defined rules
2017-05-30 15:15:38 -04:00
type NodeAuthorizer struct {
graph * Graph
identifier nodeidentifier . NodeIdentifier
2018-04-19 07:57:45 -04:00
nodeRules [ ] rbacv1 . PolicyRule
2018-01-16 23:39:11 -05:00
// allows overriding for testing
2019-03-04 12:46:52 -05:00
features featuregate . FeatureGate
2017-05-30 15:15:38 -04:00
}
2020-05-12 13:40:25 -04:00
var _ = authorizer . Authorizer ( & NodeAuthorizer { } )
var _ = authorizer . RuleResolver ( & NodeAuthorizer { } )
2017-06-23 05:53:35 -04:00
// NewAuthorizer returns a new node authorizer
2020-05-12 13:40:25 -04:00
func NewAuthorizer ( graph * Graph , identifier nodeidentifier . NodeIdentifier , rules [ ] rbacv1 . PolicyRule ) * NodeAuthorizer {
2017-05-30 15:15:38 -04:00
return & NodeAuthorizer {
graph : graph ,
identifier : identifier ,
nodeRules : rules ,
2018-01-16 23:39:11 -05:00
features : utilfeature . DefaultFeatureGate ,
2017-05-30 15:15:38 -04:00
}
}
var (
2023-03-03 07:33:20 -05:00
configMapResource = api . Resource ( "configmaps" )
secretResource = api . Resource ( "secrets" )
2024-06-27 00:13:09 -04:00
podResource = api . Resource ( "pods" )
nodeResource = api . Resource ( "nodes" )
2024-03-07 04:14:11 -05:00
resourceSlice = resourceapi . Resource ( "resourceslices" )
2023-03-03 07:33:20 -05:00
pvcResource = api . Resource ( "persistentvolumeclaims" )
pvResource = api . Resource ( "persistentvolumes" )
resourceClaimResource = resourceapi . Resource ( "resourceclaims" )
vaResource = storageapi . Resource ( "volumeattachments" )
svcAcctResource = api . Resource ( "serviceaccounts" )
leaseResource = coordapi . Resource ( "leases" )
csiNodeResource = storageapi . Resource ( "csinodes" )
2024-08-30 03:47:15 -04:00
pcrResource = certsapi . Resource ( "podcertificaterequests" )
2017-05-30 15:15:38 -04:00
)
2024-09-13 05:03:47 -04:00
func ( r * NodeAuthorizer ) RulesFor ( ctx context . Context , user user . Info , namespace string ) ( [ ] authorizer . ResourceRuleInfo , [ ] authorizer . NonResourceRuleInfo , bool , error ) {
2020-05-12 13:40:25 -04:00
if _ , isNode := r . identifier . NodeIdentity ( user ) ; isNode {
// indicate nodes do not have fully enumerated permissions
return nil , nil , true , fmt . Errorf ( "node authorizer does not support user rule resolution" )
}
return nil , nil , false , nil
}
2019-09-24 10:06:32 -04:00
func ( r * NodeAuthorizer ) Authorize ( ctx context . Context , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
2017-05-30 15:15:38 -04:00
nodeName , isNode := r . identifier . NodeIdentity ( attrs . GetUser ( ) )
if ! isNode {
// reject requests from non-nodes
2017-09-29 17:21:40 -04:00
return authorizer . DecisionNoOpinion , "" , nil
2017-05-30 15:15:38 -04:00
}
if len ( nodeName ) == 0 {
// reject requests from unidentifiable nodes
2018-11-09 13:49:10 -05:00
klog . V ( 2 ) . Infof ( "NODE DENY: unknown node for user %q" , attrs . GetUser ( ) . GetName ( ) )
2017-09-29 17:21:40 -04:00
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "unknown node for user %q" , attrs . GetUser ( ) . GetName ( ) ) , nil
2017-05-30 15:15:38 -04:00
}
// subdivide access to specific resources
if attrs . IsResourceRequest ( ) {
requestResource := schema . GroupResource { Group : attrs . GetAPIGroup ( ) , Resource : attrs . GetResource ( ) }
switch requestResource {
case secretResource :
2018-05-06 09:15:32 -04:00
return r . authorizeReadNamespacedObject ( nodeName , secretVertexType , attrs )
2017-05-30 15:15:38 -04:00
case configMapResource :
2018-05-06 09:15:32 -04:00
return r . authorizeReadNamespacedObject ( nodeName , configMapVertexType , attrs )
2017-05-30 15:15:38 -04:00
case pvcResource :
2022-03-24 10:02:47 -04:00
if attrs . GetSubresource ( ) == "status" {
return r . authorizeStatusUpdate ( nodeName , pvcVertexType , attrs )
2017-11-20 12:57:24 -05:00
}
2017-05-30 15:15:38 -04:00
return r . authorizeGet ( nodeName , pvcVertexType , attrs )
case pvResource :
return r . authorizeGet ( nodeName , pvVertexType , attrs )
2023-03-03 07:33:20 -05:00
case resourceClaimResource :
return r . authorizeGet ( nodeName , resourceClaimVertexType , attrs )
2018-01-16 23:39:11 -05:00
case vaResource :
2019-06-23 04:46:49 -04:00
return r . authorizeGet ( nodeName , vaVertexType , attrs )
2017-11-02 13:26:04 -04:00
case svcAcctResource :
2024-11-05 14:04:45 -05:00
return r . authorizeServiceAccount ( nodeName , attrs )
2018-06-21 17:24:59 -04:00
case leaseResource :
2019-10-25 05:57:54 -04:00
return r . authorizeLease ( nodeName , attrs )
2019-03-01 22:14:23 -05:00
case csiNodeResource :
2020-11-13 07:45:33 -05:00
return r . authorizeCSINode ( nodeName , attrs )
2024-03-07 04:14:11 -05:00
case resourceSlice :
return r . authorizeResourceSlice ( nodeName , attrs )
2024-06-27 00:13:09 -04:00
case nodeResource :
if r . features . Enabled ( features . AuthorizeNodeWithSelectors ) {
return r . authorizeNode ( nodeName , attrs )
}
case podResource :
if r . features . Enabled ( features . AuthorizeNodeWithSelectors ) {
return r . authorizePod ( nodeName , attrs )
}
2024-08-30 03:47:15 -04:00
case pcrResource :
if r . features . Enabled ( features . PodCertificateRequest ) && r . features . Enabled ( features . AuthorizeNodeWithSelectors ) {
return r . authorizePodCertificateRequest ( nodeName , attrs )
}
2025-09-16 16:24:07 -04:00
return authorizer . DecisionNoOpinion , "" , nil
2017-05-30 15:15:38 -04:00
}
}
// Access to other resources is not subdivided, so just evaluate against the statically defined node rules
2017-09-29 17:21:40 -04:00
if rbac . RulesAllow ( attrs , r . nodeRules ... ) {
return authorizer . DecisionAllow , "" , nil
}
return authorizer . DecisionNoOpinion , "" , nil
2017-05-30 15:15:38 -04:00
}
2017-11-20 12:57:24 -05:00
// authorizeStatusUpdate authorizes get/update/patch requests to status subresources of the specified type if they are related to the specified node
func ( r * NodeAuthorizer ) authorizeStatusUpdate ( nodeName string , startingType vertexType , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
switch attrs . GetVerb ( ) {
case "update" , "patch" :
// ok
default :
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2017-11-20 12:57:24 -05:00
return authorizer . DecisionNoOpinion , "can only get/update/patch this type" , nil
}
if attrs . GetSubresource ( ) != "status" {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2017-11-20 12:57:24 -05:00
return authorizer . DecisionNoOpinion , "can only update status subresource" , nil
}
return r . authorize ( nodeName , startingType , attrs )
}
2017-05-30 15:15:38 -04:00
// authorizeGet authorizes "get" requests to objects of the specified type if they are related to the specified node
2017-09-29 17:21:40 -04:00
func ( r * NodeAuthorizer ) authorizeGet ( nodeName string , startingType vertexType , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
2017-11-20 12:57:24 -05:00
if attrs . GetVerb ( ) != "get" {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2017-09-29 17:21:40 -04:00
return authorizer . DecisionNoOpinion , "can only get individual resources of this type" , nil
2017-05-30 15:15:38 -04:00
}
if len ( attrs . GetSubresource ( ) ) > 0 {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2017-09-29 17:21:40 -04:00
return authorizer . DecisionNoOpinion , "cannot get subresource" , nil
2017-05-30 15:15:38 -04:00
}
2017-11-20 12:57:24 -05:00
return r . authorize ( nodeName , startingType , attrs )
}
2018-05-06 09:15:32 -04:00
// authorizeReadNamespacedObject authorizes "get", "list" and "watch" requests to single objects of a
// specified types if they are related to the specified node.
func ( r * NodeAuthorizer ) authorizeReadNamespacedObject ( nodeName string , startingType vertexType , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
2020-01-15 05:08:09 -05:00
switch attrs . GetVerb ( ) {
case "get" , "list" , "watch" :
2024-11-05 14:04:45 -05:00
// ok
2020-01-15 05:08:09 -05:00
default :
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2018-05-06 09:15:32 -04:00
return authorizer . DecisionNoOpinion , "can only read resources of this type" , nil
}
2020-01-15 05:08:09 -05:00
2018-05-06 09:15:32 -04:00
if len ( attrs . GetSubresource ( ) ) > 0 {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2018-05-06 09:15:32 -04:00
return authorizer . DecisionNoOpinion , "cannot read subresource" , nil
}
if len ( attrs . GetNamespace ( ) ) == 0 {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2018-05-06 09:15:32 -04:00
return authorizer . DecisionNoOpinion , "can only read namespaced object of this type" , nil
}
return r . authorize ( nodeName , startingType , attrs )
}
2017-11-20 12:57:24 -05:00
func ( r * NodeAuthorizer ) authorize ( nodeName string , startingType vertexType , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
if len ( attrs . GetName ( ) ) == 0 {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2017-11-20 12:57:24 -05:00
return authorizer . DecisionNoOpinion , "No Object name found" , nil
}
2017-05-30 15:15:38 -04:00
ok , err := r . hasPathFrom ( nodeName , startingType , attrs . GetNamespace ( ) , attrs . GetName ( ) )
if err != nil {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . InfoS ( "NODE DENY" , "err" , err )
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "no relationship found between node '%s' and this object" , nodeName ) , nil
2017-05-30 15:15:38 -04:00
}
if ! ok {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "no relationship found between node '%s' and this object" , nodeName ) , nil
2017-05-30 15:15:38 -04:00
}
2017-09-29 17:21:40 -04:00
return authorizer . DecisionAllow , "" , nil
2017-05-30 15:15:38 -04:00
}
2024-11-05 14:04:45 -05:00
// authorizeServiceAccount authorizes
2024-08-30 03:47:15 -04:00
// - "get" requests to serviceaccounts when KubeletServiceAccountTokenForCredentialProviders or PodCertificateRequest features are enabled
2024-11-05 14:04:45 -05:00
// - "create" requests to serviceaccounts 'token' subresource of pods running on a node
func ( r * NodeAuthorizer ) authorizeServiceAccount ( nodeName string , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
verb := attrs . GetVerb ( )
2024-08-30 03:47:15 -04:00
if verb == "get" && len ( attrs . GetSubresource ( ) ) == 0 {
if ! ( r . features . Enabled ( features . KubeletServiceAccountTokenForCredentialProviders ) || r . features . Enabled ( features . PodCertificateRequest ) ) {
2024-11-05 14:04:45 -05:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , "not allowed to get service accounts" , nil
}
return r . authorizeReadNamespacedObject ( nodeName , serviceAccountVertexType , attrs )
}
return r . authorizeCreateToken ( nodeName , serviceAccountVertexType , attrs )
}
2017-11-02 13:26:04 -04:00
// authorizeCreateToken authorizes "create" requests to serviceaccounts 'token'
// subresource of pods running on a node
func ( r * NodeAuthorizer ) authorizeCreateToken ( nodeName string , startingType vertexType , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
if attrs . GetVerb ( ) != "create" || len ( attrs . GetName ( ) ) == 0 {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2017-11-02 13:26:04 -04:00
return authorizer . DecisionNoOpinion , "can only create tokens for individual service accounts" , nil
}
if attrs . GetSubresource ( ) != "token" {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2017-11-02 13:26:04 -04:00
return authorizer . DecisionNoOpinion , "can only create token subresource of serviceaccount" , nil
}
ok , err := r . hasPathFrom ( nodeName , startingType , attrs . GetNamespace ( ) , attrs . GetName ( ) )
if err != nil {
2018-11-09 13:49:10 -05:00
klog . V ( 2 ) . Infof ( "NODE DENY: %v" , err )
2020-06-09 17:49:01 -04:00
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "no relationship found between node '%s' and this object" , nodeName ) , nil
2017-11-02 13:26:04 -04:00
}
if ! ok {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "no relationship found between node '%s' and this object" , nodeName ) , nil
2017-11-02 13:26:04 -04:00
}
return authorizer . DecisionAllow , "" , nil
}
2018-06-21 17:24:59 -04:00
// authorizeLease authorizes node requests to coordination.k8s.io/leases.
func ( r * NodeAuthorizer ) authorizeLease ( nodeName string , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
// allowed verbs: get, create, update, patch, delete
verb := attrs . GetVerb ( )
2020-01-15 05:08:09 -05:00
switch verb {
case "get" , "create" , "update" , "patch" , "delete" :
2024-11-05 14:04:45 -05:00
// ok
2020-01-15 05:08:09 -05:00
default :
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2018-06-21 17:24:59 -04:00
return authorizer . DecisionNoOpinion , "can only get, create, update, patch, or delete a node lease" , nil
}
// the request must be against the system namespace reserved for node leases
if attrs . GetNamespace ( ) != api . NamespaceNodeLease {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2018-06-21 17:24:59 -04:00
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "can only access leases in the %q system namespace" , api . NamespaceNodeLease ) , nil
}
// the request must come from a node with the same name as the lease
// note we skip this check for create, since the authorizer doesn't know the name on create
// the noderestriction admission plugin is capable of performing this check at create time
if verb != "create" && attrs . GetName ( ) != nodeName {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2018-06-21 17:24:59 -04:00
return authorizer . DecisionNoOpinion , "can only access node lease with the same name as the requesting node" , nil
}
return authorizer . DecisionAllow , "" , nil
}
2019-03-01 22:14:23 -05:00
// authorizeCSINode authorizes node requests to CSINode storage.k8s.io/csinodes
func ( r * NodeAuthorizer ) authorizeCSINode ( nodeName string , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
2018-08-28 03:00:00 -04:00
// allowed verbs: get, create, update, patch, delete
verb := attrs . GetVerb ( )
2020-01-15 05:08:09 -05:00
switch verb {
case "get" , "create" , "update" , "patch" , "delete" :
2024-11-05 14:04:45 -05:00
// ok
2020-01-15 05:08:09 -05:00
default :
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2019-03-01 22:14:23 -05:00
return authorizer . DecisionNoOpinion , "can only get, create, update, patch, or delete a CSINode" , nil
2018-08-28 03:00:00 -04:00
}
if len ( attrs . GetSubresource ( ) ) > 0 {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2019-03-01 22:14:23 -05:00
return authorizer . DecisionNoOpinion , "cannot authorize CSINode subresources" , nil
2018-08-28 03:00:00 -04:00
}
2019-03-01 22:14:23 -05:00
// the request must come from a node with the same name as the CSINode
2018-08-28 03:00:00 -04:00
// note we skip this check for create, since the authorizer doesn't know the name on create
// the noderestriction admission plugin is capable of performing this check at create time
if verb != "create" && attrs . GetName ( ) != nodeName {
2020-06-09 17:49:01 -04:00
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2019-03-01 22:14:23 -05:00
return authorizer . DecisionNoOpinion , "can only access CSINode with the same name as the requesting node" , nil
2018-08-28 03:00:00 -04:00
}
return authorizer . DecisionAllow , "" , nil
}
2024-03-07 04:14:11 -05:00
// authorizeResourceSlice authorizes node requests to ResourceSlice resource.k8s.io/resourceslices
func ( r * NodeAuthorizer ) authorizeResourceSlice ( nodeName string , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
2024-01-24 11:51:40 -05:00
if len ( attrs . GetSubresource ( ) ) > 0 {
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2024-03-07 04:14:11 -05:00
return authorizer . DecisionNoOpinion , "cannot authorize ResourceSlice subresources" , nil
2024-01-24 11:51:40 -05:00
}
2024-04-11 08:36:44 -04:00
// allowed verbs: get, create, update, patch, delete, watch, list, deletecollection
2024-01-24 11:51:40 -05:00
verb := attrs . GetVerb ( )
switch verb {
2024-04-11 08:36:44 -04:00
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" :
2024-06-27 00:13:09 -04:00
if r . features . Enabled ( features . AuthorizeNodeWithSelectors ) {
// only allow a scoped fieldSelector
reqs , _ := attrs . GetFieldSelector ( )
for _ , req := range reqs {
2024-06-18 11:47:29 -04:00
if req . Field == resourceapi . ResourceSliceSelectorNodeName && req . Operator == selection . Equals && req . Value == nodeName {
2024-06-27 00:13:09 -04:00
return authorizer . DecisionAllow , "" , nil
}
}
// deny otherwise
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , "can only list/watch/deletecollection resourceslices with nodeName field selector" , nil
} else {
// Allow broad list/watch access if AuthorizeNodeWithSelectors is not enabled.
//
// The NodeRestriction admission plugin (plugin/pkg/admission/noderestriction)
// ensures that the node is not deleting some ResourceSlice belonging to
// some other node.
return authorizer . DecisionAllow , "" , nil
}
2024-01-24 11:51:40 -05:00
default :
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
2024-04-11 08:36:44 -04:00
return authorizer . DecisionNoOpinion , "only the following verbs are allowed for a ResourceSlice: get, watch, list, create, update, patch, delete, deletecollection" , nil
2024-01-24 11:51:40 -05:00
}
}
2024-08-30 03:47:15 -04:00
func ( r * NodeAuthorizer ) authorizePodCertificateRequest ( nodeName string , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
if len ( attrs . GetSubresource ( ) ) != 0 {
return authorizer . DecisionNoOpinion , "nodes may not access the status subresource of PodCertificateRequests" , nil
}
switch attrs . GetVerb ( ) {
case "create" :
// Creates are further restricted by the noderestriction admission plugin.
return authorizer . DecisionAllow , "" , nil
case "get" :
return r . authorize ( nodeName , pcrVertexType , attrs )
case "list" , "watch" :
// Allow requests that have a fieldselector restricting to this node.
reqs , _ := attrs . GetFieldSelector ( )
for _ , req := range reqs {
if req . Field == "spec.nodeName" && req . Operator == selection . Equals && req . Value == nodeName {
return authorizer . DecisionAllow , "" , nil
}
}
// deny otherwise
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , "can only list/watch podcertificaterequests with nodeName field selector" , nil
}
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "nodes may not %s podcertificaterequests" , attrs . GetVerb ( ) ) , nil
}
2024-06-27 00:13:09 -04:00
// authorizeNode authorizes node requests to Node API objects
func ( r * NodeAuthorizer ) authorizeNode ( nodeName string , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
switch attrs . GetSubresource ( ) {
case "" :
switch attrs . GetVerb ( ) {
case "create" , "update" , "patch" :
// Use the NodeRestriction admission plugin to limit a node to creating/updating its own API object.
return authorizer . DecisionAllow , "" , nil
case "get" , "list" , "watch" :
2024-07-21 15:01:46 -04:00
// Compare the name directly, rather than using the graph,
// so kubelets can attempt a read of their Node API object prior to creation.
switch attrs . GetName ( ) {
case nodeName :
return authorizer . DecisionAllow , "" , nil
case "" :
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "node '%s' cannot read all nodes, only its own Node object" , nodeName ) , nil
default :
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , fmt . Sprintf ( "node '%s' cannot read '%s', only its own Node object" , nodeName , attrs . GetName ( ) ) , nil
}
2024-06-27 00:13:09 -04:00
}
case "status" :
switch attrs . GetVerb ( ) {
case "update" , "patch" :
// Use the NodeRestriction admission plugin to limit a node to updating its own Node status.
return authorizer . DecisionAllow , "" , nil
}
}
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , "" , nil
}
// authorizePod authorizes node requests to Pod API objects
func ( r * NodeAuthorizer ) authorizePod ( nodeName string , attrs authorizer . Attributes ) ( authorizer . Decision , string , error ) {
switch attrs . GetSubresource ( ) {
case "" :
switch attrs . GetVerb ( ) {
case "get" :
return r . authorizeGet ( nodeName , podVertexType , attrs )
case "list" , "watch" :
// allow a scoped fieldSelector
reqs , _ := attrs . GetFieldSelector ( )
for _ , req := range reqs {
if req . Field == "spec.nodeName" && req . Operator == selection . Equals && req . Value == nodeName {
return authorizer . DecisionAllow , "" , nil
}
}
// allow a read of a single pod known to be related to the node
if attrs . GetName ( ) != "" {
return r . authorize ( nodeName , podVertexType , attrs )
}
// deny otherwise
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , "can only list/watch pods with spec.nodeName field selector" , nil
case "create" , "delete" :
// Needed for the node to create/delete mirror pods.
// Use the NodeRestriction admission plugin to limit a node to creating/deleting mirror pods bound to itself.
return authorizer . DecisionAllow , "" , nil
}
case "status" :
switch attrs . GetVerb ( ) {
case "update" , "patch" :
// Needed for the node to report status of pods it is running.
// Use the NodeRestriction admission plugin to limit a node to updating status of pods bound to itself.
return authorizer . DecisionAllow , "" , nil
}
case "eviction" :
if attrs . GetVerb ( ) == "create" {
// Needed for the node to evict pods it is running.
// Use the NodeRestriction admission plugin to limit a node to evicting pods bound to itself.
return authorizer . DecisionAllow , "" , nil
}
}
klog . V ( 2 ) . Infof ( "NODE DENY: '%s' %#v" , nodeName , attrs )
return authorizer . DecisionNoOpinion , "" , nil
}
2017-05-30 15:15:38 -04:00
// hasPathFrom returns true if there is a directed path from the specified type/namespace/name to the specified Node
func ( r * NodeAuthorizer ) hasPathFrom ( nodeName string , startingType vertexType , startingNamespace , startingName string ) ( bool , error ) {
r . graph . lock . RLock ( )
defer r . graph . lock . RUnlock ( )
2024-09-03 15:27:24 -04:00
nodeVertex , exists := r . graph . getVertexRLocked ( nodeVertexType , "" , nodeName )
2017-05-30 15:15:38 -04:00
if ! exists {
2020-06-09 17:49:01 -04:00
return false , fmt . Errorf ( "unknown node '%s' cannot get %s %s/%s" , nodeName , vertexTypes [ startingType ] , startingNamespace , startingName )
2017-05-30 15:15:38 -04:00
}
2024-09-03 15:27:24 -04:00
startingVertex , exists := r . graph . getVertexRLocked ( startingType , startingNamespace , startingName )
2017-05-30 15:15:38 -04:00
if ! exists {
2020-06-09 17:49:01 -04:00
return false , fmt . Errorf ( "node '%s' cannot get unknown %s %s/%s" , nodeName , vertexTypes [ startingType ] , startingNamespace , startingName )
2017-05-30 15:15:38 -04:00
}
2024-08-30 19:08:51 -04:00
if index , indexExists := r . graph . destinationEdgeIndex [ startingVertex . ID ( ) ] ; indexExists {
// Fast check to see if we know of a destination edge
if index . has ( nodeVertex . ID ( ) ) {
return true , nil
}
// For some types of vertices, the destination edge index is authoritative
// (as long as it exists), hence we can fail-fast here instead of running
// a potentially costly DFS below.
if vertexTypeWithAuthoritativeIndex [ startingType ] {
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 )
}
2018-04-19 22:22:25 -04:00
}
2017-05-30 15:15:38 -04:00
found := false
traversal := & traverse . VisitingDepthFirst {
EdgeFilter : func ( edge graph . Edge ) bool {
if destinationEdge , ok := edge . ( * destinationEdge ) ; ok {
if destinationEdge . DestinationID ( ) != nodeVertex . ID ( ) {
// Don't follow edges leading to other nodes
return false
}
// We found an edge leading to the node we want
found = true
}
// Visit this edge
return true
} ,
}
traversal . Walk ( r . graph . graph , startingVertex , func ( n graph . Node ) bool {
if n . ID ( ) == nodeVertex . ID ( ) {
// We found the node we want
found = true
}
// Stop visiting if we've found the node we want
return found
} )
if ! found {
2020-06-09 17:49:01 -04:00
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 )
2017-05-30 15:15:38 -04:00
}
return true , nil
}