Skip to content

Commit 5e6a493

Browse files
KEP-4633: Allow health-only anonymous auth mode.
Signed-off-by: Vinayak Goyal <[email protected]>
1 parent 85ede67 commit 5e6a493

File tree

25 files changed

+830
-65
lines changed

25 files changed

+830
-65
lines changed

cmd/kube-apiserver/app/options/options_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,7 @@ func TestAddFlags(t *testing.T) {
243243
EnableContentionProfiling: true,
244244
},
245245
Authentication: &kubeoptions.BuiltInAuthenticationOptions{
246-
Anonymous: &kubeoptions.AnonymousAuthenticationOptions{
247-
Allow: false,
248-
},
246+
Anonymous: s.Authentication.Anonymous,
249247
ClientCert: &apiserveroptions.ClientCertAuthenticationOptions{
250248
ClientCA: "/client-ca",
251249
},
@@ -335,6 +333,6 @@ func TestAddFlags(t *testing.T) {
335333
s.Authorization.AreLegacyFlagsSet = nil
336334

337335
if !reflect.DeepEqual(expected, s) {
338-
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{})))
336+
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{}, kubeoptions.AnonymousAuthenticationOptions{})))
339337
}
340338
}

cmd/kube-controller-manager/app/options/options_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
eventv1 "k8s.io/api/events/v1"
3131
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3232
utilerrors "k8s.io/apimachinery/pkg/util/errors"
33+
"k8s.io/apiserver/pkg/apis/apiserver"
3334
apiserveroptions "k8s.io/apiserver/pkg/server/options"
3435
cpconfig "k8s.io/cloud-provider/config"
3536
serviceconfig "k8s.io/cloud-provider/controllers/service/config"
@@ -430,6 +431,7 @@ func TestAddFlags(t *testing.T) {
430431
ExtraHeaderPrefixes: []string{"x-remote-extra-"},
431432
},
432433
RemoteKubeConfigFileOptional: true,
434+
Anonymous: &apiserver.AnonymousAuthConfig{Enabled: true},
433435
},
434436
Authorization: &apiserveroptions.DelegatingAuthorizationOptions{
435437
AllowCacheTTL: 10 * time.Second,

cmd/kubelet/app/auth.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"reflect"
2424

2525
"k8s.io/apimachinery/pkg/types"
26+
"k8s.io/apiserver/pkg/apis/apiserver"
2627
"k8s.io/apiserver/pkg/authentication/authenticator"
2728
"k8s.io/apiserver/pkg/authentication/authenticatorfactory"
2829
"k8s.io/apiserver/pkg/authorization/authorizer"
@@ -77,7 +78,7 @@ func BuildAuthn(client authenticationclient.AuthenticationV1Interface, authn kub
7778
}
7879

7980
authenticatorConfig := authenticatorfactory.DelegatingAuthenticatorConfig{
80-
Anonymous: authn.Anonymous.Enabled,
81+
Anonymous: &apiserver.AnonymousAuthConfig{Enabled: authn.Anonymous.Enabled},
8182
CacheTTL: authn.Webhook.CacheTTL.Duration,
8283
ClientCertificateCAContentProvider: dynamicCAContentFromFile,
8384
}

pkg/controlplane/apiserver/options/options_test.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ import (
3535
cliflag "k8s.io/component-base/cli/flag"
3636
"k8s.io/component-base/logs"
3737
"k8s.io/component-base/metrics"
38-
netutils "k8s.io/utils/net"
39-
4038
kubeoptions "k8s.io/kubernetes/pkg/kubeapiserver/options"
39+
netutils "k8s.io/utils/net"
4140
)
4241

4342
func TestAddFlags(t *testing.T) {
@@ -229,9 +228,7 @@ func TestAddFlags(t *testing.T) {
229228
EnableContentionProfiling: true,
230229
},
231230
Authentication: &kubeoptions.BuiltInAuthenticationOptions{
232-
Anonymous: &kubeoptions.AnonymousAuthenticationOptions{
233-
Allow: false,
234-
},
231+
Anonymous: s.Authentication.Anonymous,
235232
ClientCert: &apiserveroptions.ClientCertAuthenticationOptions{
236233
ClientCA: "/client-ca",
237234
},
@@ -290,6 +287,6 @@ func TestAddFlags(t *testing.T) {
290287
s.Authorization.AreLegacyFlagsSet = nil
291288

292289
if !reflect.DeepEqual(expected, s) {
293-
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{})))
290+
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{}, kubeoptions.AnonymousAuthenticationOptions{})))
294291
}
295292
}

