diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index b0ab7d718b9..33d1fdc6f93 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -232,6 +232,7 @@ func Run(s *options.APIServer) error { KeystoneURL: s.KeystoneURL, WebhookTokenAuthnConfigFile: s.WebhookTokenAuthnConfigFile, WebhookTokenAuthnCacheTTL: s.WebhookTokenAuthnCacheTTL, + RequestHeaderConfig: s.AuthenticationRequestHeaderConfig(), }) if err != nil { diff --git a/federation/cmd/federation-apiserver/app/server.go b/federation/cmd/federation-apiserver/app/server.go index 9c287ff4051..1b8806b9dd3 100644 --- a/federation/cmd/federation-apiserver/app/server.go +++ b/federation/cmd/federation-apiserver/app/server.go @@ -117,17 +117,18 @@ func Run(s *options.ServerRunOptions) error { } apiAuthenticator, securityDefinitions, err := authenticator.New(authenticator.AuthenticatorConfig{ - Anonymous: s.AnonymousAuth, - AnyToken: s.EnableAnyToken, - BasicAuthFile: s.BasicAuthFile, - ClientCAFile: s.ClientCAFile, - TokenAuthFile: s.TokenAuthFile, - OIDCIssuerURL: s.OIDCIssuerURL, - OIDCClientID: s.OIDCClientID, - OIDCCAFile: s.OIDCCAFile, - OIDCUsernameClaim: s.OIDCUsernameClaim, - OIDCGroupsClaim: s.OIDCGroupsClaim, - KeystoneURL: s.KeystoneURL, + Anonymous: s.AnonymousAuth, + AnyToken: s.EnableAnyToken, + BasicAuthFile: s.BasicAuthFile, + ClientCAFile: s.ClientCAFile, + TokenAuthFile: s.TokenAuthFile, + OIDCIssuerURL: s.OIDCIssuerURL, + OIDCClientID: s.OIDCClientID, + OIDCCAFile: s.OIDCCAFile, + OIDCUsernameClaim: s.OIDCUsernameClaim, + OIDCGroupsClaim: s.OIDCGroupsClaim, + KeystoneURL: s.KeystoneURL, + RequestHeaderConfig: s.AuthenticationRequestHeaderConfig(), }) if err != nil { glog.Fatalf("Invalid Authentication Config: %v", err) diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index f1a63c9bb10..e73dec1d75b 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -464,6 +464,9 @@ replication-controller-lookup-cache-size repo-root report-dir report-prefix +requestheader-allowed-names +requestheader-client-ca-file +requestheader-username-headers require-kubeconfig required-contexts resolv-conf diff --git a/pkg/apiserver/authenticator/BUILD b/pkg/apiserver/authenticator/BUILD index c8b90f65c33..79517cdd26f 100644 --- a/pkg/apiserver/authenticator/BUILD +++ b/pkg/apiserver/authenticator/BUILD @@ -25,6 +25,7 @@ go_library( "//plugin/pkg/auth/authenticator/password/passwordfile:go_default_library", "//plugin/pkg/auth/authenticator/request/anonymous:go_default_library", "//plugin/pkg/auth/authenticator/request/basicauth:go_default_library", + "//plugin/pkg/auth/authenticator/request/headerrequest:go_default_library", "//plugin/pkg/auth/authenticator/request/union:go_default_library", "//plugin/pkg/auth/authenticator/request/x509:go_default_library", "//plugin/pkg/auth/authenticator/token/anytoken:go_default_library", diff --git a/pkg/apiserver/authenticator/authn.go b/pkg/apiserver/authenticator/authn.go index 2dfe01acd71..6d5720077e3 100644 --- a/pkg/apiserver/authenticator/authn.go +++ b/pkg/apiserver/authenticator/authn.go @@ -31,6 +31,7 @@ import ( "k8s.io/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/anonymous" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth" + "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/headerrequest" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/union" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/x509" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/anytoken" @@ -39,6 +40,15 @@ import ( "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook" ) +type RequestHeaderConfig struct { + // UsernameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins. + UsernameHeaders []string + // ClientCA points to CA bundle file which is used verify the identity of the front proxy + ClientCA string + // AllowedClientNames is a list of common names that may be presented by the authenticating front proxy. Empty means: accept any. + AllowedClientNames []string +} + type AuthenticatorConfig struct { Anonymous bool AnyToken bool @@ -56,6 +66,8 @@ type AuthenticatorConfig struct { KeystoneURL string WebhookTokenAuthnConfigFile string WebhookTokenAuthnCacheTTL time.Duration + + RequestHeaderConfig *RequestHeaderConfig } // New returns an authenticator.Request or an error that supports the standard @@ -66,7 +78,20 @@ func New(config AuthenticatorConfig) (authenticator.Request, *spec.SecurityDefin hasBasicAuth := false hasTokenAuth := false - // BasicAuth methods, local first, then remote + // front-proxy, BasicAuth methods, local first, then remote + // Add the front proxy authenticator if requested + if config.RequestHeaderConfig != nil { + requestHeaderAuthenticator, err := headerrequest.NewSecure( + config.RequestHeaderConfig.ClientCA, + config.RequestHeaderConfig.AllowedClientNames, + config.RequestHeaderConfig.UsernameHeaders, + ) + if err != nil { + return nil, nil, err + } + authenticators = append(authenticators, requestHeaderAuthenticator) + } + if len(config.BasicAuthFile) > 0 { basicAuth, err := newAuthenticatorFromBasicAuthFile(config.BasicAuthFile) if err != nil { diff --git a/pkg/genericapiserver/options/BUILD b/pkg/genericapiserver/options/BUILD index eebe8f8b90a..ee766a9e78b 100644 --- a/pkg/genericapiserver/options/BUILD +++ b/pkg/genericapiserver/options/BUILD @@ -13,6 +13,7 @@ load( go_library( name = "go_default_library", srcs = [ + "authenticator.go", "doc.go", "etcd_options.go", "server_run_options.go", @@ -23,6 +24,7 @@ go_library( "//pkg/api:go_default_library", "//pkg/api/unversioned:go_default_library", "//pkg/apimachinery/registered:go_default_library", + "//pkg/apiserver/authenticator:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/restclient:go_default_library", "//pkg/storage/storagebackend:go_default_library", diff --git a/pkg/genericapiserver/options/authenticator.go b/pkg/genericapiserver/options/authenticator.go new file mode 100644 index 00000000000..58f943050e4 --- /dev/null +++ b/pkg/genericapiserver/options/authenticator.go @@ -0,0 +1,35 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package options + +import ( + "k8s.io/kubernetes/pkg/apiserver/authenticator" +) + +// AuthenticationRequestHeaderConfig returns an authenticator config object for these options +// if necessary. nil otherwise. +func (s *ServerRunOptions) AuthenticationRequestHeaderConfig() *authenticator.RequestHeaderConfig { + if len(s.RequestHeaderUsernameHeaders) == 0 { + return nil + } + + return &authenticator.RequestHeaderConfig{ + UsernameHeaders: s.RequestHeaderUsernameHeaders, + ClientCA: s.RequestHeaderClientCAFile, + AllowedClientNames: s.RequestHeaderAllowedNames, + } +} diff --git a/pkg/genericapiserver/options/server_run_options.go b/pkg/genericapiserver/options/server_run_options.go index d36516a3baf..69de738403d 100644 --- a/pkg/genericapiserver/options/server_run_options.go +++ b/pkg/genericapiserver/options/server_run_options.go @@ -67,46 +67,49 @@ type ServerRunOptions struct { AuthorizationWebhookCacheUnauthorizedTTL time.Duration AuthorizationRBACSuperUser string - AnonymousAuth bool - BasicAuthFile string - BindAddress net.IP - CertDirectory string - ClientCAFile string - CloudConfigFile string - CloudProvider string - CorsAllowedOriginList []string - DefaultStorageMediaType string - DeleteCollectionWorkers int - AuditLogPath string - AuditLogMaxAge int - AuditLogMaxBackups int - AuditLogMaxSize int - EnableGarbageCollection bool - EnableProfiling bool - EnableSwaggerUI bool - EnableWatchCache bool - EtcdServersOverrides []string - StorageConfig storagebackend.Config - ExternalHost string - InsecureBindAddress net.IP - InsecurePort int - KeystoneURL string - KubernetesServiceNodePort int - LongRunningRequestRE string - MasterCount int - MasterServiceNamespace string - MaxRequestsInFlight int - MinRequestTimeout int - OIDCCAFile string - OIDCClientID string - OIDCIssuerURL string - OIDCUsernameClaim string - OIDCGroupsClaim string - RuntimeConfig config.ConfigurationMap - SecurePort int - ServiceClusterIPRange net.IPNet // TODO: make this a list - ServiceNodePortRange utilnet.PortRange - StorageVersions string + AnonymousAuth bool + BasicAuthFile string + BindAddress net.IP + CertDirectory string + ClientCAFile string + CloudConfigFile string + CloudProvider string + CorsAllowedOriginList []string + DefaultStorageMediaType string + DeleteCollectionWorkers int + AuditLogPath string + AuditLogMaxAge int + AuditLogMaxBackups int + AuditLogMaxSize int + EnableGarbageCollection bool + EnableProfiling bool + EnableSwaggerUI bool + EnableWatchCache bool + EtcdServersOverrides []string + StorageConfig storagebackend.Config + ExternalHost string + InsecureBindAddress net.IP + InsecurePort int + KeystoneURL string + KubernetesServiceNodePort int + LongRunningRequestRE string + MasterCount int + MasterServiceNamespace string + MaxRequestsInFlight int + MinRequestTimeout int + OIDCCAFile string + OIDCClientID string + OIDCIssuerURL string + OIDCUsernameClaim string + OIDCGroupsClaim string + RequestHeaderUsernameHeaders []string + RequestHeaderClientCAFile string + RequestHeaderAllowedNames []string + RuntimeConfig config.ConfigurationMap + SecurePort int + ServiceClusterIPRange net.IPNet // TODO: make this a list + ServiceNodePortRange utilnet.PortRange + StorageVersions string // The default values for StorageVersions. StorageVersions overrides // these; you can change this if you want to change the defaults (e.g., // for testing). This is not actually exposed as a flag. @@ -423,6 +426,18 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { "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.") + fs.StringSliceVar(&s.RequestHeaderUsernameHeaders, "requestheader-username-headers", s.RequestHeaderUsernameHeaders, ""+ + "List of request headers to inspect for usernames. X-Remote-User is common.") + + fs.StringVar(&s.RequestHeaderClientCAFile, "requestheader-client-ca-file", s.RequestHeaderClientCAFile, ""+ + "Root certificate bundle to use to verify client certificates on incoming requests "+ + "before trusting usernames in headers specified by --requestheader-username-headers") + + fs.StringSliceVar(&s.RequestHeaderAllowedNames, "requestheader-allowed-names", s.RequestHeaderAllowedNames, ""+ + "List of client certificate common names to allow to provide usernames in headers "+ + "specified by --requestheader-username-headers. If empty, any client certificate validated "+ + "by the authorities in --requestheader-client-ca-file is allowed.") + fs.Var(&s.RuntimeConfig, "runtime-config", ""+ "A set of key=value pairs that describe runtime configuration that may be passed "+ "to apiserver. apis/ key can be used to turn on/off specific api versions. "+ diff --git a/plugin/pkg/auth/authenticator/request/headerrequest/BUILD b/plugin/pkg/auth/authenticator/request/headerrequest/BUILD new file mode 100644 index 00000000000..0d7dfd2b314 --- /dev/null +++ b/plugin/pkg/auth/authenticator/request/headerrequest/BUILD @@ -0,0 +1,32 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_binary", + "go_library", + "go_test", + "cgo_library", +) + +go_library( + name = "go_default_library", + srcs = ["requestheader.go"], + tags = ["automanaged"], + deps = [ + "//pkg/auth/authenticator:go_default_library", + "//pkg/auth/user:go_default_library", + "//pkg/util/cert:go_default_library", + "//pkg/util/sets:go_default_library", + "//plugin/pkg/auth/authenticator/request/x509:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = ["requestheader_test.go"], + library = "go_default_library", + tags = ["automanaged"], + deps = ["//pkg/auth/user:go_default_library"], +) diff --git a/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go b/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go index 4bb1b93c69b..cd704fbdfe0 100644 --- a/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go +++ b/plugin/pkg/auth/authenticator/request/headerrequest/requestheader.go @@ -31,6 +31,7 @@ import ( ) type requestHeaderAuthRequestHandler struct { + // nameHeaders are the headers to check (in order, case-insensitively) for an identity. The first header with a value wins. nameHeaders []string } diff --git a/plugin/pkg/auth/authenticator/request/x509/BUILD b/plugin/pkg/auth/authenticator/request/x509/BUILD index c78af13cb84..dad4d993677 100644 --- a/plugin/pkg/auth/authenticator/request/x509/BUILD +++ b/plugin/pkg/auth/authenticator/request/x509/BUILD @@ -18,8 +18,11 @@ go_library( ], tags = ["automanaged"], deps = [ + "//pkg/auth/authenticator:go_default_library", "//pkg/auth/user:go_default_library", "//pkg/util/errors:go_default_library", + "//pkg/util/sets:go_default_library", + "//vendor:github.com/golang/glog", ], ) @@ -29,5 +32,9 @@ go_test( data = glob(["testdata/*"]), library = "go_default_library", tags = ["automanaged"], - deps = ["//pkg/auth/user:go_default_library"], + deps = [ + "//pkg/auth/authenticator:go_default_library", + "//pkg/auth/user:go_default_library", + "//pkg/util/sets:go_default_library", + ], )