Add --authorization-config flag to apiserver
Signed-off-by: Nabarun Pal <pal.nabarun95@gmail.com>
This commit is contained in:
		| @@ -327,6 +327,13 @@ func TestAddFlags(t *testing.T) { | ||||
| 	expected.Authentication.OIDC.UsernameClaim = "sub" | ||||
| 	expected.Authentication.OIDC.SigningAlgs = []string{"RS256"} | ||||
|  | ||||
| 	if !s.Authorization.AreLegacyFlagsSet() { | ||||
| 		t.Errorf("expected legacy authorization flags to be set") | ||||
| 	} | ||||
|  | ||||
| 	// setting the method to nil since methods can't be compared with reflect.DeepEqual | ||||
| 	s.Authorization.AreLegacyFlagsSet = nil | ||||
|  | ||||
| 	if !reflect.DeepEqual(expected, s) { | ||||
| 		t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{}))) | ||||
| 	} | ||||
|   | ||||
| @@ -283,6 +283,12 @@ func TestAddFlags(t *testing.T) { | ||||
| 	expected.Authentication.OIDC.UsernameClaim = "sub" | ||||
| 	expected.Authentication.OIDC.SigningAlgs = []string{"RS256"} | ||||
|  | ||||
| 	if !s.Authorization.AreLegacyFlagsSet() { | ||||
| 		t.Errorf("expected legacy authorization flags to be set") | ||||
| 	} | ||||
| 	// setting the method to nil since methods can't be compared with reflect.DeepEqual | ||||
| 	s.Authorization.AreLegacyFlagsSet = nil | ||||
|  | ||||
| 	if !reflect.DeepEqual(expected, s) { | ||||
| 		t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{}))) | ||||
| 	} | ||||
|   | ||||
| @@ -21,12 +21,17 @@ import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"k8s.io/apiserver/pkg/apis/apiserver/load" | ||||
| 	genericfeatures "k8s.io/apiserver/pkg/features" | ||||
| 	utilfeature "k8s.io/apiserver/pkg/util/feature" | ||||
|  | ||||
| 	"github.com/spf13/pflag" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/util/sets" | ||||
| 	"k8s.io/apimachinery/pkg/util/wait" | ||||
| 	authzconfig "k8s.io/apiserver/pkg/apis/apiserver" | ||||
| 	"k8s.io/apiserver/pkg/apis/apiserver/validation" | ||||
| 	genericoptions "k8s.io/apiserver/pkg/server/options" | ||||
| 	versionedinformers "k8s.io/client-go/informers" | ||||
|  | ||||
| @@ -35,9 +40,19 @@ import ( | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	defaultWebhookName = "default" | ||||
| 	defaultWebhookName                      = "default" | ||||
| 	authorizationModeFlag                   = "authorization-mode" | ||||
| 	authorizationWebhookConfigFileFlag      = "authorization-webhook-config-file" | ||||
| 	authorizationWebhookVersionFlag         = "authorization-webhook-version" | ||||
| 	authorizationWebhookAuthorizedTTLFlag   = "authorization-webhook-cache-authorized-ttl" | ||||
| 	authorizationWebhookUnauthorizedTTLFlag = "authorization-webhook-cache-unauthorized-ttl" | ||||
| 	authorizationPolicyFileFlag             = "authorization-policy-file" | ||||
| 	authorizationConfigFlag                 = "authorization-config" | ||||
| ) | ||||
|  | ||||
| // RepeatableAuthorizerTypes is the list of Authorizer that can be repeated in the Authorization Config | ||||
| var repeatableAuthorizerTypes = []string{authzmodes.ModeWebhook} | ||||
|  | ||||
| // BuiltInAuthorizationOptions contains all build-in authorization options for API Server | ||||
| type BuiltInAuthorizationOptions struct { | ||||
| 	Modes                       []string | ||||
| @@ -50,6 +65,16 @@ type BuiltInAuthorizationOptions struct { | ||||
| 	// This allows us to configure the sleep time at each iteration and the maximum number of retries allowed | ||||
| 	// before we fail the webhook call in order to limit the fan out that ensues when the system is degraded. | ||||
| 	WebhookRetryBackoff *wait.Backoff | ||||
|  | ||||
| 	// AuthorizationConfigurationFile is mutually exclusive with all of: | ||||
| 	//	- Modes | ||||
| 	//	- WebhookConfigFile | ||||
| 	//	- WebHookVersion | ||||
| 	//	- WebhookCacheAuthorizedTTL | ||||
| 	//	- WebhookCacheUnauthorizedTTL | ||||
| 	AuthorizationConfigurationFile string | ||||
|  | ||||
| 	AreLegacyFlagsSet func() bool | ||||
| } | ||||
|  | ||||
| // NewBuiltInAuthorizationOptions create a BuiltInAuthorizationOptions with default value | ||||
| @@ -69,6 +94,54 @@ func (o *BuiltInAuthorizationOptions) Validate() []error { | ||||
| 		return nil | ||||
| 	} | ||||
| 	var allErrors []error | ||||
|  | ||||
| 	// if --authorization-config is set, check if | ||||
| 	// 	- the feature flag is set | ||||
| 	//	- legacyFlags are not set | ||||
| 	//	- the config file can be loaded | ||||
| 	//	- the config file represents a valid configuration | ||||
| 	if o.AuthorizationConfigurationFile != "" { | ||||
| 		if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthorizationConfiguration) { | ||||
| 			return append(allErrors, fmt.Errorf("--%s cannot be used without enabling StructuredAuthorizationConfiguration feature flag", authorizationConfigFlag)) | ||||
| 		} | ||||
|  | ||||
| 		// error out if legacy flags are defined | ||||
| 		if o.AreLegacyFlagsSet != nil && o.AreLegacyFlagsSet() { | ||||
| 			return append(allErrors, fmt.Errorf("--%s can not be specified when --%s or --authorization-webhook-* flags are defined", authorizationConfigFlag, authorizationModeFlag)) | ||||
| 		} | ||||
|  | ||||
| 		// load the file and check for errors | ||||
| 		config, err := load.LoadFromFile(o.AuthorizationConfigurationFile) | ||||
| 		if err != nil { | ||||
| 			return append(allErrors, fmt.Errorf("failed to load AuthorizationConfiguration from file: %v", err)) | ||||
| 		} | ||||
|  | ||||
| 		// validate the file and return any error | ||||
| 		if errors := validation.ValidateAuthorizationConfiguration(nil, config, | ||||
| 			sets.NewString(authzmodes.AuthorizationModeChoices...), | ||||
| 			sets.NewString(repeatableAuthorizerTypes...), | ||||
| 		); len(errors) != 0 { | ||||
| 			allErrors = append(allErrors, errors.ToAggregate().Errors()...) | ||||
| 		} | ||||
|  | ||||
| 		// test to check if the authorizer names passed conform to the authorizers for type!=Webhook | ||||
| 		// this test is only for kube-apiserver and hence checked here | ||||
| 		// it preserves compatibility with o.buildAuthorizationConfiguration | ||||
| 		for _, authorizer := range config.Authorizers { | ||||
| 			if string(authorizer.Type) == authzmodes.ModeWebhook { | ||||
| 				continue | ||||
| 			} | ||||
|  | ||||
| 			expectedName := getNameForAuthorizerMode(string(authorizer.Type)) | ||||
| 			if expectedName != authorizer.Name { | ||||
| 				allErrors = append(allErrors, fmt.Errorf("expected name %s for authorizer %s instead of %s", expectedName, authorizer.Type, authorizer.Name)) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		return allErrors | ||||
| 	} | ||||
|  | ||||
| 	// validate the legacy flags using the legacy mode if --authorization-config is not passed | ||||
| 	if len(o.Modes) == 0 { | ||||
| 		allErrors = append(allErrors, fmt.Errorf("at least one authorization-mode must be passed")) | ||||
| 	} | ||||
| @@ -111,27 +184,47 @@ func (o *BuiltInAuthorizationOptions) AddFlags(fs *pflag.FlagSet) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	fs.StringSliceVar(&o.Modes, "authorization-mode", o.Modes, ""+ | ||||
| 	fs.StringSliceVar(&o.Modes, authorizationModeFlag, o.Modes, ""+ | ||||
| 		"Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+ | ||||
| 		strings.Join(authzmodes.AuthorizationModeChoices, ",")+".") | ||||
|  | ||||
| 	fs.StringVar(&o.PolicyFile, "authorization-policy-file", o.PolicyFile, ""+ | ||||
| 	fs.StringVar(&o.PolicyFile, authorizationPolicyFileFlag, o.PolicyFile, ""+ | ||||
| 		"File with authorization policy in json line by line format, used with --authorization-mode=ABAC, on the secure port.") | ||||
|  | ||||
| 	fs.StringVar(&o.WebhookConfigFile, "authorization-webhook-config-file", o.WebhookConfigFile, ""+ | ||||
| 	fs.StringVar(&o.WebhookConfigFile, authorizationWebhookConfigFileFlag, o.WebhookConfigFile, ""+ | ||||
| 		"File with webhook configuration in kubeconfig format, used with --authorization-mode=Webhook. "+ | ||||
| 		"The API server will query the remote service to determine access on the API server's secure port.") | ||||
|  | ||||
| 	fs.StringVar(&o.WebhookVersion, "authorization-webhook-version", o.WebhookVersion, ""+ | ||||
| 	fs.StringVar(&o.WebhookVersion, authorizationWebhookVersionFlag, o.WebhookVersion, ""+ | ||||
| 		"The API version of the authorization.k8s.io SubjectAccessReview to send to and expect from the webhook.") | ||||
|  | ||||
| 	fs.DurationVar(&o.WebhookCacheAuthorizedTTL, "authorization-webhook-cache-authorized-ttl", | ||||
| 	fs.DurationVar(&o.WebhookCacheAuthorizedTTL, authorizationWebhookAuthorizedTTLFlag, | ||||
| 		o.WebhookCacheAuthorizedTTL, | ||||
| 		"The duration to cache 'authorized' responses from the webhook authorizer.") | ||||
|  | ||||
| 	fs.DurationVar(&o.WebhookCacheUnauthorizedTTL, | ||||
| 		"authorization-webhook-cache-unauthorized-ttl", o.WebhookCacheUnauthorizedTTL, | ||||
| 		authorizationWebhookUnauthorizedTTLFlag, o.WebhookCacheUnauthorizedTTL, | ||||
| 		"The duration to cache 'unauthorized' responses from the webhook authorizer.") | ||||
|  | ||||
| 	fs.StringVar(&o.AuthorizationConfigurationFile, authorizationConfigFlag, o.AuthorizationConfigurationFile, ""+ | ||||
| 		"File with Authorization Configuration to configure the authorizer chain."+ | ||||
| 		"Note: This feature is in Alpha since v1.29."+ | ||||
| 		"--feature-gate=StructuredAuthorizationConfiguration=true feature flag needs to be set to true for enabling the functionality."+ | ||||
| 		"This feature is mutually exclusive with the other --authorization-mode and --authorization-webhook-* flags.") | ||||
|  | ||||
| 	// preserves compatibility with any method set during initialization | ||||
| 	oldAreLegacyFlagsSet := o.AreLegacyFlagsSet | ||||
| 	o.AreLegacyFlagsSet = func() bool { | ||||
| 		if oldAreLegacyFlagsSet != nil && oldAreLegacyFlagsSet() { | ||||
| 			return true | ||||
| 		} | ||||
|  | ||||
| 		return fs.Changed(authorizationModeFlag) || | ||||
| 			fs.Changed(authorizationWebhookConfigFileFlag) || | ||||
| 			fs.Changed(authorizationWebhookVersionFlag) || | ||||
| 			fs.Changed(authorizationWebhookAuthorizedTTLFlag) || | ||||
| 			fs.Changed(authorizationWebhookUnauthorizedTTLFlag) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ToAuthorizationConfig convert BuiltInAuthorizationOptions to authorizer.Config | ||||
| @@ -140,9 +233,44 @@ func (o *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFac | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	authzConfiguration, err := o.buildAuthorizationConfiguration() | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to build authorization config: %s", err) | ||||
| 	var authorizationConfiguration *authzconfig.AuthorizationConfiguration | ||||
| 	var err error | ||||
|  | ||||
| 	// if --authorization-config is set, check if | ||||
| 	// 	- the feature flag is set | ||||
| 	//	- legacyFlags are not set | ||||
| 	//	- the config file can be loaded | ||||
| 	//	- the config file represents a valid configuration | ||||
| 	// else, | ||||
| 	//	- build the AuthorizationConfig from the legacy flags | ||||
| 	if o.AuthorizationConfigurationFile != "" { | ||||
| 		if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthorizationConfiguration) { | ||||
| 			return nil, fmt.Errorf("--%s cannot be used without enabling StructuredAuthorizationConfiguration feature flag", authorizationConfigFlag) | ||||
| 		} | ||||
|  | ||||
| 		// error out if legacy flags are defined | ||||
| 		if o.AreLegacyFlagsSet != nil && o.AreLegacyFlagsSet() { | ||||
| 			return nil, fmt.Errorf("--%s can not be specified when --%s or --authorization-webhook-* flags are defined", authorizationConfigFlag, authorizationModeFlag) | ||||
| 		} | ||||
|  | ||||
| 		// load the file and check for errors | ||||
| 		authorizationConfiguration, err = load.LoadFromFile(o.AuthorizationConfigurationFile) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed to load AuthorizationConfiguration from file: %v", err) | ||||
| 		} | ||||
|  | ||||
| 		// validate the file and return any error | ||||
| 		if errors := validation.ValidateAuthorizationConfiguration(nil, authorizationConfiguration, | ||||
| 			sets.NewString(authzmodes.AuthorizationModeChoices...), | ||||
| 			sets.NewString(repeatableAuthorizerTypes...), | ||||
| 		); len(errors) != 0 { | ||||
| 			return nil, fmt.Errorf(errors.ToAggregate().Error()) | ||||
| 		} | ||||
| 	} else { | ||||
| 		authorizationConfiguration, err = o.buildAuthorizationConfiguration() | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed to build authorization config: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &authorizer.Config{ | ||||
| @@ -150,7 +278,7 @@ func (o *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFac | ||||
| 		VersionedInformerFactory: versionedInformerFactory, | ||||
| 		WebhookRetryBackoff:      o.WebhookRetryBackoff, | ||||
|  | ||||
| 		AuthorizationConfiguration: authzConfiguration, | ||||
| 		AuthorizationConfiguration: authorizationConfiguration, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -173,6 +173,13 @@ func TestBuiltInAuthorizationOptionsAddFlags(t *testing.T) { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
|  | ||||
| 	if !opts.AreLegacyFlagsSet() { | ||||
| 		t.Fatal("legacy flags should have been configured") | ||||
| 	} | ||||
|  | ||||
| 	// setting the method to nil since methods can't be compared with reflect.DeepEqual | ||||
| 	opts.AreLegacyFlagsSet = nil | ||||
|  | ||||
| 	if !reflect.DeepEqual(opts, expected) { | ||||
| 		t.Error(cmp.Diff(opts, expected)) | ||||
| 	} | ||||
|   | ||||
							
								
								
									
										82
									
								
								staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| /* | ||||
| Copyright 2023 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 load | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
|  | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/apimachinery/pkg/runtime/serializer" | ||||
| 	api "k8s.io/apiserver/pkg/apis/apiserver" | ||||
| 	"k8s.io/apiserver/pkg/apis/apiserver/install" | ||||
| 	externalapi "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	scheme = runtime.NewScheme() | ||||
| 	codecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict) | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	install.Install(scheme) | ||||
| } | ||||
|  | ||||
| func LoadFromFile(file string) (*api.AuthorizationConfiguration, error) { | ||||
| 	data, err := os.ReadFile(file) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return LoadFromData(data) | ||||
| } | ||||
|  | ||||
| func LoadFromReader(reader io.Reader) (*api.AuthorizationConfiguration, error) { | ||||
| 	if reader == nil { | ||||
| 		// no reader specified, use default config | ||||
| 		return LoadFromData(nil) | ||||
| 	} | ||||
|  | ||||
| 	data, err := io.ReadAll(reader) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return LoadFromData(data) | ||||
| } | ||||
|  | ||||
| func LoadFromData(data []byte) (*api.AuthorizationConfiguration, error) { | ||||
| 	if len(data) == 0 { | ||||
| 		// no config provided, return default | ||||
| 		externalConfig := &externalapi.AuthorizationConfiguration{} | ||||
| 		scheme.Default(externalConfig) | ||||
| 		internalConfig := &api.AuthorizationConfiguration{} | ||||
| 		if err := scheme.Convert(externalConfig, internalConfig, nil); err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return internalConfig, nil | ||||
| 	} | ||||
|  | ||||
| 	decodedObj, err := runtime.Decode(codecs.UniversalDecoder(), data) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	configuration, ok := decodedObj.(*api.AuthorizationConfiguration) | ||||
| 	if !ok { | ||||
| 		return nil, fmt.Errorf("expected AuthorizationConfiguration, got %T", decodedObj) | ||||
| 	} | ||||
| 	return configuration, nil | ||||
| } | ||||
| @@ -0,0 +1,290 @@ | ||||
| /* | ||||
| Copyright 2023 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 load | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"os" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/google/go-cmp/cmp" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	api "k8s.io/apiserver/pkg/apis/apiserver" | ||||
| ) | ||||
|  | ||||
| var defaultConfig = &api.AuthorizationConfiguration{} | ||||
|  | ||||
| func writeTempFile(t *testing.T, content string) string { | ||||
| 	t.Helper() | ||||
| 	file, err := os.CreateTemp("", "config") | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	t.Cleanup(func() { | ||||
| 		if err := os.Remove(file.Name()); err != nil { | ||||
| 			t.Fatal(err) | ||||
| 		} | ||||
| 	}) | ||||
| 	if err := os.WriteFile(file.Name(), []byte(content), 0600); err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	return file.Name() | ||||
| } | ||||
|  | ||||
| func TestLoadFromFile(t *testing.T) { | ||||
| 	// no file | ||||
| 	{ | ||||
| 		_, err := LoadFromFile("") | ||||
| 		if err == nil { | ||||
| 			t.Fatalf("expected err: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// empty file | ||||
| 	{ | ||||
| 		config, err := LoadFromFile(writeTempFile(t, ``)) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("unexpected err: %v", err) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(config, defaultConfig) { | ||||
| 			t.Fatalf("unexpected config:\n%s", cmp.Diff(defaultConfig, config)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// valid file | ||||
| 	{ | ||||
| 		input := `{ | ||||
| 			"apiVersion":"apiserver.config.k8s.io/v1alpha1", | ||||
| 			"kind":"AuthorizationConfiguration", | ||||
| 			"authorizers":[{"type":"Webhook"}]}` | ||||
| 		expect := &api.AuthorizationConfiguration{ | ||||
| 			Authorizers: []api.AuthorizerConfiguration{{Type: "Webhook"}}, | ||||
| 		} | ||||
|  | ||||
| 		config, err := LoadFromFile(writeTempFile(t, input)) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("unexpected err: %v", err) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(config, expect) { | ||||
| 			t.Fatalf("unexpected config:\n%s", cmp.Diff(expect, config)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// missing file | ||||
| 	{ | ||||
| 		_, err := LoadFromFile(`bogus-missing-file`) | ||||
| 		if err == nil { | ||||
| 			t.Fatalf("expected err, got none") | ||||
| 		} | ||||
| 		if !strings.Contains(err.Error(), "bogus-missing-file") { | ||||
| 			t.Fatalf("expected missing file error, got %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// invalid content file | ||||
| 	{ | ||||
| 		input := `{ | ||||
| 			"apiVersion":"apiserver.config.k8s.io/v99", | ||||
| 			"kind":"AuthorizationConfiguration", | ||||
| 			"authorizers":{"type":"Webhook"}}` | ||||
|  | ||||
| 		_, err := LoadFromFile(writeTempFile(t, input)) | ||||
| 		if err == nil { | ||||
| 			t.Fatalf("expected err, got none") | ||||
| 		} | ||||
| 		if !strings.Contains(err.Error(), "apiserver.config.k8s.io/v99") { | ||||
| 			t.Fatalf("expected apiVersion error, got %v", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestLoadFromReader(t *testing.T) { | ||||
| 	// no reader | ||||
| 	{ | ||||
| 		config, err := LoadFromReader(nil) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("unexpected err: %v", err) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(config, defaultConfig) { | ||||
| 			t.Fatalf("unexpected config:\n%s", cmp.Diff(defaultConfig, config)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// empty reader | ||||
| 	{ | ||||
| 		config, err := LoadFromReader(&bytes.Buffer{}) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("unexpected err: %v", err) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(config, defaultConfig) { | ||||
| 			t.Fatalf("unexpected config:\n%s", cmp.Diff(defaultConfig, config)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// valid reader | ||||
| 	{ | ||||
| 		input := `{ | ||||
| 			"apiVersion":"apiserver.config.k8s.io/v1alpha1", | ||||
| 			"kind":"AuthorizationConfiguration", | ||||
| 			"authorizers":[{"type":"Webhook"}]}` | ||||
| 		expect := &api.AuthorizationConfiguration{ | ||||
| 			Authorizers: []api.AuthorizerConfiguration{{Type: "Webhook"}}, | ||||
| 		} | ||||
|  | ||||
| 		config, err := LoadFromReader(bytes.NewBufferString(input)) | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("unexpected err: %v", err) | ||||
| 		} | ||||
| 		if !reflect.DeepEqual(config, expect) { | ||||
| 			t.Fatalf("unexpected config:\n%s", cmp.Diff(expect, config)) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// invalid reader | ||||
| 	{ | ||||
| 		input := `{ | ||||
| 			"apiVersion":"apiserver.config.k8s.io/v99", | ||||
| 			"kind":"AuthorizationConfiguration", | ||||
| 			"authorizers":[{"type":"Webhook"}]}` | ||||
|  | ||||
| 		_, err := LoadFromReader(bytes.NewBufferString(input)) | ||||
| 		if err == nil { | ||||
| 			t.Fatalf("expected err, got none") | ||||
| 		} | ||||
| 		if !strings.Contains(err.Error(), "apiserver.config.k8s.io/v99") { | ||||
| 			t.Fatalf("expected apiVersion error, got %v", err) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestLoadFromData(t *testing.T) { | ||||
| 	testcases := []struct { | ||||
| 		name         string | ||||
| 		data         []byte | ||||
| 		expectErr    string | ||||
| 		expectConfig *api.AuthorizationConfiguration | ||||
| 	}{ | ||||
| 		{ | ||||
| 			name:         "nil", | ||||
| 			data:         nil, | ||||
| 			expectConfig: defaultConfig, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:         "nil", | ||||
| 			data:         []byte{}, | ||||
| 			expectConfig: defaultConfig, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "v1alpha1 - json", | ||||
| 			data: []byte(`{ | ||||
| "apiVersion":"apiserver.config.k8s.io/v1alpha1", | ||||
| "kind":"AuthorizationConfiguration", | ||||
| "authorizers":[{"type":"Webhook"}]}`), | ||||
| 			expectConfig: &api.AuthorizationConfiguration{ | ||||
| 				Authorizers: []api.AuthorizerConfiguration{{Type: "Webhook"}}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "v1alpha1 - defaults", | ||||
| 			data: []byte(`{ | ||||
| "apiVersion":"apiserver.config.k8s.io/v1alpha1", | ||||
| "kind":"AuthorizationConfiguration", | ||||
| "authorizers":[{"type":"Webhook","name":"default","webhook":{}}]}`), | ||||
| 			expectConfig: &api.AuthorizationConfiguration{ | ||||
| 				Authorizers: []api.AuthorizerConfiguration{{ | ||||
| 					Type: "Webhook", | ||||
| 					Name: "default", | ||||
| 					Webhook: &api.WebhookConfiguration{ | ||||
| 						AuthorizedTTL:   metav1.Duration{Duration: 5 * time.Minute}, | ||||
| 						UnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second}, | ||||
| 					}, | ||||
| 				}}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "v1alpha1 - yaml", | ||||
| 			data: []byte(` | ||||
| apiVersion: apiserver.config.k8s.io/v1alpha1 | ||||
| kind: AuthorizationConfiguration | ||||
| authorizers: | ||||
| - type: Webhook | ||||
| `), | ||||
| 			expectConfig: &api.AuthorizationConfiguration{ | ||||
| 				Authorizers: []api.AuthorizerConfiguration{{Type: "Webhook"}}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:      "missing apiVersion", | ||||
| 			data:      []byte(`{"kind":"AuthorizationConfiguration"}`), | ||||
| 			expectErr: `'apiVersion' is missing`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:      "missing kind", | ||||
| 			data:      []byte(`{"apiVersion":"apiserver.config.k8s.io/v1alpha1"}`), | ||||
| 			expectErr: `'Kind' is missing`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:      "unknown group", | ||||
| 			data:      []byte(`{"apiVersion":"apps/v1alpha1","kind":"AuthorizationConfiguration"}`), | ||||
| 			expectErr: `apps/v1alpha1`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:      "unknown version", | ||||
| 			data:      []byte(`{"apiVersion":"apiserver.config.k8s.io/v99","kind":"AuthorizationConfiguration"}`), | ||||
| 			expectErr: `apiserver.config.k8s.io/v99`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name:      "unknown kind", | ||||
| 			data:      []byte(`{"apiVersion":"apiserver.config.k8s.io/v1alpha1","kind":"SomeConfiguration"}`), | ||||
| 			expectErr: `SomeConfiguration`, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "unknown field", | ||||
| 			data: []byte(`{ | ||||
| "apiVersion":"apiserver.config.k8s.io/v1alpha1", | ||||
| "kind":"AuthorizationConfiguration", | ||||
| "authorzers":[{"type":"Webhook"}]}`), | ||||
| 			expectErr: `unknown field "authorzers"`, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range testcases { | ||||
| 		t.Run(tc.name, func(t *testing.T) { | ||||
| 			config, err := LoadFromData(tc.data) | ||||
| 			if err != nil { | ||||
| 				if len(tc.expectErr) == 0 { | ||||
| 					t.Fatalf("unexpected error: %v", err) | ||||
| 				} | ||||
| 				if !strings.Contains(err.Error(), tc.expectErr) { | ||||
| 					t.Fatalf("unexpected error: %v", err) | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
| 			if len(tc.expectErr) > 0 { | ||||
| 				t.Fatalf("expected err, got none") | ||||
| 			} | ||||
|  | ||||
| 			if !reflect.DeepEqual(config, tc.expectConfig) { | ||||
| 				t.Fatalf("unexpected config:\n%s", cmp.Diff(tc.expectConfig, config)) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										1
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							| @@ -1457,6 +1457,7 @@ k8s.io/apiserver/pkg/admission/plugin/webhook/validating | ||||
| k8s.io/apiserver/pkg/admission/testing | ||||
| k8s.io/apiserver/pkg/apis/apiserver | ||||
| k8s.io/apiserver/pkg/apis/apiserver/install | ||||
| k8s.io/apiserver/pkg/apis/apiserver/load | ||||
| k8s.io/apiserver/pkg/apis/apiserver/v1 | ||||
| k8s.io/apiserver/pkg/apis/apiserver/v1alpha1 | ||||
| k8s.io/apiserver/pkg/apis/apiserver/v1beta1 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Nabarun Pal
					Nabarun Pal