pkg/features/kube_features.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
12031203

12041204
genericfeatures.AggregatedDiscoveryEndpoint: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.33
12051205

1206+
genericfeatures.AnonymousAuthConfigurableEndpoints: {Default: false, PreRelease: featuregate.Alpha},
1207+
12061208
genericfeatures.APIListChunking: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.32
12071209

12081210
genericfeatures.APIPriorityAndFairness: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.31

pkg/kubeapiserver/authenticator/config.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,10 @@ import (
5555

5656
// Config contains the data on how to authenticate a request to the Kube API Server
5757
type Config struct {
58-
Anonymous bool
58+
// Anonymous holds the effective anonymous config, specified either via config file
59+
// (hoisted out of AuthenticationConfig) or via flags (constructed from flag-specified values).
60+
Anonymous apiserver.AnonymousAuthConfig
61+
5962
BootstrapToken bool
6063

6164
TokenAuthFile string
@@ -212,8 +215,8 @@ func (config Config) New(serverLifecycle context.Context) (authenticator.Request
212215
}
213216

214217
if len(authenticators) == 0 {
215-
if config.Anonymous {
216-
return anonymous.NewAuthenticator(), nil, &securityDefinitionsV2, securitySchemesV3, nil
218+
if config.Anonymous.Enabled {
219+
return anonymous.NewAuthenticator(config.Anonymous.Conditions), nil, &securityDefinitionsV2, securitySchemesV3, nil
217220
}
218221
return nil, nil, &securityDefinitionsV2, securitySchemesV3, nil
219222
}
@@ -222,10 +225,10 @@ func (config Config) New(serverLifecycle context.Context) (authenticator.Request
222225

223226
authenticator = group.NewAuthenticatedGroupAdder(authenticator)
224227

225-
if config.Anonymous {
228+
if config.Anonymous.Enabled {
226229
// If the authenticator chain returns an error, return an error (don't consider a bad bearer token
227230
// or invalid username/password combination anonymous).
228-
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator())
231+
authenticator = union.NewFailOnError(authenticator, anonymous.NewAuthenticator(config.Anonymous.Conditions))
229232
}
230233

231234
return authenticator, updateAuthenticationConfig, &securityDefinitionsV2, securitySchemesV3, nil

pkg/kubeapiserver/options/authentication.go

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"fmt"
2323
"net/url"
2424
"os"
25+
"reflect"
2526
"strings"
2627
"sync"
2728
"time"
@@ -32,6 +33,7 @@ import (
3233
"k8s.io/apimachinery/pkg/runtime"
3334
"k8s.io/apimachinery/pkg/runtime/serializer"
3435
"k8s.io/apimachinery/pkg/util/sets"
36+
"k8s.io/apimachinery/pkg/util/validation/field"
3537
"k8s.io/apimachinery/pkg/util/wait"
3638
"k8s.io/apiserver/pkg/apis/apiserver"
3739
"k8s.io/apiserver/pkg/apis/apiserver/install"
@@ -95,7 +97,8 @@ type BuiltInAuthenticationOptions struct {
9597

9698
// AnonymousAuthenticationOptions contains anonymous authentication options for API Server
9799
type AnonymousAuthenticationOptions struct {
98-
Allow bool
100+
Allow bool
101+
areFlagsSet func() bool
99102
}
100103

101104
// BootstrapTokenAuthenticationOptions contains bootstrap token authentication options for API Server
@@ -169,7 +172,10 @@ func (o *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions {
169172

170173
// WithAnonymous set default value for anonymous authentication
171174
func (o *BuiltInAuthenticationOptions) WithAnonymous() *BuiltInAuthenticationOptions {
172-
o.Anonymous = &AnonymousAuthenticationOptions{Allow: true}
175+
o.Anonymous = &AnonymousAuthenticationOptions{
176+
Allow: true,
177+
areFlagsSet: func() bool { return false },
178+
}
173179
return o
174180
}
175181

@@ -294,6 +300,14 @@ func (o *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
294300
return
295301
}
296302

303+
fs.StringVar(&o.AuthenticationConfigFile, "authentication-config", o.AuthenticationConfigFile, ""+
304+
"File with Authentication Configuration to configure the JWT Token authenticator or the anonymous authenticator. "+
305+
"Note: This feature is in Alpha since v1.29."+
306+
"--feature-gate=StructuredAuthenticationConfiguration=true needs to be set for enabling this feature."+
307+
"This feature is mutually exclusive with the oidc-* flags."+
308+
"To configure anonymous authenticator you need to enable --feature-gate=AnonymousAuthConfigurableEndpoints."+
309+
"When you configure anonymous authenticator in the authentication config you cannot use the --anonymous-auth flag.")
310+
297311
fs.StringSliceVar(&o.APIAudiences, "api-audiences", o.APIAudiences, ""+
298312
"Identifiers of the API. The service account token authenticator will validate that "+
299313
"tokens used against the API are bound to at least one of these audiences. If the "+
@@ -305,6 +319,10 @@ func (o *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
305319
"Enables anonymous requests to the secure port of the API server. "+
306320
"Requests that are not rejected by another authentication method are treated as anonymous requests. "+
307321
"Anonymous requests have a username of system:anonymous, and a group name of system:unauthenticated.")
322+
323+
o.Anonymous.areFlagsSet = func() bool {
324+
return fs.Changed("anonymous-auth")
325+
}
308326
}
309327

310328
if o.BootstrapToken != nil {
@@ -358,12 +376,6 @@ func (o *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
358376
"If set, the claim is verified to be present in the ID Token with a matching value. "+
359377
"Repeat this flag to specify multiple claims.")
360378

361-
fs.StringVar(&o.AuthenticationConfigFile, "authentication-config", o.AuthenticationConfigFile, ""+
362-
"File with Authentication Configuration to configure the JWT Token authenticator. "+
363-
"Note: This feature is in Alpha since v1.29."+
364-
"--feature-gate=StructuredAuthenticationConfiguration=true needs to be set for enabling this feature."+
365-
"This feature is mutually exclusive with the oidc-* flags.")
366-
367379
o.OIDC.areFlagsConfigured = func() bool {
368380
return fs.Changed(oidcIssuerURLFlag) ||
369381
fs.Changed(oidcClientIDFlag) ||
@@ -452,10 +464,6 @@ func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat
452464
TokenFailureCacheTTL: o.TokenFailureCacheTTL,
453465
}
454466

455-
if o.Anonymous != nil {
456-
ret.Anonymous = o.Anonymous.Allow
457-
}
458-
459467
if o.BootstrapToken != nil {
460468
ret.BootstrapToken = o.BootstrapToken.Enable
461469
}
@@ -469,12 +477,18 @@ func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat
469477
}
470478

471479
// When the StructuredAuthenticationConfiguration feature is enabled and the authentication config file is provided,
472-
// load the authentication config from the file.
480+
// load the authentication config from the file, otherwise set up an empty configuration.
473481
if len(o.AuthenticationConfigFile) > 0 {
474482
var err error
475483
if ret.AuthenticationConfig, ret.AuthenticationConfigData, err = loadAuthenticationConfig(o.AuthenticationConfigFile); err != nil {
476484
return kubeauthenticator.Config{}, err
477485
}
486+
} else {
487+
ret.AuthenticationConfig = &apiserver.AuthenticationConfiguration{}
488+
}
489+
490+
// Set up JWT authenticators from config file or from flags
491+
if len(o.AuthenticationConfigFile) > 0 {
478492
// all known signing algs are allowed when using authentication config
479493
// TODO: what we really want to express is 'any alg is fine as long it matches a public key'
480494
ret.OIDCSigningAlgs = oidc.AllValidSigningAlgorithms()
@@ -532,20 +546,30 @@ func (o *BuiltInAuthenticationOptions) ToAuthenticationConfig() (kubeauthenticat
532546
jwtAuthenticator.ClaimValidationRules = claimValidationRules
533547
}
534548

535-
authConfig := &apiserver.AuthenticationConfiguration{
536-
JWT: []apiserver.JWTAuthenticator{jwtAuthenticator},
537-
}
549+
ret.AuthenticationConfig.JWT = []apiserver.JWTAuthenticator{jwtAuthenticator}
538550

539-
ret.AuthenticationConfig = authConfig
540551
ret.OIDCSigningAlgs = o.OIDC.SigningAlgs
541552
}
542553

543-
if ret.AuthenticationConfig != nil {
544-
if err := apiservervalidation.ValidateAuthenticationConfiguration(ret.AuthenticationConfig, ret.ServiceAccountIssuers).ToAggregate(); err != nil {
545-
return kubeauthenticator.Config{}, err
554+
// Set up anonymous authenticator from config file or flags
555+
if o.Anonymous != nil {
556+
switch {
557+
case ret.AuthenticationConfig.Anonymous != nil && o.Anonymous.areFlagsSet():
558+
// Flags and config file are mutually exclusive
559+
return kubeauthenticator.Config{}, field.Forbidden(field.NewPath("anonymous"), "--anonynous-auth flag cannot be set when anonymous field is configured in authentication configuration file")
560+
case ret.AuthenticationConfig.Anonymous != nil:
561+
// Use the config-file-specified values
562+
ret.Anonymous = *ret.AuthenticationConfig.Anonymous
563+
default:
564+
// Use the flag-specified values
565+
ret.Anonymous = apiserver.AnonymousAuthConfig{Enabled: o.Anonymous.Allow}
546566
}
547567
}
548568

569+
if err := apiservervalidation.ValidateAuthenticationConfiguration(ret.AuthenticationConfig, ret.ServiceAccountIssuers).ToAggregate(); err != nil {
570+
return kubeauthenticator.Config{}, err
571+
}
572+
549573
if o.RequestHeader != nil {
550574
var err error
551575
ret.RequestHeaderConfig, err = o.RequestHeader.ToAuthenticationRequestHeaderConfig()
@@ -667,6 +691,10 @@ func (o *BuiltInAuthenticationOptions) ApplyTo(
667691
authenticationconfigmetrics.RegisterMetrics()
668692
trackedAuthenticationConfigData := authenticatorConfig.AuthenticationConfigData
669693
var mu sync.Mutex
694+
695+
// ensure anonymous config doesn't change on reload
696+
originalFileAnonymousConfig := authenticatorConfig.AuthenticationConfig.DeepCopy().Anonymous
697+
670698
go filesystem.WatchUntil(
671699
ctx,
672700
time.Minute,
@@ -700,7 +728,11 @@ func (o *BuiltInAuthenticationOptions) ApplyTo(
700728
return
701729
}
702730

703-
if err := apiservervalidation.ValidateAuthenticationConfiguration(authConfig, authenticatorConfig.ServiceAccountIssuers).ToAggregate(); err != nil {
731+
validationErrs := apiservervalidation.ValidateAuthenticationConfiguration(authConfig, authenticatorConfig.ServiceAccountIssuers)
732+
if !reflect.DeepEqual(originalFileAnonymousConfig, authConfig.Anonymous) {
733+
validationErrs = append(validationErrs, field.Forbidden(field.NewPath("anonymous"), "changed from initial configuration file"))
734+
}
735+
if err := validationErrs.ToAggregate(); err != nil {
704736
klog.ErrorS(err, "failed to validate authentication config")
705737
authenticationconfigmetrics.RecordAuthenticationConfigAutomaticReloadFailure(apiServerID)
706738
// this config is not semantically valid and never will be, update the tracker so we stop retrying

0 commit comments

Comments
 (0)