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 }