diff --git a/staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory/delegating.go b/staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory/delegating.go index 61114c1c866..d8e18345545 100644 --- a/staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory/delegating.go +++ b/staging/src/k8s.io/apiserver/pkg/authentication/authenticatorfactory/delegating.go @@ -41,6 +41,7 @@ import ( type DelegatingAuthenticatorConfig struct { Anonymous bool + // TokenAccessReviewClient is a client to do token review. It can be nil. Then every token is ignored. TokenAccessReviewClient authenticationclient.TokenReviewInterface // CacheTTL is the length of time that a token authentication answer will be cached. diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go b/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go index 7c021072fdb..d40b78bc904 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/authentication.go @@ -106,6 +106,9 @@ type DelegatingAuthenticationOptions struct { // RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the // TokenAccessReview.authentication.k8s.io endpoint for checking tokens. RemoteKubeConfigFile string + // RemoteKubeConfigFileOptional is specifying whether not specifying the kubeconfig or + // a missing in-cluster config will be fatal. + RemoteKubeConfigFileOptional bool // CacheTTL is the length of time that a token authentication answer will be cached. CacheTTL time.Duration @@ -139,9 +142,13 @@ func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) { return } + var optionalKubeConfigSentence string + if s.RemoteKubeConfigFileOptional { + optionalKubeConfigSentence = " This is optional. If empty, all token requests are considered to be anonymous and no client CA is looked up in the cluster." + } fs.StringVar(&s.RemoteKubeConfigFile, "authentication-kubeconfig", s.RemoteKubeConfigFile, ""+ "kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+ - "tokenaccessreviews.authentication.k8s.io.") + "tokenaccessreviews.authentication.k8s.io."+optionalKubeConfigSentence) fs.DurationVar(&s.CacheTTL, "authentication-token-webhook-cache-ttl", s.CacheTTL, "The duration to cache responses from the webhook token authenticator.") @@ -152,7 +159,6 @@ func (s *DelegatingAuthenticationOptions) AddFlags(fs *pflag.FlagSet) { fs.BoolVar(&s.SkipInClusterLookup, "authentication-skip-lookup", s.SkipInClusterLookup, ""+ "If false, the authentication-kubeconfig will be used to lookup missing authentication "+ "configuration from the cluster.") - } func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo, servingInfo *server.SecureServingInfo, openAPIConfig *openapicommon.Config) error { @@ -161,15 +167,19 @@ func (s *DelegatingAuthenticationOptions) ApplyTo(c *server.AuthenticationInfo, return nil } + cfg := authenticatorfactory.DelegatingAuthenticatorConfig{ + Anonymous: true, + CacheTTL: s.CacheTTL, + } + client, err := s.getClient() if err != nil { return fmt.Errorf("failed to get delegated authentication kubeconfig: %v", err) } - cfg := authenticatorfactory.DelegatingAuthenticatorConfig{ - Anonymous: true, - CacheTTL: s.CacheTTL, - TokenAccessReviewClient: client.AuthenticationV1beta1().TokenReviews(), + // configure token review + if client != nil { + cfg.TokenAccessReviewClient = client.AuthenticationV1beta1().TokenReviews() } // look into configmaps/external-apiserver-authentication for missing authn info @@ -217,6 +227,15 @@ func (s *DelegatingAuthenticationOptions) lookupMissingConfigInCluster(client ku if len(s.ClientCert.ClientCA) > 0 && len(s.RequestHeader.ClientCAFile) > 0 { return nil } + if client == nil { + if len(s.ClientCert.ClientCA) == 0 { + glog.Warningf("No authentication-kubeconfig provided in order to lookup client-ca-file in configmap/%s in %s, so client certificate authentication to extension api-server won't work.", authenticationConfigMapName, authenticationConfigMapNamespace) + } + if len(s.RequestHeader.ClientCAFile) == 0 { + glog.Warningf("No authentication-kubeconfig provided in order to lookup requestheader-client-ca-file in configmap/%s in %s, so request-header client certificate authentication to extension api-server won't work.", authenticationConfigMapName, authenticationConfigMapNamespace) + } + return nil + } authConfigMap, err := client.CoreV1().ConfigMaps(authenticationConfigMapNamespace).Get(authenticationConfigMapName, metav1.GetOptions{}) if err != nil { @@ -321,6 +340,8 @@ func deserializeStrings(in string) ([]string, error) { return ret, nil } +// getClient returns a Kubernetes clientset. If s.RemoteKubeConfigFileOptional is true, nil will be returned +// if no kubeconfig is specified by the user and the in-cluster config is not found. func (s *DelegatingAuthenticationOptions) getClient() (kubernetes.Interface, error) { var clientConfig *rest.Config var err error @@ -329,11 +350,13 @@ func (s *DelegatingAuthenticationOptions) getClient() (kubernetes.Interface, err loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) clientConfig, err = loader.ClientConfig() - } else { // without the remote kubeconfig file, try to use the in-cluster config. Most addon API servers will // use this path clientConfig, err = rest.InClusterConfig() + if err == rest.ErrNotInCluster && s.RemoteKubeConfigFileOptional { + return nil, nil + } } if err != nil { return nil, fmt.Errorf("failed to get delegated authentication kubeconfig: %v", err) diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/authorization.go b/staging/src/k8s.io/apiserver/pkg/server/options/authorization.go index fab1335c244..a014d94a2c7 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/authorization.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/authorization.go @@ -20,6 +20,7 @@ import ( "fmt" "time" + "github.com/golang/glog" "github.com/spf13/pflag" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -41,6 +42,9 @@ type DelegatingAuthorizationOptions struct { // RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the // SubjectAccessReview.authorization.k8s.io endpoint for checking tokens. RemoteKubeConfigFile string + // RemoteKubeConfigFileOptional is specifying whether not specifying the kubeconfig or + // a missing in-cluster config will be fatal. + RemoteKubeConfigFileOptional bool // AllowCacheTTL is the length of time that a successful authorization response will be cached AllowCacheTTL time.Duration @@ -72,9 +76,13 @@ func (s *DelegatingAuthorizationOptions) AddFlags(fs *pflag.FlagSet) { return } + var optionalKubeConfigSentence string + if s.RemoteKubeConfigFileOptional { + optionalKubeConfigSentence = " This is optional. If empty, all requests not skipped by authorization are forbidden." + } fs.StringVar(&s.RemoteKubeConfigFile, "authorization-kubeconfig", s.RemoteKubeConfigFile, "kubeconfig file pointing at the 'core' kubernetes server with enough rights to create "+ - " subjectaccessreviews.authorization.k8s.io.") + "subjectaccessreviews.authorization.k8s.io."+optionalKubeConfigSentence) fs.DurationVar(&s.AllowCacheTTL, "authorization-webhook-cache-authorized-ttl", s.AllowCacheTTL, @@ -115,16 +123,20 @@ func (s *DelegatingAuthorizationOptions) toAuthorizer(client kubernetes.Interfac authorizers = append(authorizers, a) } - cfg := authorizerfactory.DelegatingAuthorizerConfig{ - SubjectAccessReviewClient: client.AuthorizationV1beta1().SubjectAccessReviews(), - AllowCacheTTL: s.AllowCacheTTL, - DenyCacheTTL: s.DenyCacheTTL, + if client == nil { + glog.Warningf("No authorization-kubeconfig provided, so SubjectAccessReview of authorization tokens won't work.") + } else { + cfg := authorizerfactory.DelegatingAuthorizerConfig{ + SubjectAccessReviewClient: client.AuthorizationV1beta1().SubjectAccessReviews(), + AllowCacheTTL: s.AllowCacheTTL, + DenyCacheTTL: s.DenyCacheTTL, + } + delegatedAuthorizer, err := cfg.New() + if err != nil { + return nil, err + } + authorizers = append(authorizers, delegatedAuthorizer) } - a, err := cfg.New() - if err != nil { - return nil, err - } - authorizers = append(authorizers, a) return union.New(authorizers...), nil } @@ -141,6 +153,9 @@ func (s *DelegatingAuthorizationOptions) getClient() (kubernetes.Interface, erro // without the remote kubeconfig file, try to use the in-cluster config. Most addon API servers will // use this path clientConfig, err = rest.InClusterConfig() + if err == rest.ErrNotInCluster && s.RemoteKubeConfigFileOptional { + return nil, nil + } } if err != nil { return nil, fmt.Errorf("failed to get delegated authorization kubeconfig: %v", err) diff --git a/staging/src/k8s.io/client-go/rest/config.go b/staging/src/k8s.io/client-go/rest/config.go index 39fde2de51d..6700f5b4c8d 100644 --- a/staging/src/k8s.io/client-go/rest/config.go +++ b/staging/src/k8s.io/client-go/rest/config.go @@ -18,6 +18,7 @@ package rest import ( "context" + "errors" "fmt" "io/ioutil" "net" @@ -44,6 +45,8 @@ const ( DefaultBurst int = 10 ) +var ErrNotInCluster = errors.New("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined") + // Config holds the common attributes that can be passed to a Kubernetes client on // initialization. type Config struct { @@ -308,12 +311,12 @@ func DefaultKubernetesUserAgent() string { // InClusterConfig returns a config object which uses the service account // kubernetes gives to pods. It's intended for clients that expect to be -// running inside a pod running on kubernetes. It will return an error if -// called from a process not running in a kubernetes environment. +// running inside a pod running on kubernetes. It will return ErrNotInCluster +// if called from a process not running in a kubernetes environment. func InClusterConfig() (*Config, error) { host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT") if len(host) == 0 || len(port) == 0 { - return nil, fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined") + return nil, ErrNotInCluster } token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")