From 22e5a806a73e48486a90491fc3eb03d208b520a0 Mon Sep 17 00:00:00 2001 From: Nabarun Pal Date: Mon, 25 Sep 2023 09:18:11 +0530 Subject: [PATCH] Add --authorization-config flag to apiserver Signed-off-by: Nabarun Pal --- .../app/options/options_test.go | 7 + .../apiserver/options/options_test.go | 6 + pkg/kubeapiserver/options/authorization.go | 150 ++++++++- .../options/authorization_test.go | 7 + .../apiserver/pkg/apis/apiserver/load/load.go | 82 +++++ .../pkg/apis/apiserver/load/load_test.go | 290 ++++++++++++++++++ vendor/modules.txt | 1 + 7 files changed, 532 insertions(+), 11 deletions(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load_test.go diff --git a/cmd/kube-apiserver/app/options/options_test.go b/cmd/kube-apiserver/app/options/options_test.go index 9dabfa2dd34..367664c4e8f 100644 --- a/cmd/kube-apiserver/app/options/options_test.go +++ b/cmd/kube-apiserver/app/options/options_test.go @@ -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{}))) } diff --git a/pkg/controlplane/apiserver/options/options_test.go b/pkg/controlplane/apiserver/options/options_test.go index f11ee47f676..c26f2373032 100644 --- a/pkg/controlplane/apiserver/options/options_test.go +++ b/pkg/controlplane/apiserver/options/options_test.go @@ -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{}))) } diff --git a/pkg/kubeapiserver/options/authorization.go b/pkg/kubeapiserver/options/authorization.go index e1c6e37ee3d..4e9e24fb729 100644 --- a/pkg/kubeapiserver/options/authorization.go +++ b/pkg/kubeapiserver/options/authorization.go @@ -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 } diff --git a/pkg/kubeapiserver/options/authorization_test.go b/pkg/kubeapiserver/options/authorization_test.go index 97ef24ac2d1..306fa976df7 100644 --- a/pkg/kubeapiserver/options/authorization_test.go +++ b/pkg/kubeapiserver/options/authorization_test.go @@ -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)) } diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load.go new file mode 100644 index 00000000000..575b7c49f0e --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load.go @@ -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 +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load_test.go b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load_test.go new file mode 100644 index 00000000000..9459c848d76 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/apiserver/load/load_test.go @@ -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)) + } + }) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 313f1b74099..cf26c14ae2b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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