k8s.io/kubernetes@v1.29.3/pkg/kubeapiserver/options/authorization.go (about)

     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 options
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"time"
    23  
    24  	"k8s.io/apiserver/pkg/apis/apiserver/load"
    25  	genericfeatures "k8s.io/apiserver/pkg/features"
    26  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    27  
    28  	"github.com/spf13/pflag"
    29  
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"k8s.io/apimachinery/pkg/util/sets"
    32  	"k8s.io/apimachinery/pkg/util/wait"
    33  	authzconfig "k8s.io/apiserver/pkg/apis/apiserver"
    34  	"k8s.io/apiserver/pkg/apis/apiserver/validation"
    35  	genericoptions "k8s.io/apiserver/pkg/server/options"
    36  	versionedinformers "k8s.io/client-go/informers"
    37  
    38  	"k8s.io/kubernetes/pkg/kubeapiserver/authorizer"
    39  	authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
    40  )
    41  
    42  const (
    43  	defaultWebhookName                      = "default"
    44  	authorizationModeFlag                   = "authorization-mode"
    45  	authorizationWebhookConfigFileFlag      = "authorization-webhook-config-file"
    46  	authorizationWebhookVersionFlag         = "authorization-webhook-version"
    47  	authorizationWebhookAuthorizedTTLFlag   = "authorization-webhook-cache-authorized-ttl"
    48  	authorizationWebhookUnauthorizedTTLFlag = "authorization-webhook-cache-unauthorized-ttl"
    49  	authorizationPolicyFileFlag             = "authorization-policy-file"
    50  	authorizationConfigFlag                 = "authorization-config"
    51  )
    52  
    53  // RepeatableAuthorizerTypes is the list of Authorizer that can be repeated in the Authorization Config
    54  var repeatableAuthorizerTypes = []string{authzmodes.ModeWebhook}
    55  
    56  // BuiltInAuthorizationOptions contains all build-in authorization options for API Server
    57  type BuiltInAuthorizationOptions struct {
    58  	Modes                       []string
    59  	PolicyFile                  string
    60  	WebhookConfigFile           string
    61  	WebhookVersion              string
    62  	WebhookCacheAuthorizedTTL   time.Duration
    63  	WebhookCacheUnauthorizedTTL time.Duration
    64  	// WebhookRetryBackoff specifies the backoff parameters for the authorization webhook retry logic.
    65  	// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed
    66  	// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded.
    67  	WebhookRetryBackoff *wait.Backoff
    68  
    69  	// AuthorizationConfigurationFile is mutually exclusive with all of:
    70  	//	- Modes
    71  	//	- WebhookConfigFile
    72  	//	- WebHookVersion
    73  	//	- WebhookCacheAuthorizedTTL
    74  	//	- WebhookCacheUnauthorizedTTL
    75  	AuthorizationConfigurationFile string
    76  
    77  	AreLegacyFlagsSet func() bool
    78  }
    79  
    80  // NewBuiltInAuthorizationOptions create a BuiltInAuthorizationOptions with default value
    81  func NewBuiltInAuthorizationOptions() *BuiltInAuthorizationOptions {
    82  	return &BuiltInAuthorizationOptions{
    83  		Modes:                       []string{},
    84  		WebhookVersion:              "v1beta1",
    85  		WebhookCacheAuthorizedTTL:   5 * time.Minute,
    86  		WebhookCacheUnauthorizedTTL: 30 * time.Second,
    87  		WebhookRetryBackoff:         genericoptions.DefaultAuthWebhookRetryBackoff(),
    88  	}
    89  }
    90  
    91  // Complete modifies authorization options
    92  func (o *BuiltInAuthorizationOptions) Complete() []error {
    93  	if len(o.AuthorizationConfigurationFile) == 0 && len(o.Modes) == 0 {
    94  		o.Modes = []string{authzmodes.ModeAlwaysAllow}
    95  	}
    96  	return nil
    97  }
    98  
    99  // Validate checks invalid config combination
   100  func (o *BuiltInAuthorizationOptions) Validate() []error {
   101  	if o == nil {
   102  		return nil
   103  	}
   104  	var allErrors []error
   105  
   106  	// if --authorization-config is set, check if
   107  	// 	- the feature flag is set
   108  	//	- legacyFlags are not set
   109  	//	- the config file can be loaded
   110  	//	- the config file represents a valid configuration
   111  	if o.AuthorizationConfigurationFile != "" {
   112  		if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthorizationConfiguration) {
   113  			return append(allErrors, fmt.Errorf("--%s cannot be used without enabling StructuredAuthorizationConfiguration feature flag", authorizationConfigFlag))
   114  		}
   115  
   116  		// error out if legacy flags are defined
   117  		if o.AreLegacyFlagsSet != nil && o.AreLegacyFlagsSet() {
   118  			return append(allErrors, fmt.Errorf("--%s can not be specified when --%s or --authorization-webhook-* flags are defined", authorizationConfigFlag, authorizationModeFlag))
   119  		}
   120  
   121  		// load the file and check for errors
   122  		config, err := load.LoadFromFile(o.AuthorizationConfigurationFile)
   123  		if err != nil {
   124  			return append(allErrors, fmt.Errorf("failed to load AuthorizationConfiguration from file: %v", err))
   125  		}
   126  
   127  		// validate the file and return any error
   128  		if errors := validation.ValidateAuthorizationConfiguration(nil, config,
   129  			sets.NewString(authzmodes.AuthorizationModeChoices...),
   130  			sets.NewString(repeatableAuthorizerTypes...),
   131  		); len(errors) != 0 {
   132  			allErrors = append(allErrors, errors.ToAggregate().Errors()...)
   133  		}
   134  
   135  		// test to check if the authorizer names passed conform to the authorizers for type!=Webhook
   136  		// this test is only for kube-apiserver and hence checked here
   137  		// it preserves compatibility with o.buildAuthorizationConfiguration
   138  		for _, authorizer := range config.Authorizers {
   139  			if string(authorizer.Type) == authzmodes.ModeWebhook {
   140  				continue
   141  			}
   142  
   143  			expectedName := getNameForAuthorizerMode(string(authorizer.Type))
   144  			if expectedName != authorizer.Name {
   145  				allErrors = append(allErrors, fmt.Errorf("expected name %s for authorizer %s instead of %s", expectedName, authorizer.Type, authorizer.Name))
   146  			}
   147  		}
   148  
   149  		return allErrors
   150  	}
   151  
   152  	// validate the legacy flags using the legacy mode if --authorization-config is not passed
   153  	if len(o.Modes) == 0 {
   154  		allErrors = append(allErrors, fmt.Errorf("at least one authorization-mode must be passed"))
   155  	}
   156  
   157  	modes := sets.NewString(o.Modes...)
   158  	for _, mode := range o.Modes {
   159  		if !authzmodes.IsValidAuthorizationMode(mode) {
   160  			allErrors = append(allErrors, fmt.Errorf("authorization-mode %q is not a valid mode", mode))
   161  		}
   162  		if mode == authzmodes.ModeABAC && o.PolicyFile == "" {
   163  			allErrors = append(allErrors, fmt.Errorf("authorization-mode ABAC's authorization policy file not passed"))
   164  		}
   165  		if mode == authzmodes.ModeWebhook && o.WebhookConfigFile == "" {
   166  			allErrors = append(allErrors, fmt.Errorf("authorization-mode Webhook's authorization config file not passed"))
   167  		}
   168  	}
   169  
   170  	if o.PolicyFile != "" && !modes.Has(authzmodes.ModeABAC) {
   171  		allErrors = append(allErrors, fmt.Errorf("cannot specify --authorization-policy-file without mode ABAC"))
   172  	}
   173  
   174  	if o.WebhookConfigFile != "" && !modes.Has(authzmodes.ModeWebhook) {
   175  		allErrors = append(allErrors, fmt.Errorf("cannot specify --authorization-webhook-config-file without mode Webhook"))
   176  	}
   177  
   178  	if len(o.Modes) != modes.Len() {
   179  		allErrors = append(allErrors, fmt.Errorf("authorization-mode %q has mode specified more than once", o.Modes))
   180  	}
   181  
   182  	if o.WebhookRetryBackoff != nil && o.WebhookRetryBackoff.Steps <= 0 {
   183  		allErrors = append(allErrors, fmt.Errorf("number of webhook retry attempts must be greater than 0, but is: %d", o.WebhookRetryBackoff.Steps))
   184  	}
   185  
   186  	return allErrors
   187  }
   188  
   189  // AddFlags returns flags of authorization for a API Server
   190  func (o *BuiltInAuthorizationOptions) AddFlags(fs *pflag.FlagSet) {
   191  	if o == nil {
   192  		return
   193  	}
   194  
   195  	fs.StringSliceVar(&o.Modes, authorizationModeFlag, o.Modes, ""+
   196  		"Ordered list of plug-ins to do authorization on secure port. Defaults to AlwaysAllow if --authorization-config is not used. Comma-delimited list of: "+
   197  		strings.Join(authzmodes.AuthorizationModeChoices, ",")+".")
   198  
   199  	fs.StringVar(&o.PolicyFile, authorizationPolicyFileFlag, o.PolicyFile, ""+
   200  		"File with authorization policy in json line by line format, used with --authorization-mode=ABAC, on the secure port.")
   201  
   202  	fs.StringVar(&o.WebhookConfigFile, authorizationWebhookConfigFileFlag, o.WebhookConfigFile, ""+
   203  		"File with webhook configuration in kubeconfig format, used with --authorization-mode=Webhook. "+
   204  		"The API server will query the remote service to determine access on the API server's secure port.")
   205  
   206  	fs.StringVar(&o.WebhookVersion, authorizationWebhookVersionFlag, o.WebhookVersion, ""+
   207  		"The API version of the authorization.k8s.io SubjectAccessReview to send to and expect from the webhook.")
   208  
   209  	fs.DurationVar(&o.WebhookCacheAuthorizedTTL, authorizationWebhookAuthorizedTTLFlag,
   210  		o.WebhookCacheAuthorizedTTL,
   211  		"The duration to cache 'authorized' responses from the webhook authorizer.")
   212  
   213  	fs.DurationVar(&o.WebhookCacheUnauthorizedTTL,
   214  		authorizationWebhookUnauthorizedTTLFlag, o.WebhookCacheUnauthorizedTTL,
   215  		"The duration to cache 'unauthorized' responses from the webhook authorizer.")
   216  
   217  	fs.StringVar(&o.AuthorizationConfigurationFile, authorizationConfigFlag, o.AuthorizationConfigurationFile, ""+
   218  		"File with Authorization Configuration to configure the authorizer chain."+
   219  		"Note: This feature is in Alpha since v1.29."+
   220  		"--feature-gate=StructuredAuthorizationConfiguration=true feature flag needs to be set to true for enabling the functionality."+
   221  		"This feature is mutually exclusive with the other --authorization-mode and --authorization-webhook-* flags.")
   222  
   223  	// preserves compatibility with any method set during initialization
   224  	oldAreLegacyFlagsSet := o.AreLegacyFlagsSet
   225  	o.AreLegacyFlagsSet = func() bool {
   226  		if oldAreLegacyFlagsSet != nil && oldAreLegacyFlagsSet() {
   227  			return true
   228  		}
   229  
   230  		return fs.Changed(authorizationModeFlag) ||
   231  			fs.Changed(authorizationWebhookConfigFileFlag) ||
   232  			fs.Changed(authorizationWebhookVersionFlag) ||
   233  			fs.Changed(authorizationWebhookAuthorizedTTLFlag) ||
   234  			fs.Changed(authorizationWebhookUnauthorizedTTLFlag)
   235  	}
   236  }
   237  
   238  // ToAuthorizationConfig convert BuiltInAuthorizationOptions to authorizer.Config
   239  func (o *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFactory versionedinformers.SharedInformerFactory) (*authorizer.Config, error) {
   240  	if o == nil {
   241  		return nil, nil
   242  	}
   243  
   244  	var authorizationConfiguration *authzconfig.AuthorizationConfiguration
   245  	var err error
   246  
   247  	// if --authorization-config is set, check if
   248  	// 	- the feature flag is set
   249  	//	- legacyFlags are not set
   250  	//	- the config file can be loaded
   251  	//	- the config file represents a valid configuration
   252  	// else,
   253  	//	- build the AuthorizationConfig from the legacy flags
   254  	if o.AuthorizationConfigurationFile != "" {
   255  		if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthorizationConfiguration) {
   256  			return nil, fmt.Errorf("--%s cannot be used without enabling StructuredAuthorizationConfiguration feature flag", authorizationConfigFlag)
   257  		}
   258  
   259  		// error out if legacy flags are defined
   260  		if o.AreLegacyFlagsSet != nil && o.AreLegacyFlagsSet() {
   261  			return nil, fmt.Errorf("--%s can not be specified when --%s or --authorization-webhook-* flags are defined", authorizationConfigFlag, authorizationModeFlag)
   262  		}
   263  
   264  		// load the file and check for errors
   265  		authorizationConfiguration, err = load.LoadFromFile(o.AuthorizationConfigurationFile)
   266  		if err != nil {
   267  			return nil, fmt.Errorf("failed to load AuthorizationConfiguration from file: %v", err)
   268  		}
   269  
   270  		// validate the file and return any error
   271  		if errors := validation.ValidateAuthorizationConfiguration(nil, authorizationConfiguration,
   272  			sets.NewString(authzmodes.AuthorizationModeChoices...),
   273  			sets.NewString(repeatableAuthorizerTypes...),
   274  		); len(errors) != 0 {
   275  			return nil, fmt.Errorf(errors.ToAggregate().Error())
   276  		}
   277  	} else {
   278  		authorizationConfiguration, err = o.buildAuthorizationConfiguration()
   279  		if err != nil {
   280  			return nil, fmt.Errorf("failed to build authorization config: %s", err)
   281  		}
   282  	}
   283  
   284  	return &authorizer.Config{
   285  		PolicyFile:               o.PolicyFile,
   286  		VersionedInformerFactory: versionedInformerFactory,
   287  		WebhookRetryBackoff:      o.WebhookRetryBackoff,
   288  
   289  		AuthorizationConfiguration: authorizationConfiguration,
   290  	}, nil
   291  }
   292  
   293  // buildAuthorizationConfiguration converts existing flags to the AuthorizationConfiguration format
   294  func (o *BuiltInAuthorizationOptions) buildAuthorizationConfiguration() (*authzconfig.AuthorizationConfiguration, error) {
   295  	var authorizers []authzconfig.AuthorizerConfiguration
   296  
   297  	if len(o.Modes) != sets.NewString(o.Modes...).Len() {
   298  		return nil, fmt.Errorf("modes should not be repeated in --authorization-mode")
   299  	}
   300  
   301  	for _, mode := range o.Modes {
   302  		switch mode {
   303  		case authzmodes.ModeWebhook:
   304  			authorizers = append(authorizers, authzconfig.AuthorizerConfiguration{
   305  				Type: authzconfig.TypeWebhook,
   306  				Name: defaultWebhookName,
   307  				Webhook: &authzconfig.WebhookConfiguration{
   308  					AuthorizedTTL:   metav1.Duration{Duration: o.WebhookCacheAuthorizedTTL},
   309  					UnauthorizedTTL: metav1.Duration{Duration: o.WebhookCacheUnauthorizedTTL},
   310  					// Timeout and FailurePolicy are required for the new configuration.
   311  					// Setting these two implicitly to preserve backward compatibility.
   312  					Timeout:                    metav1.Duration{Duration: 30 * time.Second},
   313  					FailurePolicy:              authzconfig.FailurePolicyNoOpinion,
   314  					SubjectAccessReviewVersion: o.WebhookVersion,
   315  					ConnectionInfo: authzconfig.WebhookConnectionInfo{
   316  						Type:           authzconfig.AuthorizationWebhookConnectionInfoTypeKubeConfigFile,
   317  						KubeConfigFile: &o.WebhookConfigFile,
   318  					},
   319  				},
   320  			})
   321  		default:
   322  			authorizers = append(authorizers, authzconfig.AuthorizerConfiguration{
   323  				Type: authzconfig.AuthorizerType(mode),
   324  				Name: getNameForAuthorizerMode(mode),
   325  			})
   326  		}
   327  	}
   328  
   329  	return &authzconfig.AuthorizationConfiguration{Authorizers: authorizers}, nil
   330  }
   331  
   332  // getNameForAuthorizerMode returns the name to be set for the mode in AuthorizationConfiguration
   333  // For now, lower cases the mode name
   334  func getNameForAuthorizerMode(mode string) string {
   335  	return strings.ToLower(mode)
   336  }