From 5080a575ad0c7c8f85f5cf5f293db6f5dce91663 Mon Sep 17 00:00:00 2001 From: deads2k Date: Fri, 23 Sep 2016 12:35:31 -0400 Subject: [PATCH] add anytoken authenticator --- cmd/kube-apiserver/app/server.go | 1 + .../cmd/federation-apiserver/app/server.go | 1 + hack/local-up-cluster.sh | 6 +- hack/verify-flags/known-flags.txt | 3 +- pkg/apiserver/authenticator/authn.go | 11 +++ pkg/generated/openapi/zz_generated.openapi.go | 2 +- pkg/genericapiserver/genericapiserver.go | 2 +- .../options/server_run_options.go | 5 ++ .../authenticator/token/anytoken/anytoken.go | 42 +++++++++++ .../token/anytoken/anytoken_test.go | 71 +++++++++++++++++++ 10 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 plugin/pkg/auth/authenticator/token/anytoken/anytoken.go create mode 100644 plugin/pkg/auth/authenticator/token/anytoken/anytoken_test.go diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index bb4118c26f..c9036cda1b 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -202,6 +202,7 @@ func Run(s *options.APIServer) error { apiAuthenticator, err := authenticator.New(authenticator.AuthenticatorConfig{ Anonymous: s.AnonymousAuth, + AnyToken: s.EnableAnyToken, BasicAuthFile: s.BasicAuthFile, ClientCAFile: s.ClientCAFile, TokenAuthFile: s.TokenAuthFile, diff --git a/federation/cmd/federation-apiserver/app/server.go b/federation/cmd/federation-apiserver/app/server.go index ff7eaca86e..36d9163d03 100644 --- a/federation/cmd/federation-apiserver/app/server.go +++ b/federation/cmd/federation-apiserver/app/server.go @@ -116,6 +116,7 @@ func Run(s *options.ServerRunOptions) error { apiAuthenticator, err := authenticator.New(authenticator.AuthenticatorConfig{ Anonymous: s.AnonymousAuth, + AnyToken: s.EnableAnyToken, BasicAuthFile: s.BasicAuthFile, ClientCAFile: s.ClientCAFile, TokenAuthFile: s.TokenAuthFile, diff --git a/hack/local-up-cluster.sh b/hack/local-up-cluster.sh index d1403280e6..42135dc7d1 100755 --- a/hack/local-up-cluster.sh +++ b/hack/local-up-cluster.sh @@ -280,6 +280,10 @@ function start_apiserver { CERT_DIR=/var/run/kubernetes ROOT_CA_FILE=$CERT_DIR/apiserver.crt + anytoken_arg="" + if [[ -n "${ALLOW_ANY_TOKEN:-}" ]]; then + anytoken_arg="--insecure-allow-any-token " + fi priv_arg="" if [[ -n "${ALLOW_PRIVILEGED}" ]]; then priv_arg="--allow-privileged " @@ -297,7 +301,7 @@ function start_apiserver { fi APISERVER_LOG=/tmp/kube-apiserver.log - sudo -E "${GO_OUT}/hyperkube" apiserver ${priv_arg} ${runtime_config}\ + sudo -E "${GO_OUT}/hyperkube" apiserver ${anytoken_arg} ${priv_arg} ${runtime_config}\ ${advertise_address} \ --v=${LOG_LEVEL} \ --cert-dir="${CERT_DIR}" \ diff --git a/hack/verify-flags/known-flags.txt b/hack/verify-flags/known-flags.txt index 2e78575917..8de4227000 100644 --- a/hack/verify-flags/known-flags.txt +++ b/hack/verify-flags/known-flags.txt @@ -250,8 +250,9 @@ included-types-overrides include-extended-apis input-base input-dirs -insecure-experimental-approve-all-kubelet-csrs-for-group +insecure-allow-any-token insecure-bind-address +insecure-experimental-approve-all-kubelet-csrs-for-group insecure-port insecure-skip-tls-verify instance-metadata diff --git a/pkg/apiserver/authenticator/authn.go b/pkg/apiserver/authenticator/authn.go index 6978eb6083..e415be65aa 100644 --- a/pkg/apiserver/authenticator/authn.go +++ b/pkg/apiserver/authenticator/authn.go @@ -32,6 +32,7 @@ import ( "k8s.io/kubernetes/plugin/pkg/auth/authenticator/request/basicauth" "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" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/oidc" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/tokenfile" "k8s.io/kubernetes/plugin/pkg/auth/authenticator/token/webhook" @@ -39,6 +40,7 @@ import ( type AuthenticatorConfig struct { Anonymous bool + AnyToken bool BasicAuthFile string ClientCAFile string TokenAuthFile string @@ -121,10 +123,19 @@ func New(config AuthenticatorConfig) (authenticator.Request, error) { authenticators = append(authenticators, webhookTokenAuth) } + // always add anytoken last, so that every other token authenticator gets to try first + if config.AnyToken { + authenticators = append(authenticators, bearertoken.New(anytoken.AnyTokenAuthenticator{})) + } + if len(authenticators) == 0 { if config.Anonymous { return anonymous.NewAuthenticator(), nil } + } + + switch len(authenticators) { + case 0: return nil, nil } diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index ec362fcdb7..927a85a774 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -9148,7 +9148,7 @@ var OpenAPIDefinitions *common.OpenAPIDefinitions = &common.OpenAPIDefinitions{ "v1.Node": { Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ - Description: "Node is a worker node in Kubernetes, formerly known as minion. Each node will have a unique identifier in the cache (i.e. in etcd).", + Description: "Node is a worker node in Kubernetes. Each node will have a unique identifier in the cache (i.e. in etcd).", Properties: map[string]spec.Schema{ "metadata": { SchemaProps: spec.SchemaProps{ diff --git a/pkg/genericapiserver/genericapiserver.go b/pkg/genericapiserver/genericapiserver.go index 5bd4b7e5a4..4b4e797be3 100644 --- a/pkg/genericapiserver/genericapiserver.go +++ b/pkg/genericapiserver/genericapiserver.go @@ -258,7 +258,7 @@ func (s *GenericAPIServer) Run(options *options.ServerRunOptions) { options.TLSPrivateKeyFile = path.Join(options.CertDirectory, "apiserver.key") // TODO (cjcullen): Is ClusterIP the right address to sign a cert with? alternateIPs := []net.IP{s.ServiceReadWriteIP} - alternateDNS := []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes"} + alternateDNS := []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost"} // It would be nice to set a fqdn subject alt name, but only the kubelets know, the apiserver is clueless // alternateDNS = append(alternateDNS, "kubernetes.default.svc.CLUSTER.DNS.NAME") if !certutil.CanReadCertOrKey(options.TLSCertFile, options.TLSPrivateKeyFile) { diff --git a/pkg/genericapiserver/options/server_run_options.go b/pkg/genericapiserver/options/server_run_options.go index 68d9f1396f..f84af14b1e 100644 --- a/pkg/genericapiserver/options/server_run_options.go +++ b/pkg/genericapiserver/options/server_run_options.go @@ -120,6 +120,7 @@ type ServerRunOptions struct { TLSCertFile string TLSPrivateKeyFile string TokenAuthFile string + EnableAnyToken bool WatchCacheSizes []string } @@ -473,6 +474,10 @@ func (s *ServerRunOptions) AddUniversalFlags(fs *pflag.FlagSet) { "If set, the file that will be used to secure the secure port of the API server "+ "via token authentication.") + fs.BoolVar(&s.EnableAnyToken, "insecure-allow-any-token", s.EnableAnyToken, ""+ + "If set, your server will be INSECURE. Any token will be allowed and user information will be parsed "+ + "from the token as `username/group1,group2`") + fs.StringSliceVar(&s.WatchCacheSizes, "watch-cache-sizes", s.WatchCacheSizes, ""+ "List of watch cache sizes for every resource (pods, nodes, etc.), comma separated. "+ "The individual override format: resource#size, where size is a number. It takes effect "+ diff --git a/plugin/pkg/auth/authenticator/token/anytoken/anytoken.go b/plugin/pkg/auth/authenticator/token/anytoken/anytoken.go new file mode 100644 index 0000000000..6e23999a7f --- /dev/null +++ b/plugin/pkg/auth/authenticator/token/anytoken/anytoken.go @@ -0,0 +1,42 @@ +/* +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 anytoken + +import ( + "strings" + + "k8s.io/kubernetes/pkg/auth/user" +) + +type AnyTokenAuthenticator struct{} + +func (AnyTokenAuthenticator) AuthenticateToken(value string) (user.Info, bool, error) { + lastSlash := strings.LastIndex(value, "/") + if lastSlash == -1 { + return &user.DefaultInfo{Name: value}, true, nil + } + + ret := &user.DefaultInfo{Name: value[:lastSlash]} + + groupString := value[lastSlash+1:] + if len(groupString) == 0 { + return ret, true, nil + } + + ret.Groups = strings.Split(groupString, ",") + return ret, true, nil +} diff --git a/plugin/pkg/auth/authenticator/token/anytoken/anytoken_test.go b/plugin/pkg/auth/authenticator/token/anytoken/anytoken_test.go new file mode 100644 index 0000000000..5375050dcf --- /dev/null +++ b/plugin/pkg/auth/authenticator/token/anytoken/anytoken_test.go @@ -0,0 +1,71 @@ +/* +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 anytoken + +import ( + "reflect" + "testing" + + "k8s.io/kubernetes/pkg/auth/user" +) + +func TestAnyTokenAuthenticator(t *testing.T) { + tests := []struct { + name string + token string + + expectedUser user.Info + }{ + { + name: "user only", + token: "joe", + expectedUser: &user.DefaultInfo{Name: "joe"}, + }, + { + name: "user with slash", + token: "scheme/joe/", + expectedUser: &user.DefaultInfo{Name: "scheme/joe"}, + }, + { + name: "user with groups", + token: "joe/group1,group2", + expectedUser: &user.DefaultInfo{Name: "joe", Groups: []string{"group1", "group2"}}, + }, + { + name: "user with slash and groups", + token: "scheme/joe/group1,group2", + expectedUser: &user.DefaultInfo{Name: "scheme/joe", Groups: []string{"group1", "group2"}}, + }, + } + + for _, tc := range tests { + actualUser, _, _ := AnyTokenAuthenticator{}.AuthenticateToken(tc.token) + + if len(actualUser.GetExtra()) != 0 { + t.Errorf("%q: got extra: %v", tc.name, actualUser.GetExtra()) + } + if len(actualUser.GetUID()) != 0 { + t.Errorf("%q: got extra: %v", tc.name, actualUser.GetUID()) + } + if e, a := tc.expectedUser.GetName(), actualUser.GetName(); e != a { + t.Errorf("%q: expected %v, got %v", tc.name, e, a) + } + if e, a := tc.expectedUser.GetGroups(), actualUser.GetGroups(); !reflect.DeepEqual(e, a) { + t.Errorf("%q: expected %v, got %v", tc.name, e, a) + } + } +}