Merge pull request #50258 from liggitt/token-cache

Automatic merge from submit-queue (batch tested with PRs 49488, 50407, 46105, 50456, 50258)

Enable caching successful token authentication

Resolves #50472

To support revocation of service account tokens, an etcd lookup of the token and service account is done by the token authenticator. Controllers that make dozens or hundreds of API calls per second (like the endpoints controller) cause this lookup to be done very frequently on the same objects.

This PR:
* Implements a cached token authenticator that conforms to the authenticator.Token interface
* Implements a union token authenticator (same approach as the union request authenticator, conforming to the authenticator.Token interface)
* Cleans up the auth chain construction to group all token authenticators (means we only do bearer and websocket header parsing once)
* Adds a 10-second TTL cache to successful token authentication

```release-note
API server authentication now caches successful bearer token authentication results for a few seconds.
```
This commit is contained in:
Kubernetes Submit Queue
2017-08-11 14:14:06 -07:00
committed by GitHub
17 changed files with 853 additions and 16 deletions

View File

@@ -20,7 +20,9 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/authentication/request/union:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/request/websocket:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/request/x509:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/token/cache:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/token/tokenfile:go_default_library",
"//vendor/k8s.io/apiserver/pkg/authentication/token/union:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/keystone:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile:go_default_library",
"//vendor/k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth:go_default_library",

View File

