mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	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"
 | 
			
		||||
 | 
			
		||||
@@ -36,8 +41,18 @@ import (
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	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,17 +233,52 @@ func (o *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFac
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	authzConfiguration, err := o.buildAuthorizationConfiguration()
 | 
			
		||||
	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{
 | 
			
		||||
		PolicyFile:               o.PolicyFile,
 | 
			
		||||
		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