...

Source file src/k8s.io/kubernetes/pkg/kubeapiserver/authorizer/reload.go

Documentation: k8s.io/kubernetes/pkg/kubeapiserver/authorizer

     1  /*
     2  Copyright 2024 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 authorizer
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  	"os"
    25  	"reflect"
    26  	"sync"
    27  	"sync/atomic"
    28  	"time"
    29  
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	authzconfig "k8s.io/apiserver/pkg/apis/apiserver"
    32  	"k8s.io/apiserver/pkg/authentication/user"
    33  	"k8s.io/apiserver/pkg/authorization/authorizer"
    34  	"k8s.io/apiserver/pkg/authorization/authorizerfactory"
    35  	"k8s.io/apiserver/pkg/authorization/cel"
    36  	authorizationmetrics "k8s.io/apiserver/pkg/authorization/metrics"
    37  	"k8s.io/apiserver/pkg/authorization/union"
    38  	"k8s.io/apiserver/pkg/server/options/authorizationconfig/metrics"
    39  	webhookutil "k8s.io/apiserver/pkg/util/webhook"
    40  	"k8s.io/apiserver/plugin/pkg/authorizer/webhook"
    41  	webhookmetrics "k8s.io/apiserver/plugin/pkg/authorizer/webhook/metrics"
    42  	"k8s.io/klog/v2"
    43  	"k8s.io/kubernetes/pkg/auth/authorizer/abac"
    44  	"k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
    45  	"k8s.io/kubernetes/pkg/util/filesystem"
    46  	"k8s.io/kubernetes/plugin/pkg/auth/authorizer/node"
    47  	"k8s.io/kubernetes/plugin/pkg/auth/authorizer/rbac"
    48  )
    49  
    50  type reloadableAuthorizerResolver struct {
    51  	// initialConfig holds the ReloadFile used to initiate background reloading,
    52  	// and information used to construct webhooks that isn't exposed in the authorization
    53  	// configuration file (dial function, backoff settings, etc)
    54  	initialConfig Config
    55  
    56  	apiServerID string
    57  
    58  	reloadInterval         time.Duration
    59  	requireNonWebhookTypes sets.Set[authzconfig.AuthorizerType]
    60  
    61  	nodeAuthorizer *node.NodeAuthorizer
    62  	rbacAuthorizer *rbac.RBACAuthorizer
    63  	abacAuthorizer abac.PolicyList
    64  
    65  	lastLoadedLock   sync.Mutex
    66  	lastLoadedConfig *authzconfig.AuthorizationConfiguration
    67  	lastReadData     []byte
    68  
    69  	current atomic.Pointer[authorizerResolver]
    70  }
    71  
    72  type authorizerResolver struct {
    73  	authorizer   authorizer.Authorizer
    74  	ruleResolver authorizer.RuleResolver
    75  }
    76  
    77  func (r *reloadableAuthorizerResolver) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
    78  	return r.current.Load().authorizer.Authorize(ctx, a)
    79  }
    80  
    81  func (r *reloadableAuthorizerResolver) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
    82  	return r.current.Load().ruleResolver.RulesFor(user, namespace)
    83  }
    84  
    85  // newForConfig constructs
    86  func (r *reloadableAuthorizerResolver) newForConfig(authzConfig *authzconfig.AuthorizationConfiguration) (authorizer.Authorizer, authorizer.RuleResolver, error) {
    87  	if len(authzConfig.Authorizers) == 0 {
    88  		return nil, nil, fmt.Errorf("at least one authorization mode must be passed")
    89  	}
    90  
    91  	var (
    92  		authorizers   []authorizer.Authorizer
    93  		ruleResolvers []authorizer.RuleResolver
    94  	)
    95  
    96  	// Add SystemPrivilegedGroup as an authorizing group
    97  	superuserAuthorizer := authorizerfactory.NewPrivilegedGroups(user.SystemPrivilegedGroup)
    98  	authorizers = append(authorizers, superuserAuthorizer)
    99  
   100  	for _, configuredAuthorizer := range authzConfig.Authorizers {
   101  		// Keep cases in sync with constant list in k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes/modes.go.
   102  		switch configuredAuthorizer.Type {
   103  		case authzconfig.AuthorizerType(modes.ModeNode):
   104  			if r.nodeAuthorizer == nil {
   105  				return nil, nil, fmt.Errorf("authorizer type Node is not allowed if it was not enabled at initial server startup")
   106  			}
   107  			authorizers = append(authorizers, authorizationmetrics.InstrumentedAuthorizer(string(configuredAuthorizer.Type), configuredAuthorizer.Name, r.nodeAuthorizer))
   108  			ruleResolvers = append(ruleResolvers, r.nodeAuthorizer)
   109  		case authzconfig.AuthorizerType(modes.ModeAlwaysAllow):
   110  			alwaysAllowAuthorizer := authorizerfactory.NewAlwaysAllowAuthorizer()
   111  			authorizers = append(authorizers, authorizationmetrics.InstrumentedAuthorizer(string(configuredAuthorizer.Type), configuredAuthorizer.Name, alwaysAllowAuthorizer))
   112  			ruleResolvers = append(ruleResolvers, alwaysAllowAuthorizer)
   113  		case authzconfig.AuthorizerType(modes.ModeAlwaysDeny):
   114  			alwaysDenyAuthorizer := authorizerfactory.NewAlwaysDenyAuthorizer()
   115  			authorizers = append(authorizers, authorizationmetrics.InstrumentedAuthorizer(string(configuredAuthorizer.Type), configuredAuthorizer.Name, alwaysDenyAuthorizer))
   116  			ruleResolvers = append(ruleResolvers, alwaysDenyAuthorizer)
   117  		case authzconfig.AuthorizerType(modes.ModeABAC):
   118  			if r.abacAuthorizer == nil {
   119  				return nil, nil, fmt.Errorf("authorizer type ABAC is not allowed if it was not enabled at initial server startup")
   120  			}
   121  			authorizers = append(authorizers, authorizationmetrics.InstrumentedAuthorizer(string(configuredAuthorizer.Type), configuredAuthorizer.Name, r.abacAuthorizer))
   122  			ruleResolvers = append(ruleResolvers, r.abacAuthorizer)
   123  		case authzconfig.AuthorizerType(modes.ModeWebhook):
   124  			if r.initialConfig.WebhookRetryBackoff == nil {
   125  				return nil, nil, errors.New("retry backoff parameters for authorization webhook has not been specified")
   126  			}
   127  			clientConfig, err := webhookutil.LoadKubeconfig(*configuredAuthorizer.Webhook.ConnectionInfo.KubeConfigFile, r.initialConfig.CustomDial)
   128  			if err != nil {
   129  				return nil, nil, err
   130  			}
   131  			var decisionOnError authorizer.Decision
   132  			switch configuredAuthorizer.Webhook.FailurePolicy {
   133  			case authzconfig.FailurePolicyNoOpinion:
   134  				decisionOnError = authorizer.DecisionNoOpinion
   135  			case authzconfig.FailurePolicyDeny:
   136  				decisionOnError = authorizer.DecisionDeny
   137  			default:
   138  				return nil, nil, fmt.Errorf("unknown failurePolicy %q", configuredAuthorizer.Webhook.FailurePolicy)
   139  			}
   140  			webhookAuthorizer, err := webhook.New(clientConfig,
   141  				configuredAuthorizer.Webhook.SubjectAccessReviewVersion,
   142  				configuredAuthorizer.Webhook.AuthorizedTTL.Duration,
   143  				configuredAuthorizer.Webhook.UnauthorizedTTL.Duration,
   144  				*r.initialConfig.WebhookRetryBackoff,
   145  				decisionOnError,
   146  				configuredAuthorizer.Webhook.MatchConditions,
   147  				configuredAuthorizer.Name,
   148  				kubeapiserverWebhookMetrics{WebhookMetrics: webhookmetrics.NewWebhookMetrics(), MatcherMetrics: cel.NewMatcherMetrics()},
   149  			)
   150  			if err != nil {
   151  				return nil, nil, err
   152  			}
   153  			authorizers = append(authorizers, authorizationmetrics.InstrumentedAuthorizer(string(configuredAuthorizer.Type), configuredAuthorizer.Name, webhookAuthorizer))
   154  			ruleResolvers = append(ruleResolvers, webhookAuthorizer)
   155  		case authzconfig.AuthorizerType(modes.ModeRBAC):
   156  			if r.rbacAuthorizer == nil {
   157  				return nil, nil, fmt.Errorf("authorizer type RBAC is not allowed if it was not enabled at initial server startup")
   158  			}
   159  			authorizers = append(authorizers, authorizationmetrics.InstrumentedAuthorizer(string(configuredAuthorizer.Type), configuredAuthorizer.Name, r.rbacAuthorizer))
   160  			ruleResolvers = append(ruleResolvers, r.rbacAuthorizer)
   161  		default:
   162  			return nil, nil, fmt.Errorf("unknown authorization mode %s specified", configuredAuthorizer.Type)
   163  		}
   164  	}
   165  
   166  	return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil
   167  }
   168  
   169  type kubeapiserverWebhookMetrics struct {
   170  	// kube-apiserver doesn't report request metrics
   171  	webhookmetrics.NoopRequestMetrics
   172  	// kube-apiserver does report webhook metrics
   173  	webhookmetrics.WebhookMetrics
   174  	// kube-apiserver does report matchCondition metrics
   175  	cel.MatcherMetrics
   176  }
   177  
   178  // runReload starts checking the config file for changes and reloads the authorizer when it changes.
   179  // Blocks until ctx is complete.
   180  func (r *reloadableAuthorizerResolver) runReload(ctx context.Context) {
   181  	metrics.RegisterMetrics()
   182  	metrics.RecordAuthorizationConfigAutomaticReloadSuccess(r.apiServerID)
   183  
   184  	filesystem.WatchUntil(
   185  		ctx,
   186  		r.reloadInterval,
   187  		r.initialConfig.ReloadFile,
   188  		func() {
   189  			r.checkFile(ctx)
   190  		},
   191  		func(err error) {
   192  			klog.ErrorS(err, "watching authorization config file")
   193  		},
   194  	)
   195  }
   196  
   197  func (r *reloadableAuthorizerResolver) checkFile(ctx context.Context) {
   198  	r.lastLoadedLock.Lock()
   199  	defer r.lastLoadedLock.Unlock()
   200  
   201  	data, err := os.ReadFile(r.initialConfig.ReloadFile)
   202  	if err != nil {
   203  		klog.ErrorS(err, "reloading authorization config")
   204  		metrics.RecordAuthorizationConfigAutomaticReloadFailure(r.apiServerID)
   205  		return
   206  	}
   207  	if bytes.Equal(data, r.lastReadData) {
   208  		// no change
   209  		return
   210  	}
   211  	klog.InfoS("found new authorization config data")
   212  	r.lastReadData = data
   213  
   214  	config, err := LoadAndValidateData(data, r.requireNonWebhookTypes)
   215  	if err != nil {
   216  		klog.ErrorS(err, "reloading authorization config")
   217  		metrics.RecordAuthorizationConfigAutomaticReloadFailure(r.apiServerID)
   218  		return
   219  	}
   220  	if reflect.DeepEqual(config, r.lastLoadedConfig) {
   221  		// no change
   222  		return
   223  	}
   224  	klog.InfoS("found new authorization config")
   225  	r.lastLoadedConfig = config
   226  
   227  	authorizer, ruleResolver, err := r.newForConfig(config)
   228  	if err != nil {
   229  		klog.ErrorS(err, "reloading authorization config")
   230  		metrics.RecordAuthorizationConfigAutomaticReloadFailure(r.apiServerID)
   231  		return
   232  	}
   233  	klog.InfoS("constructed new authorizer")
   234  
   235  	r.current.Store(&authorizerResolver{
   236  		authorizer:   authorizer,
   237  		ruleResolver: ruleResolver,
   238  	})
   239  	klog.InfoS("reloaded authz config")
   240  	metrics.RecordAuthorizationConfigAutomaticReloadSuccess(r.apiServerID)
   241  }
   242  

View as plain text