Merge pull request #33378 from deads2k/rbac-10-allow-token

Automatic merge from submit-queue

add anytoken authenticator

Adds `--insecure-allow-any-token` as a flag to the API server to create an authenticator that will accept any bearer token and transform it into a user by parsing it out as `username/group1,group2,...`.

This gives an easy way to identify as a user and check permissions:
```bash
ALLOW_ANY_TOKEN=true hack/local-up-cluster.sh 
kubectl config set-cluster local-kube --server=https://localhost:6443 --insecure-skip-tls-verify=true
kubectl config set-credentials david --token=david/group1
kubectl config set-context local --cluster=local-kube --user=david
kubectl config use-context local
```

@kubernetes/sig-auth
pull/6/head
Kubernetes Submit Queue 2016-09-29 12:32:08 -07:00 committed by GitHub
commit 6c5a187171
10 changed files with 140 additions and 4 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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}" \

View File

@ -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

View File

@ -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
}

View File

@ -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{

View File

@ -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) {

View File

@ -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 "+

View File

@ -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
}

View File

@ -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)
}
}
}