mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 18:00:08 +00:00
Merge pull request #119142 from aramase/aramase/f/kep_3331_add_feature_flag
[StructuredAuthenticationConfig] Add feature flag and wire up `--authentication-config` flag
This commit is contained in:
commit
f68c66f96d
@ -257,11 +257,8 @@ func TestAddFlags(t *testing.T) {
|
|||||||
RetryBackoff: apiserveroptions.DefaultAuthWebhookRetryBackoff(),
|
RetryBackoff: apiserveroptions.DefaultAuthWebhookRetryBackoff(),
|
||||||
},
|
},
|
||||||
BootstrapToken: &kubeoptions.BootstrapTokenAuthenticationOptions{},
|
BootstrapToken: &kubeoptions.BootstrapTokenAuthenticationOptions{},
|
||||||
OIDC: &kubeoptions.OIDCAuthenticationOptions{
|
OIDC: s.Authentication.OIDC,
|
||||||
UsernameClaim: "sub",
|
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{},
|
||||||
SigningAlgs: []string{"RS256"},
|
|
||||||
},
|
|
||||||
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{},
|
|
||||||
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
||||||
Lookup: true,
|
Lookup: true,
|
||||||
ExtendExpiration: true,
|
ExtendExpiration: true,
|
||||||
@ -327,7 +324,10 @@ func TestAddFlags(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected.Authentication.OIDC.UsernameClaim = "sub"
|
||||||
|
expected.Authentication.OIDC.SigningAlgs = []string{"RS256"}
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, s) {
|
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{})))
|
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,11 +243,8 @@ func TestAddFlags(t *testing.T) {
|
|||||||
RetryBackoff: apiserveroptions.DefaultAuthWebhookRetryBackoff(),
|
RetryBackoff: apiserveroptions.DefaultAuthWebhookRetryBackoff(),
|
||||||
},
|
},
|
||||||
BootstrapToken: &kubeoptions.BootstrapTokenAuthenticationOptions{},
|
BootstrapToken: &kubeoptions.BootstrapTokenAuthenticationOptions{},
|
||||||
OIDC: &kubeoptions.OIDCAuthenticationOptions{
|
OIDC: s.Authentication.OIDC,
|
||||||
UsernameClaim: "sub",
|
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{},
|
||||||
SigningAlgs: []string{"RS256"},
|
|
||||||
},
|
|
||||||
RequestHeader: &apiserveroptions.RequestHeaderAuthenticationOptions{},
|
|
||||||
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
ServiceAccounts: &kubeoptions.ServiceAccountAuthenticationOptions{
|
||||||
Lookup: true,
|
Lookup: true,
|
||||||
ExtendExpiration: true,
|
ExtendExpiration: true,
|
||||||
@ -283,7 +280,10 @@ func TestAddFlags(t *testing.T) {
|
|||||||
AggregatorRejectForwardingRedirects: true,
|
AggregatorRejectForwardingRedirects: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expected.Authentication.OIDC.UsernameClaim = "sub"
|
||||||
|
expected.Authentication.OIDC.SigningAlgs = []string{"RS256"}
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, s) {
|
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{})))
|
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,19 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/apiserver/pkg/apis/apiserver"
|
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||||
|
"k8s.io/apiserver/pkg/apis/apiserver/install"
|
||||||
apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
|
apiservervalidation "k8s.io/apiserver/pkg/apis/apiserver/validation"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/apiserver/pkg/server/egressselector"
|
"k8s.io/apiserver/pkg/server/egressselector"
|
||||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/informers"
|
"k8s.io/client-go/informers"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
cliflag "k8s.io/component-base/cli/flag"
|
cliflag "k8s.io/component-base/cli/flag"
|
||||||
@ -47,6 +52,18 @@ import (
|
|||||||
"k8s.io/utils/pointer"
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
oidcIssuerURLFlag = "oidc-issuer-url"
|
||||||
|
oidcClientIDFlag = "oidc-client-id"
|
||||||
|
oidcCAFileFlag = "oidc-ca-file"
|
||||||
|
oidcUsernameClaimFlag = "oidc-username-claim"
|
||||||
|
oidcUsernamePrefixFlag = "oidc-username-prefix"
|
||||||
|
oidcGroupsClaimFlag = "oidc-groups-claim"
|
||||||
|
oidcGroupsPrefixFlag = "oidc-groups-prefix"
|
||||||
|
oidcSigningAlgsFlag = "oidc-signing-algs"
|
||||||
|
oidcRequiredClaimFlag = "oidc-required-claim"
|
||||||
|
)
|
||||||
|
|
||||||
// BuiltInAuthenticationOptions contains all build-in authentication options for API Server
|
// BuiltInAuthenticationOptions contains all build-in authentication options for API Server
|
||||||
type BuiltInAuthenticationOptions struct {
|
type BuiltInAuthenticationOptions struct {
|
||||||
APIAudiences []string
|
APIAudiences []string
|
||||||
@ -59,6 +76,8 @@ type BuiltInAuthenticationOptions struct {
|
|||||||
TokenFile *TokenFileAuthenticationOptions
|
TokenFile *TokenFileAuthenticationOptions
|
||||||
WebHook *WebHookAuthenticationOptions
|
WebHook *WebHookAuthenticationOptions
|
||||||
|
|
||||||
|
AuthenticationConfigFile string
|
||||||
|
|
||||||
TokenSuccessCacheTTL time.Duration
|
TokenSuccessCacheTTL time.Duration
|
||||||
TokenFailureCacheTTL time.Duration
|
TokenFailureCacheTTL time.Duration
|
||||||
}
|
}
|
||||||
@ -84,6 +103,9 @@ type OIDCAuthenticationOptions struct {
|
|||||||
GroupsPrefix string
|
GroupsPrefix string
|
||||||
SigningAlgs []string
|
SigningAlgs []string
|
||||||
RequiredClaims map[string]string
|
RequiredClaims map[string]string
|
||||||
|
|
||||||
|
// areFlagsConfigured is a function that returns true if any of the oidc-* flags are configured.
|
||||||
|
areFlagsConfigured func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceAccountAuthenticationOptions contains service account authentication options for API Server
|
// ServiceAccountAuthenticationOptions contains service account authentication options for API Server
|
||||||
@ -154,7 +176,7 @@ func (o *BuiltInAuthenticationOptions) WithClientCert() *BuiltInAuthenticationOp
|
|||||||
|
|
||||||
// WithOIDC set default value for OIDC authentication
|
// WithOIDC set default value for OIDC authentication
|
||||||
func (o *BuiltInAuthenticationOptions) WithOIDC() *BuiltInAuthenticationOptions {
|
func (o *BuiltInAuthenticationOptions) WithOIDC() *BuiltInAuthenticationOptions {
|
||||||
o.OIDC = &OIDCAuthenticationOptions{}
|
o.OIDC = &OIDCAuthenticationOptions{areFlagsConfigured: func() bool { return false }}
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,9 +212,7 @@ func (o *BuiltInAuthenticationOptions) WithWebHook() *BuiltInAuthenticationOptio
|
|||||||
func (o *BuiltInAuthenticationOptions) Validate() []error {
|
func (o *BuiltInAuthenticationOptions) Validate() []error {
|
||||||
var allErrors []error
|
var allErrors []error
|
||||||
|
|
||||||
if o.OIDC != nil && (len(o.OIDC.IssuerURL) > 0) != (len(o.OIDC.ClientID) > 0) {
|
allErrors = append(allErrors, o.validateOIDCOptions()...)
|
||||||
allErrors = append(allErrors, fmt.Errorf("oidc-issuer-url and oidc-client-id should be specified together"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.ServiceAccounts != nil && len(o.ServiceAccounts.Issuers) > 0 {
|
if o.ServiceAccounts != nil && len(o.ServiceAccounts.Issuers) > 0 {
|
||||||
seen := make(map[string]bool)
|
seen := make(map[string]bool)
|
||||||
@ -274,45 +294,63 @@ func (o *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if o.OIDC != nil {
|
if o.OIDC != nil {
|
||||||
fs.StringVar(&o.OIDC.IssuerURL, "oidc-issuer-url", o.OIDC.IssuerURL, ""+
|
fs.StringVar(&o.OIDC.IssuerURL, oidcIssuerURLFlag, o.OIDC.IssuerURL, ""+
|
||||||
"The URL of the OpenID issuer, only HTTPS scheme will be accepted. "+
|
"The URL of the OpenID issuer, only HTTPS scheme will be accepted. "+
|
||||||
"If set, it will be used to verify the OIDC JSON Web Token (JWT).")
|
"If set, it will be used to verify the OIDC JSON Web Token (JWT).")
|
||||||
|
|
||||||
fs.StringVar(&o.OIDC.ClientID, "oidc-client-id", o.OIDC.ClientID,
|
fs.StringVar(&o.OIDC.ClientID, oidcClientIDFlag, o.OIDC.ClientID,
|
||||||
"The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set.")
|
"The client ID for the OpenID Connect client, must be set if oidc-issuer-url is set.")
|
||||||
|
|
||||||
fs.StringVar(&o.OIDC.CAFile, "oidc-ca-file", o.OIDC.CAFile, ""+
|
fs.StringVar(&o.OIDC.CAFile, oidcCAFileFlag, o.OIDC.CAFile, ""+
|
||||||
"If set, the OpenID server's certificate will be verified by one of the authorities "+
|
"If set, the OpenID server's certificate will be verified by one of the authorities "+
|
||||||
"in the oidc-ca-file, otherwise the host's root CA set will be used.")
|
"in the oidc-ca-file, otherwise the host's root CA set will be used.")
|
||||||
|
|
||||||
fs.StringVar(&o.OIDC.UsernameClaim, "oidc-username-claim", "sub", ""+
|
fs.StringVar(&o.OIDC.UsernameClaim, oidcUsernameClaimFlag, "sub", ""+
|
||||||
"The OpenID claim to use as the user name. Note that claims other than the default ('sub') "+
|
"The OpenID claim to use as the user name. Note that claims other than the default ('sub') "+
|
||||||
"is not guaranteed to be unique and immutable. This flag is experimental, please see "+
|
"is not guaranteed to be unique and immutable. This flag is experimental, please see "+
|
||||||
"the authentication documentation for further details.")
|
"the authentication documentation for further details.")
|
||||||
|
|
||||||
fs.StringVar(&o.OIDC.UsernamePrefix, "oidc-username-prefix", "", ""+
|
fs.StringVar(&o.OIDC.UsernamePrefix, oidcUsernamePrefixFlag, "", ""+
|
||||||
"If provided, all usernames will be prefixed with this value. If not provided, "+
|
"If provided, all usernames will be prefixed with this value. If not provided, "+
|
||||||
"username claims other than 'email' are prefixed by the issuer URL to avoid "+
|
"username claims other than 'email' are prefixed by the issuer URL to avoid "+
|
||||||
"clashes. To skip any prefixing, provide the value '-'.")
|
"clashes. To skip any prefixing, provide the value '-'.")
|
||||||
|
|
||||||
fs.StringVar(&o.OIDC.GroupsClaim, "oidc-groups-claim", "", ""+
|
fs.StringVar(&o.OIDC.GroupsClaim, oidcGroupsClaimFlag, "", ""+
|
||||||
"If provided, the name of a custom OpenID Connect claim for specifying user groups. "+
|
"If provided, the name of a custom OpenID Connect claim for specifying user groups. "+
|
||||||
"The claim value is expected to be a string or array of strings. This flag is experimental, "+
|
"The claim value is expected to be a string or array of strings. This flag is experimental, "+
|
||||||
"please see the authentication documentation for further details.")
|
"please see the authentication documentation for further details.")
|
||||||
|
|
||||||
fs.StringVar(&o.OIDC.GroupsPrefix, "oidc-groups-prefix", "", ""+
|
fs.StringVar(&o.OIDC.GroupsPrefix, oidcGroupsPrefixFlag, "", ""+
|
||||||
"If provided, all groups will be prefixed with this value to prevent conflicts with "+
|
"If provided, all groups will be prefixed with this value to prevent conflicts with "+
|
||||||
"other authentication strategies.")
|
"other authentication strategies.")
|
||||||
|
|
||||||
fs.StringSliceVar(&o.OIDC.SigningAlgs, "oidc-signing-algs", []string{"RS256"}, ""+
|
fs.StringSliceVar(&o.OIDC.SigningAlgs, oidcSigningAlgsFlag, []string{"RS256"}, ""+
|
||||||
"Comma-separated list of allowed JOSE asymmetric signing algorithms. JWTs with a "+
|
"Comma-separated list of allowed JOSE asymmetric signing algorithms. JWTs with a "+
|
||||||
"supported 'alg' header values are: RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512. "+
|
"supported 'alg' header values are: RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512. "+
|
||||||
"Values are defined by RFC 7518 https://tools.ietf.org/html/rfc7518#section-3.1.")
|
"Values are defined by RFC 7518 https://tools.ietf.org/html/rfc7518#section-3.1.")
|
||||||
|
|
||||||
fs.Var(cliflag.NewMapStringStringNoSplit(&o.OIDC.RequiredClaims), "oidc-required-claim", ""+
|
fs.Var(cliflag.NewMapStringStringNoSplit(&o.OIDC.RequiredClaims), oidcRequiredClaimFlag, ""+
|
||||||
"A key=value pair that describes a required claim in the ID Token. "+
|
"A key=value pair that describes a required claim in the ID Token. "+
|
||||||
"If set, the claim is verified to be present in the ID Token with a matching value. "+
|
"If set, the claim is verified to be present in the ID Token with a matching value. "+
|
||||||
"Repeat this flag to specify multiple claims.")
|
"Repeat this flag to specify multiple claims.")
|
||||||
|
|
||||||
|
fs.StringVar(&o.AuthenticationConfigFile, "authentication-config", o.AuthenticationConfigFile, ""+
|
||||||
|
"File with Authentication Configuration to configure the JWT Token authenticator. "+
|
||||||
|
"Note: This feature is in Alpha since v1.29."+
|
||||||
|
"--feature-gate=StructuredAuthenticationConfiguration=true needs to be set for enabling this feature."+
|
||||||
|
"This feature is mutually exclusive with the oidc-* flags.")
|
||||||
|
|
||||||
|
o.OIDC.areFlagsConfigured = func() bool {
|
||||||
|
return fs.Changed(oidcIssuerURLFlag) ||
|
||||||
|
fs.Changed(oidcClientIDFlag) ||
|
||||||
|
fs.Changed(oidcCAFileFlag) ||
|
||||||
|
fs.Changed(oidcUsernameClaimFlag) ||
|
||||||
|
fs.Changed(oidcUsernamePrefixFlag) ||
|
||||||
|
fs.Changed(oidcGroupsClaimFlag) ||
|
||||||
|
fs.Changed(oidcGroupsPrefixFlag) ||
|
||||||
|
fs.Changed(oidcSigningAlgsFlag) ||
|
||||||
|
fs.Changed(oidcRequiredClaimFlag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.RequestHeader != nil {
|
if o.RequestHeader != nil {
|
||||||
@ -401,7 +439,14 @@ func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.OIDC != nil && len(o.OIDC.IssuerURL) > 0 && len(o.OIDC.ClientID) > 0 {
|
// When the StructuredAuthenticationConfiguration feature is enabled and the authentication config file is provided,
|
||||||
|
// load the authentication config from the file.
|
||||||
|
if len(o.AuthenticationConfigFile) > 0 {
|
||||||
|
var err error
|
||||||
|
if ret.AuthenticationConfig, err = loadAuthenticationConfig(o.AuthenticationConfigFile); err != nil {
|
||||||
|
return kubeauthenticator.Config{}, err
|
||||||
|
}
|
||||||
|
} else if o.OIDC != nil && len(o.OIDC.IssuerURL) > 0 && len(o.OIDC.ClientID) > 0 {
|
||||||
usernamePrefix := o.OIDC.UsernamePrefix
|
usernamePrefix := o.OIDC.UsernamePrefix
|
||||||
|
|
||||||
if o.OIDC.UsernamePrefix == "" && o.OIDC.UsernameClaim != "email" {
|
if o.OIDC.UsernamePrefix == "" && o.OIDC.UsernameClaim != "email" {
|
||||||
@ -458,13 +503,17 @@ func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat
|
|||||||
authConfig := &apiserver.AuthenticationConfiguration{
|
authConfig := &apiserver.AuthenticationConfiguration{
|
||||||
JWT: []apiserver.JWTAuthenticator{jwtAuthenticator},
|
JWT: []apiserver.JWTAuthenticator{jwtAuthenticator},
|
||||||
}
|
}
|
||||||
if err := apiservervalidation.ValidateAuthenticationConfiguration(authConfig).ToAggregate(); err != nil {
|
|
||||||
return kubeauthenticator.Config{}, err
|
|
||||||
}
|
|
||||||
ret.AuthenticationConfig = authConfig
|
ret.AuthenticationConfig = authConfig
|
||||||
ret.OIDCSigningAlgs = o.OIDC.SigningAlgs
|
ret.OIDCSigningAlgs = o.OIDC.SigningAlgs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ret.AuthenticationConfig != nil {
|
||||||
|
if err := apiservervalidation.ValidateAuthenticationConfiguration(ret.AuthenticationConfig).ToAggregate(); err != nil {
|
||||||
|
return kubeauthenticator.Config{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if o.RequestHeader != nil {
|
if o.RequestHeader != nil {
|
||||||
var err error
|
var err error
|
||||||
ret.RequestHeaderConfig, err = o.RequestHeader.ToAuthenticationRequestHeaderConfig()
|
ret.RequestHeaderConfig, err = o.RequestHeader.ToAuthenticationRequestHeaderConfig()
|
||||||
@ -584,3 +633,62 @@ func (o *BuiltInAuthenticationOptions) ApplyAuthorization(authorization *BuiltIn
|
|||||||
o.Anonymous.Allow = false
|
o.Anonymous.Allow = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *BuiltInAuthenticationOptions) validateOIDCOptions() []error {
|
||||||
|
var allErrors []error
|
||||||
|
|
||||||
|
// Existing validation when jwt authenticator is configured with oidc-* flags
|
||||||
|
if len(o.AuthenticationConfigFile) == 0 {
|
||||||
|
if o.OIDC != nil && o.OIDC.areFlagsConfigured() && (len(o.OIDC.IssuerURL) == 0 || len(o.OIDC.ClientID) == 0) {
|
||||||
|
allErrors = append(allErrors, fmt.Errorf("oidc-issuer-url and oidc-client-id must be specified together when any oidc-* flags are set"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
// New validation when authentication config file is provided
|
||||||
|
|
||||||
|
// Authentication config file is only supported when the StructuredAuthenticationConfiguration feature is enabled
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StructuredAuthenticationConfiguration) {
|
||||||
|
allErrors = append(allErrors, fmt.Errorf("set --feature-gates=%s=true to use authentication-config file", genericfeatures.StructuredAuthenticationConfiguration))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication config file and oidc-* flags are mutually exclusive
|
||||||
|
if o.OIDC != nil && o.OIDC.areFlagsConfigured() {
|
||||||
|
allErrors = append(allErrors, fmt.Errorf("authentication-config file and oidc-* flags are mutually exclusive"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return allErrors
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
cfgScheme = runtime.NewScheme()
|
||||||
|
codecs = serializer.NewCodecFactory(cfgScheme, serializer.EnableStrict)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
install.Install(cfgScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAuthenticationConfig parses the authentication configuration from the given file and returns it.
|
||||||
|
func loadAuthenticationConfig(configFilePath string) (*apiserver.AuthenticationConfiguration, error) {
|
||||||
|
// read from file
|
||||||
|
data, err := os.ReadFile(configFilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(data) == 0 {
|
||||||
|
return nil, fmt.Errorf("empty config file %q", configFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedObj, err := runtime.Decode(codecs.UniversalDecoder(), data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
configuration, ok := decodedObj.(*apiserver.AuthenticationConfiguration)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected AuthenticationConfiguration, got %T", decodedObj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuration, nil
|
||||||
|
}
|
||||||
|
@ -32,18 +32,22 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
|
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
|
||||||
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
||||||
|
"k8s.io/apiserver/pkg/features"
|
||||||
apiserveroptions "k8s.io/apiserver/pkg/server/options"
|
apiserveroptions "k8s.io/apiserver/pkg/server/options"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
|
kubeauthenticator "k8s.io/kubernetes/pkg/kubeapiserver/authenticator"
|
||||||
"k8s.io/utils/pointer"
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthenticationValidate(t *testing.T) {
|
func TestAuthenticationValidate(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
testOIDC *OIDCAuthenticationOptions
|
testOIDC *OIDCAuthenticationOptions
|
||||||
testSA *ServiceAccountAuthenticationOptions
|
testSA *ServiceAccountAuthenticationOptions
|
||||||
testWebHook *WebHookAuthenticationOptions
|
testWebHook *WebHookAuthenticationOptions
|
||||||
expectErr string
|
testAuthenticationConfigFile string
|
||||||
|
expectErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "test when OIDC and ServiceAccounts are nil",
|
name: "test when OIDC and ServiceAccounts are nil",
|
||||||
@ -51,10 +55,11 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when OIDC and ServiceAccounts are valid",
|
name: "test when OIDC and ServiceAccounts are valid",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
Issuers: []string{"http://foo.bar.com"},
|
Issuers: []string{"http://foo.bar.com"},
|
||||||
@ -64,23 +69,25 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when OIDC is invalid",
|
name: "test when OIDC is invalid",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
Issuers: []string{"http://foo.bar.com"},
|
Issuers: []string{"http://foo.bar.com"},
|
||||||
KeyFiles: []string{"testkeyfile1", "testkeyfile2"},
|
KeyFiles: []string{"testkeyfile1", "testkeyfile2"},
|
||||||
},
|
},
|
||||||
expectErr: "oidc-issuer-url and oidc-client-id should be specified together",
|
expectErr: "oidc-issuer-url and oidc-client-id must be specified together when any oidc-* flags are set",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "test when ServiceAccounts doesn't have key file",
|
name: "test when ServiceAccounts doesn't have key file",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
Issuers: []string{"http://foo.bar.com"},
|
Issuers: []string{"http://foo.bar.com"},
|
||||||
@ -90,10 +97,11 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when ServiceAccounts doesn't have issuer",
|
name: "test when ServiceAccounts doesn't have issuer",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
Issuers: []string{},
|
Issuers: []string{},
|
||||||
@ -103,10 +111,11 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when ServiceAccounts has empty string as issuer",
|
name: "test when ServiceAccounts has empty string as issuer",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
Issuers: []string{""},
|
Issuers: []string{""},
|
||||||
@ -116,10 +125,11 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when ServiceAccounts has duplicate issuers",
|
name: "test when ServiceAccounts has duplicate issuers",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
Issuers: []string{"http://foo.bar.com", "http://foo.bar.com"},
|
Issuers: []string{"http://foo.bar.com", "http://foo.bar.com"},
|
||||||
@ -129,10 +139,11 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when ServiceAccount has bad issuer",
|
name: "test when ServiceAccount has bad issuer",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
Issuers: []string{"http://[::1]:namedport"},
|
Issuers: []string{"http://[::1]:namedport"},
|
||||||
@ -142,10 +153,11 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when ServiceAccounts has invalid JWKSURI",
|
name: "test when ServiceAccounts has invalid JWKSURI",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
KeyFiles: []string{"cert", "key"},
|
KeyFiles: []string{"cert", "key"},
|
||||||
@ -157,10 +169,11 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when ServiceAccounts has invalid JWKSURI (not https scheme)",
|
name: "test when ServiceAccounts has invalid JWKSURI (not https scheme)",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
KeyFiles: []string{"cert", "key"},
|
KeyFiles: []string{"cert", "key"},
|
||||||
@ -172,10 +185,11 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "test when WebHook has invalid retry attempts",
|
name: "test when WebHook has invalid retry attempts",
|
||||||
testOIDC: &OIDCAuthenticationOptions{
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
UsernameClaim: "sub",
|
UsernameClaim: "sub",
|
||||||
SigningAlgs: []string{"RS256"},
|
SigningAlgs: []string{"RS256"},
|
||||||
IssuerURL: "https://testIssuerURL",
|
IssuerURL: "https://testIssuerURL",
|
||||||
ClientID: "testClientID",
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
},
|
},
|
||||||
testSA: &ServiceAccountAuthenticationOptions{
|
testSA: &ServiceAccountAuthenticationOptions{
|
||||||
KeyFiles: []string{"cert", "key"},
|
KeyFiles: []string{"cert", "key"},
|
||||||
@ -195,6 +209,23 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expectErr: "number of webhook retry attempts must be greater than 0, but is: 0",
|
expectErr: "number of webhook retry attempts must be greater than 0, but is: 0",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "test when authentication config file is set without feature gate",
|
||||||
|
testAuthenticationConfigFile: "configfile",
|
||||||
|
expectErr: "set --feature-gates=StructuredAuthenticationConfiguration=true to use authentication-config file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "test when authentication config file and oidc-* flags are set",
|
||||||
|
testAuthenticationConfigFile: "configfile",
|
||||||
|
testOIDC: &OIDCAuthenticationOptions{
|
||||||
|
UsernameClaim: "sub",
|
||||||
|
SigningAlgs: []string{"RS256"},
|
||||||
|
IssuerURL: "https://testIssuerURL",
|
||||||
|
ClientID: "testClientID",
|
||||||
|
areFlagsConfigured: func() bool { return true },
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range testCases {
|
for _, testcase := range testCases {
|
||||||
@ -203,6 +234,7 @@ func TestAuthenticationValidate(t *testing.T) {
|
|||||||
options.OIDC = testcase.testOIDC
|
options.OIDC = testcase.testOIDC
|
||||||
options.ServiceAccounts = testcase.testSA
|
options.ServiceAccounts = testcase.testSA
|
||||||
options.WebHook = testcase.testWebHook
|
options.WebHook = testcase.testWebHook
|
||||||
|
options.AuthenticationConfigFile = testcase.testAuthenticationConfigFile
|
||||||
|
|
||||||
errs := options.Validate()
|
errs := options.Validate()
|
||||||
if len(errs) > 0 && (!strings.Contains(utilerrors.NewAggregate(errs).Error(), testcase.expectErr) || testcase.expectErr == "") {
|
if len(errs) > 0 && (!strings.Contains(utilerrors.NewAggregate(errs).Error(), testcase.expectErr) || testcase.expectErr == "") {
|
||||||
@ -402,8 +434,14 @@ func TestBuiltInAuthenticationOptionsAddFlags(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !opts.OIDC.areFlagsConfigured() {
|
||||||
|
t.Fatal("OIDC flags should be configured")
|
||||||
|
}
|
||||||
|
// nil these out because you cannot compare functions
|
||||||
|
opts.OIDC.areFlagsConfigured = nil
|
||||||
|
|
||||||
if !reflect.DeepEqual(opts, expected) {
|
if !reflect.DeepEqual(opts, expected) {
|
||||||
t.Error(cmp.Diff(opts, expected))
|
t.Error(cmp.Diff(opts, expected, cmp.AllowUnexported(OIDCAuthenticationOptions{})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -624,3 +662,355 @@ func TestToAuthenticationConfig_OIDC(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateOIDCOptions(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
args []string
|
||||||
|
structuredAuthenticationConfigEnabled bool
|
||||||
|
expectErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "issuer url and client id are not set",
|
||||||
|
args: []string{
|
||||||
|
"--oidc-username-claim=testClaim",
|
||||||
|
},
|
||||||
|
expectErr: "oidc-issuer-url and oidc-client-id must be specified together when any oidc-* flags are set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issuer url set, client id is not set",
|
||||||
|
args: []string{
|
||||||
|
"--oidc-issuer-url=https://testIssuerURL",
|
||||||
|
"--oidc-username-claim=testClaim",
|
||||||
|
},
|
||||||
|
expectErr: "oidc-issuer-url and oidc-client-id must be specified together when any oidc-* flags are set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issuer url is not set, client id is set",
|
||||||
|
args: []string{
|
||||||
|
"--oidc-client-id=testClientID",
|
||||||
|
"--oidc-username-claim=testClaim",
|
||||||
|
},
|
||||||
|
expectErr: "oidc-issuer-url and oidc-client-id must be specified together when any oidc-* flags are set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issuer url and client id are set",
|
||||||
|
args: []string{
|
||||||
|
"--oidc-client-id=testClientID",
|
||||||
|
"--oidc-issuer-url=https://testIssuerURL",
|
||||||
|
},
|
||||||
|
expectErr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, feature gate is not enabled",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
},
|
||||||
|
expectErr: "set --feature-gates=StructuredAuthenticationConfiguration=true to use authentication-config file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-issuer-url is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-issuer-url=https://testIssuerURL",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-client-id is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-client-id=testClientID",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-username-claim is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-username-claim=testClaim",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-username-prefix is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-username-prefix=testPrefix",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-ca-file is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-ca-file=testCAFile",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-groups-claim is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-groups-claim=testClaim",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-groups-prefix is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-groups-prefix=testPrefix",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-required-claim is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-required-claim=foo=bar",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-signature-algs is set",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-signing-algs=RS512",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-username-claim flag not set, defaulting shouldn't error",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
},
|
||||||
|
expectErr: "",
|
||||||
|
structuredAuthenticationConfigEnabled: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "authentication-config file, --oidc-username-claim flag explicitly set with default value should error",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
"--oidc-username-claim=sub",
|
||||||
|
},
|
||||||
|
expectErr: "authentication-config file and oidc-* flags are mutually exclusive",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid authentication-config file",
|
||||||
|
args: []string{
|
||||||
|
"--authentication-config=configfile",
|
||||||
|
},
|
||||||
|
structuredAuthenticationConfigEnabled: true,
|
||||||
|
expectErr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthenticationConfiguration, tt.structuredAuthenticationConfigEnabled)()
|
||||||
|
|
||||||
|
opts := NewBuiltInAuthenticationOptions().WithOIDC()
|
||||||
|
pf := pflag.NewFlagSet("test-builtin-authentication-opts", pflag.ContinueOnError)
|
||||||
|
opts.AddFlags(pf)
|
||||||
|
|
||||||
|
if err := pf.Parse(tt.args); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := opts.Validate()
|
||||||
|
if len(errs) > 0 && (!strings.Contains(utilerrors.NewAggregate(errs).Error(), tt.expectErr) || tt.expectErr == "") {
|
||||||
|
t.Errorf("Got err: %v, Expected err: %s", errs, tt.expectErr)
|
||||||
|
}
|
||||||
|
if len(errs) == 0 && len(tt.expectErr) != 0 {
|
||||||
|
t.Errorf("Got err nil, Expected err: %s", tt.expectErr)
|
||||||
|
}
|
||||||
|
if len(errs) > 0 && len(tt.expectErr) == 0 {
|
||||||
|
t.Errorf("Got err: %v, Expected err nil", errs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoadAuthenticationConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
file func() string
|
||||||
|
expectErr string
|
||||||
|
expectedConfig *apiserver.AuthenticationConfiguration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty file",
|
||||||
|
file: func() string { return writeTempFile(t, ``) },
|
||||||
|
expectErr: "empty config file",
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid file",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t,
|
||||||
|
`{
|
||||||
|
"apiVersion":"apiserver.config.k8s.io/v1alpha1",
|
||||||
|
"kind":"AuthenticationConfiguration",
|
||||||
|
"jwt":[{"issuer":{"url": "https://test-issuer"}}]}`)
|
||||||
|
},
|
||||||
|
expectErr: "",
|
||||||
|
expectedConfig: &apiserver.AuthenticationConfiguration{
|
||||||
|
JWT: []apiserver.JWTAuthenticator{
|
||||||
|
{
|
||||||
|
Issuer: apiserver.Issuer{URL: "https://test-issuer"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing file",
|
||||||
|
file: func() string { return "bogus-missing-file" },
|
||||||
|
expectErr: "no such file or directory",
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid content file",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t, `{"apiVersion":"apiserver.config.k8s.io/v99","kind":"AuthenticationConfiguration","authorizers":{"type":"Webhook"}}`)
|
||||||
|
},
|
||||||
|
expectErr: `no kind "AuthenticationConfiguration" is registered for version "apiserver.config.k8s.io/v99"`,
|
||||||
|
expectedConfig: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing apiVersion",
|
||||||
|
file: func() string { return writeTempFile(t, `{"kind":"AuthenticationConfiguration"}`) },
|
||||||
|
expectErr: `'apiVersion' is missing`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing kind",
|
||||||
|
file: func() string { return writeTempFile(t, `{"apiVersion":"apiserver.config.k8s.io/v1alpha1"}`) },
|
||||||
|
expectErr: `'Kind' is missing`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown group",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t, `{"apiVersion":"apps/v1alpha1","kind":"AuthenticationConfiguration"}`)
|
||||||
|
},
|
||||||
|
expectErr: `apps/v1alpha1`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown version",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t, `{"apiVersion":"apiserver.config.k8s.io/v99","kind":"AuthenticationConfiguration"}`)
|
||||||
|
},
|
||||||
|
expectErr: `apiserver.config.k8s.io/v99`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown kind",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t, `{"apiVersion":"apiserver.config.k8s.io/v1alpha1","kind":"SomeConfiguration"}`)
|
||||||
|
},
|
||||||
|
expectErr: `SomeConfiguration`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown field",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t, `{
|
||||||
|
"apiVersion":"apiserver.config.k8s.io/v1alpha1",
|
||||||
|
"kind":"AuthenticationConfiguration",
|
||||||
|
"jwt1":[{"issuer":{"url": "https://test-issuer"}}]}`)
|
||||||
|
},
|
||||||
|
expectErr: `unknown field "jwt1"`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v1alpha1 - json",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t, `{
|
||||||
|
"apiVersion":"apiserver.config.k8s.io/v1alpha1",
|
||||||
|
"kind":"AuthenticationConfiguration",
|
||||||
|
"jwt":[{"issuer":{"url": "https://test-issuer"}}]}`)
|
||||||
|
},
|
||||||
|
expectedConfig: &apiserver.AuthenticationConfiguration{
|
||||||
|
JWT: []apiserver.JWTAuthenticator{
|
||||||
|
{
|
||||||
|
Issuer: apiserver.Issuer{
|
||||||
|
URL: "https://test-issuer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v1alpha1 - yaml",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t, `
|
||||||
|
apiVersion: apiserver.config.k8s.io/v1alpha1
|
||||||
|
kind: AuthenticationConfiguration
|
||||||
|
jwt:
|
||||||
|
- issuer:
|
||||||
|
url: https://test-issuer
|
||||||
|
claimMappings:
|
||||||
|
username:
|
||||||
|
claim: sub
|
||||||
|
prefix: ""
|
||||||
|
`)
|
||||||
|
},
|
||||||
|
expectedConfig: &apiserver.AuthenticationConfiguration{
|
||||||
|
JWT: []apiserver.JWTAuthenticator{
|
||||||
|
{
|
||||||
|
Issuer: apiserver.Issuer{
|
||||||
|
URL: "https://test-issuer",
|
||||||
|
},
|
||||||
|
ClaimMappings: apiserver.ClaimMappings{
|
||||||
|
Username: apiserver.PrefixedClaimOrExpression{
|
||||||
|
Claim: "sub",
|
||||||
|
Prefix: pointer.String(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "v1alpha1 - no jwt",
|
||||||
|
file: func() string {
|
||||||
|
return writeTempFile(t, `{
|
||||||
|
"apiVersion":"apiserver.config.k8s.io/v1alpha1",
|
||||||
|
"kind":"AuthenticationConfiguration"}`)
|
||||||
|
},
|
||||||
|
expectedConfig: &apiserver.AuthenticationConfiguration{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
config, err := loadAuthenticationConfig(tc.file())
|
||||||
|
if !strings.Contains(errString(err), tc.expectErr) {
|
||||||
|
t.Fatalf("expected error %q, got %v", tc.expectErr, err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(config, tc.expectedConfig) {
|
||||||
|
t.Fatalf("unexpected config:\n%s", cmp.Diff(tc.expectedConfig, config))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 errString(err error) string {
|
||||||
|
if err == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
|
@ -43,6 +43,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||||||
)
|
)
|
||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
&AdmissionConfiguration{},
|
&AdmissionConfiguration{},
|
||||||
|
&AuthenticationConfiguration{},
|
||||||
&EgressSelectorConfiguration{},
|
&EgressSelectorConfiguration{},
|
||||||
&TracingConfiguration{},
|
&TracingConfiguration{},
|
||||||
)
|
)
|
||||||
|
@ -53,6 +53,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||||||
&EgressSelectorConfiguration{},
|
&EgressSelectorConfiguration{},
|
||||||
)
|
)
|
||||||
scheme.AddKnownTypes(ConfigSchemeGroupVersion,
|
scheme.AddKnownTypes(ConfigSchemeGroupVersion,
|
||||||
|
&AuthenticationConfiguration{},
|
||||||
&TracingConfiguration{},
|
&TracingConfiguration{},
|
||||||
)
|
)
|
||||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
|
@ -198,6 +198,13 @@ const (
|
|||||||
// document.
|
// document.
|
||||||
StorageVersionHash featuregate.Feature = "StorageVersionHash"
|
StorageVersionHash featuregate.Feature = "StorageVersionHash"
|
||||||
|
|
||||||
|
// owner: @aramase, @enj, @nabokihms
|
||||||
|
// kep: https://kep.k8s.io/3331
|
||||||
|
// alpha: v1.29
|
||||||
|
//
|
||||||
|
// Enables Structured Authentication Configuration
|
||||||
|
StructuredAuthenticationConfiguration featuregate.Feature = "StructuredAuthenticationConfiguration"
|
||||||
|
|
||||||
// owner: @wojtek-t
|
// owner: @wojtek-t
|
||||||
// alpha: v1.15
|
// alpha: v1.15
|
||||||
// beta: v1.16
|
// beta: v1.16
|
||||||
@ -278,6 +285,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
|
|
||||||
StorageVersionHash: {Default: true, PreRelease: featuregate.Beta},
|
StorageVersionHash: {Default: true, PreRelease: featuregate.Beta},
|
||||||
|
|
||||||
|
StructuredAuthenticationConfiguration: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
WatchBookmark: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
WatchBookmark: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
|
|
||||||
InPlacePodVerticalScaling: {Default: false, PreRelease: featuregate.Alpha},
|
InPlacePodVerticalScaling: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
@ -27,23 +27,28 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apiserver/pkg/features"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd/api"
|
"k8s.io/client-go/tools/clientcmd/api"
|
||||||
certutil "k8s.io/client-go/util/cert"
|
certutil "k8s.io/client-go/util/cert"
|
||||||
|
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||||
kubeapiserverapptesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
kubeapiserverapptesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||||
"k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
|
||||||
"k8s.io/kubernetes/test/integration/framework"
|
"k8s.io/kubernetes/test/integration/framework"
|
||||||
utilsoidc "k8s.io/kubernetes/test/utils/oidc"
|
utilsoidc "k8s.io/kubernetes/test/utils/oidc"
|
||||||
utilsnet "k8s.io/utils/net"
|
utilsnet "k8s.io/utils/net"
|
||||||
@ -95,9 +100,21 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestOIDC(t *testing.T) {
|
func TestOIDC(t *testing.T) {
|
||||||
|
t.Log("Testing OIDC authenticator with --oidc-* flags")
|
||||||
|
runTests(t, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStructuredAuthenticationConfig(t *testing.T) {
|
||||||
|
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.StructuredAuthenticationConfiguration, true)()
|
||||||
|
|
||||||
|
t.Log("Testing OIDC authenticator with authentication config")
|
||||||
|
runTests(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runTests(t *testing.T, useAuthenticationConfig bool) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
name string
|
name string
|
||||||
configureInfrastructure func(t *testing.T) (
|
configureInfrastructure func(t *testing.T, useAuthenticationConfig bool) (
|
||||||
oidcServer *utilsoidc.TestServer,
|
oidcServer *utilsoidc.TestServer,
|
||||||
apiServer *kubeapiserverapptesting.TestServer,
|
apiServer *kubeapiserverapptesting.TestServer,
|
||||||
signingPrivateKey *rsa.PrivateKey,
|
signingPrivateKey *rsa.PrivateKey,
|
||||||
@ -207,7 +224,7 @@ func TestOIDC(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ID token signature can not be verified due to wrong JWKs",
|
name: "ID token signature can not be verified due to wrong JWKs",
|
||||||
configureInfrastructure: func(t *testing.T) (
|
configureInfrastructure: func(t *testing.T, useAuthenticationConfig bool) (
|
||||||
oidcServer *utilsoidc.TestServer,
|
oidcServer *utilsoidc.TestServer,
|
||||||
apiServer *kubeapiserverapptesting.TestServer,
|
apiServer *kubeapiserverapptesting.TestServer,
|
||||||
signingPrivateKey *rsa.PrivateKey,
|
signingPrivateKey *rsa.PrivateKey,
|
||||||
@ -220,7 +237,13 @@ func TestOIDC(t *testing.T) {
|
|||||||
require.NoError(t, wantErr)
|
require.NoError(t, wantErr)
|
||||||
|
|
||||||
oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath)
|
oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath)
|
||||||
apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath)
|
|
||||||
|
if useAuthenticationConfig {
|
||||||
|
authenticationConfig := generateAuthenticationConfig(t, oidcServer.URL(), defaultOIDCClientID, string(caCertContent), defaultOIDCUsernamePrefix)
|
||||||
|
apiServer = startTestAPIServerForOIDC(t, "", "", "", authenticationConfig)
|
||||||
|
} else {
|
||||||
|
apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath, "")
|
||||||
|
}
|
||||||
|
|
||||||
adminClient := kubernetes.NewForConfigOrDie(apiServer.ClientConfig)
|
adminClient := kubernetes.NewForConfigOrDie(apiServer.ClientConfig)
|
||||||
configureRBAC(t, adminClient, defaultRole, defaultRoleBinding)
|
configureRBAC(t, adminClient, defaultRole, defaultRoleBinding)
|
||||||
@ -252,7 +275,7 @@ func TestOIDC(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
oidcServer, apiServer, signingPrivateKey, caCert, certPath := tt.configureInfrastructure(t)
|
oidcServer, apiServer, signingPrivateKey, caCert, certPath := tt.configureInfrastructure(t, useAuthenticationConfig)
|
||||||
|
|
||||||
tt.configureOIDCServerBehaviour(t, oidcServer, signingPrivateKey)
|
tt.configureOIDCServerBehaviour(t, oidcServer, signingPrivateKey)
|
||||||
|
|
||||||
@ -304,7 +327,7 @@ func TestUpdatingRefreshTokenInCaseOfExpiredIDToken(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
oidcServer, apiServer, signingPrivateKey, caCert, certPath := configureTestInfrastructure(t)
|
oidcServer, apiServer, signingPrivateKey, caCert, certPath := configureTestInfrastructure(t, false)
|
||||||
|
|
||||||
tokenURL, err := oidcServer.TokenURL()
|
tokenURL, err := oidcServer.TokenURL()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -333,7 +356,7 @@ func TestUpdatingRefreshTokenInCaseOfExpiredIDToken(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureTestInfrastructure(t *testing.T) (
|
func configureTestInfrastructure(t *testing.T, useAuthenticationConfig bool) (
|
||||||
oidcServer *utilsoidc.TestServer,
|
oidcServer *utilsoidc.TestServer,
|
||||||
apiServer *kubeapiserverapptesting.TestServer,
|
apiServer *kubeapiserverapptesting.TestServer,
|
||||||
signingPrivateKey *rsa.PrivateKey,
|
signingPrivateKey *rsa.PrivateKey,
|
||||||
@ -348,7 +371,13 @@ func configureTestInfrastructure(t *testing.T) (
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath)
|
oidcServer = utilsoidc.BuildAndRunTestServer(t, caFilePath, caKeyFilePath)
|
||||||
apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath)
|
|
||||||
|
if useAuthenticationConfig {
|
||||||
|
authenticationConfig := generateAuthenticationConfig(t, oidcServer.URL(), defaultOIDCClientID, string(caCertContent), defaultOIDCUsernamePrefix)
|
||||||
|
apiServer = startTestAPIServerForOIDC(t, "", "", "", authenticationConfig)
|
||||||
|
} else {
|
||||||
|
apiServer = startTestAPIServerForOIDC(t, oidcServer.URL(), defaultOIDCClientID, caFilePath, "")
|
||||||
|
}
|
||||||
|
|
||||||
oidcServer.JwksHandler().EXPECT().KeySet().AnyTimes().DoAndReturn(utilsoidc.DefaultJwksHandlerBehaviour(t, &signingPrivateKey.PublicKey))
|
oidcServer.JwksHandler().EXPECT().KeySet().AnyTimes().DoAndReturn(utilsoidc.DefaultJwksHandlerBehaviour(t, &signingPrivateKey.PublicKey))
|
||||||
|
|
||||||
@ -399,19 +428,26 @@ func configureClientConfigForOIDC(t *testing.T, config *rest.Config, clientID, c
|
|||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func startTestAPIServerForOIDC(t *testing.T, oidcURL, oidcClientID, oidcCAFilePath string) *kubeapiserverapptesting.TestServer {
|
func startTestAPIServerForOIDC(t *testing.T, oidcURL, oidcClientID, oidcCAFilePath, authenticationConfigYAML string) *kubeapiserverapptesting.TestServer {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
server, err := kubeapiserverapptesting.StartTestServer(
|
var customFlags []string
|
||||||
t,
|
if authenticationConfigYAML != "" {
|
||||||
kubeapiserverapptesting.NewDefaultTestServerOptions(),
|
customFlags = []string{fmt.Sprintf("--authentication-config=%s", writeTempFile(t, authenticationConfigYAML))}
|
||||||
[]string{
|
} else {
|
||||||
|
customFlags = []string{
|
||||||
fmt.Sprintf("--oidc-issuer-url=%s", oidcURL),
|
fmt.Sprintf("--oidc-issuer-url=%s", oidcURL),
|
||||||
fmt.Sprintf("--oidc-client-id=%s", oidcClientID),
|
fmt.Sprintf("--oidc-client-id=%s", oidcClientID),
|
||||||
fmt.Sprintf("--oidc-ca-file=%s", oidcCAFilePath),
|
fmt.Sprintf("--oidc-ca-file=%s", oidcCAFilePath),
|
||||||
fmt.Sprintf("--oidc-username-prefix=%s", defaultOIDCUsernamePrefix),
|
fmt.Sprintf("--oidc-username-prefix=%s", defaultOIDCUsernamePrefix),
|
||||||
fmt.Sprintf("--authorization-mode=%s", modes.ModeRBAC),
|
}
|
||||||
},
|
}
|
||||||
|
customFlags = append(customFlags, "--authorization-mode=RBAC")
|
||||||
|
|
||||||
|
server, err := kubeapiserverapptesting.StartTestServer(
|
||||||
|
t,
|
||||||
|
kubeapiserverapptesting.NewDefaultTestServerOptions(),
|
||||||
|
customFlags,
|
||||||
framework.SharedEtcd(),
|
framework.SharedEtcd(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -494,3 +530,44 @@ func generateCert(t *testing.T) (cert, key []byte, certFilePath, keyFilePath str
|
|||||||
|
|
||||||
return cert, key, certFilePath, keyFilePath
|
return cert, key, certFilePath, keyFilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeTempFile(t *testing.T, content string) string {
|
||||||
|
t.Helper()
|
||||||
|
file, err := os.CreateTemp("", "oidc-test")
|
||||||
|
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 generateAuthenticationConfig(t *testing.T, issuerURL, clientID, caCert, usernamePrefix string) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
// Indent the certificate authority to match the format of the generated
|
||||||
|
// authentication config.
|
||||||
|
caCert = strings.ReplaceAll(caCert, "\n", "\n ")
|
||||||
|
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
apiVersion: apiserver.config.k8s.io/v1alpha1
|
||||||
|
kind: AuthenticationConfiguration
|
||||||
|
jwt:
|
||||||
|
- issuer:
|
||||||
|
url: %s
|
||||||
|
audiences:
|
||||||
|
- %s
|
||||||
|
certificateAuthority: |
|
||||||
|
%s
|
||||||
|
claimMappings:
|
||||||
|
username:
|
||||||
|
claim: sub
|
||||||
|
prefix: %s
|
||||||
|
`, issuerURL, clientID, string(caCert), usernamePrefix)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user