@@ -30,7 +30,9 @@ import (
"k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/authentication/request/websocket"
"k8s.io/apiserver/pkg/authentication/request/x509"
tokencache "k8s.io/apiserver/pkg/authentication/token/cache"
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
tokenunion "k8s.io/apiserver/pkg/authentication/token/union"
"k8s.io/apiserver/plugin/pkg/authenticator/password/keystone"
"k8s.io/apiserver/plugin/pkg/authenticator/password/passwordfile"
"k8s.io/apiserver/plugin/pkg/authenticator/request/basicauth"
@@ -62,6 +64,9 @@ type AuthenticatorConfig struct {
WebhookTokenAuthnConfigFile string
WebhookTokenAuthnCacheTTL time.Duration
TokenSuccessCacheTTL time.Duration
TokenFailureCacheTTL time.Duration
RequestHeaderConfig *authenticatorfactory.RequestHeaderConfig
// TODO, this is the only non-serializable part of the entire config. Factor it out into a clientconfig
@@ -73,9 +78,9 @@ type AuthenticatorConfig struct {
// Kubernetes authentication mechanisms.
func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDefinitions, error) {
var authenticators []authenticator.Request
var tokenAuthenticators []authenticator.Token
securityDefinitions := spec.SecurityDefinitions{}
hasBasicAuth := false
hasTokenAuth := false
// front-proxy, BasicAuth methods, local first, then remote
// Add the front proxy authenticator if requested
@@ -125,22 +130,19 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
hasTokenAuth = true
tokenAuthenticators = append(tokenAuthenticators, tokenAuth)
}
if len(config.ServiceAccountKeyFiles) > 0 {
serviceAccountAuth, err := newServiceAccountAuthenticator(config.ServiceAccountKeyFiles, config.ServiceAccountLookup, config.ServiceAccountTokenGetter)
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, bearertoken.New(serviceAccountAuth), websocket.NewProtocolAuthenticator(serviceAccountAuth))
hasTokenAuth = true
tokenAuthenticators = append(tokenAuthenticators, serviceAccountAuth)
}
if config.BootstrapToken {
if config.BootstrapTokenAuthenticator != nil {
// TODO: This can sometimes be nil because of
authenticators = append(authenticators, bearertoken.New(config.BootstrapTokenAuthenticator), websocket.NewProtocolAuthenticator(config.BootstrapTokenAuthenticator))
hasTokenAuth = true
tokenAuthenticators = append(tokenAuthenticators, config.BootstrapTokenAuthenticator)
}
}
// NOTE(ericchiang): Keep the OpenID Connect after Service Accounts.
@@ -154,16 +156,14 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, bearertoken.New(oidcAuth), websocket.NewProtocolAuthenticator(oidcAuth))
hasTokenAuth = true
tokenAuthenticators = append(tokenAuthenticators, oidcAuth)
}
if len(config.WebhookTokenAuthnConfigFile) > 0 {
webhookTokenAuth, err := newWebhookTokenAuthenticator(config.WebhookTokenAuthnConfigFile, config.WebhookTokenAuthnCacheTTL)
if err != nil {
return nil, nil, err
}
authenticators = append(authenticators, bearertoken.New(webhookTokenAuth), websocket.NewProtocolAuthenticator(webhookTokenAuth))
hasTokenAuth = true
tokenAuthenticators = append(tokenAuthenticators, webhookTokenAuth)
}
if hasBasicAuth {
@@ -175,7 +175,14 @@ func (config AuthenticatorConfig) New() (authenticator.Request, *spec.SecurityDe
}
}
if hasTokenAuth {
if len(tokenAuthenticators) > 0 {
// Union the token authenticators
tokenAuth := tokenunion.New(tokenAuthenticators...)
// Optionally cache authentication results
if config.TokenSuccessCacheTTL > 0 || config.TokenFailureCacheTTL > 0 {
tokenAuth = tokencache.New(tokenAuth, config.TokenSuccessCacheTTL, config.TokenFailureCacheTTL)
}
authenticators = append(authenticators, bearertoken.New(tokenAuth), websocket.NewProtocolAuthenticator(tokenAuth))
securityDefinitions["BearerToken"] = &spec.SecurityScheme{
SecuritySchemeProps: spec.SecuritySchemeProps{
Type: "apiKey",

View File

@@ -41,6 +41,9 @@ type BuiltInAuthenticationOptions struct {
ServiceAccounts *ServiceAccountAuthenticationOptions
TokenFile *TokenFileAuthenticationOptions
WebHook *WebHookAuthenticationOptions
TokenSuccessCacheTTL time.Duration
TokenFailureCacheTTL time.Duration
}
type AnonymousAuthenticationOptions struct {
@@ -83,7 +86,10 @@ type WebHookAuthenticationOptions struct {
}
func NewBuiltInAuthenticationOptions() *BuiltInAuthenticationOptions {
return &BuiltInAuthenticationOptions{}
return &BuiltInAuthenticationOptions{
TokenSuccessCacheTTL: 10 * time.Second,
TokenFailureCacheTTL: 0 * time.Second,
}
}
func (s *BuiltInAuthenticationOptions) WithAll() *BuiltInAuthenticationOptions {
@@ -250,7 +256,10 @@ func (s *BuiltInAuthenticationOptions) AddFlags(fs *pflag.FlagSet) {
}
func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() authenticator.AuthenticatorConfig {
ret := authenticator.AuthenticatorConfig{}
ret := authenticator.AuthenticatorConfig{
TokenSuccessCacheTTL: s.TokenSuccessCacheTTL,
TokenFailureCacheTTL: s.TokenFailureCacheTTL,
}
if s.Anonymous != nil {
ret.Anonymous = s.Anonymous.Allow
@@ -297,6 +306,15 @@ func (s *BuiltInAuthenticationOptions) ToAuthenticationConfig() authenticator.Au
if s.WebHook != nil {
ret.WebhookTokenAuthnConfigFile = s.WebHook.ConfigFile
ret.WebhookTokenAuthnCacheTTL = s.WebHook.CacheTTL
if len(s.WebHook.ConfigFile) > 0 && s.WebHook.CacheTTL > 0 {
if s.TokenSuccessCacheTTL > 0 && s.WebHook.CacheTTL < s.TokenSuccessCacheTTL {
glog.Warningf("the webhook cache ttl of %s is shorter than the overall cache ttl of %s for successful token authentication attempts.", s.WebHook.CacheTTL, s.TokenSuccessCacheTTL)
}
if s.TokenFailureCacheTTL > 0 && s.WebHook.CacheTTL < s.TokenFailureCacheTTL {
glog.Warningf("the webhook cache ttl of %s is shorter than the overall cache ttl of %s for failed token authentication attempts.", s.WebHook.CacheTTL, s.TokenFailureCacheTTL)
}
}
}
return ret