Prevent auth recursion for service account tokens

pull/6/head
Jordan Liggitt 2015-05-16 23:29:18 -04:00
parent d51e131726
commit d90e7409e4
6 changed files with 105 additions and 15 deletions

View File

@ -300,7 +300,7 @@ func (s *APIServer) Run(_ []string) error {
if s.ServiceAccountKeyFile == "" && s.TLSPrivateKeyFile != "" {
s.ServiceAccountKeyFile = s.TLSPrivateKeyFile
}
authenticator, err := apiserver.NewAuthenticator(s.BasicAuthFile, s.ClientCAFile, s.TokenAuthFile, s.ServiceAccountKeyFile, s.ServiceAccountLookup, client)
authenticator, err := apiserver.NewAuthenticator(s.BasicAuthFile, s.ClientCAFile, s.TokenAuthFile, s.ServiceAccountKeyFile, s.ServiceAccountLookup, helper)
if err != nil {
glog.Fatalf("Invalid Authentication Config: %v", err)
}

View File

@ -21,8 +21,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/serviceaccount"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/basicauth"
@ -32,7 +32,7 @@ import (
)
// NewAuthenticator returns an authenticator.Request or an error
func NewAuthenticator(basicAuthFile, clientCAFile, tokenFile, serviceAccountKeyFile string, serviceAccountLookup bool, client client.Interface) (authenticator.Request, error) {
func NewAuthenticator(basicAuthFile, clientCAFile, tokenFile, serviceAccountKeyFile string, serviceAccountLookup bool, helper tools.EtcdHelper) (authenticator.Request, error) {
var authenticators []authenticator.Request
if len(basicAuthFile) > 0 {
@ -60,7 +60,7 @@ func NewAuthenticator(basicAuthFile, clientCAFile, tokenFile, serviceAccountKeyF
}
if len(serviceAccountKeyFile) > 0 {
serviceAccountAuth, err := newServiceAccountAuthenticator(serviceAccountKeyFile, serviceAccountLookup, client)
serviceAccountAuth, err := newServiceAccountAuthenticator(serviceAccountKeyFile, serviceAccountLookup, helper)
if err != nil {
return nil, err
}
@ -98,12 +98,20 @@ func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request,
}
// newServiceAccountAuthenticator returns an authenticator.Request or an error
func newServiceAccountAuthenticator(keyfile string, lookup bool, client client.Interface) (authenticator.Request, error) {
func newServiceAccountAuthenticator(keyfile string, lookup bool, helper tools.EtcdHelper) (authenticator.Request, error) {
publicKey, err := serviceaccount.ReadPublicKey(keyfile)
if err != nil {
return nil, err
}
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{publicKey}, lookup, client)
var serviceAccountGetter serviceaccount.ServiceAccountTokenGetter
if lookup {
// If we need to look up service accounts and tokens,
// go directly to etcd to avoid recursive auth insanity
serviceAccountGetter = serviceaccount.NewGetterFromEtcdHelper(helper)
}
tokenAuthenticator := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{publicKey}, lookup, serviceAccountGetter)
return bearertoken.New(tokenAuthenticator), nil
}

View File

@ -29,7 +29,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
)
const (
@ -124,15 +123,15 @@ func (j *jwtTokenGenerator) GenerateToken(serviceAccount api.ServiceAccount, sec
// JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator
// Token signatures are verified using each of the given public keys until one works (allowing key rotation)
// If lookup is true, the service account and secret referenced as claims inside the token are retrieved and verified using the given client
func JWTTokenAuthenticator(keys []*rsa.PublicKey, lookup bool, client client.Interface) authenticator.Token {
return &jwtTokenAuthenticator{keys, lookup, client}
// If lookup is true, the service account and secret referenced as claims inside the token are retrieved and verified with the provided ServiceAccountTokenGetter
func JWTTokenAuthenticator(keys []*rsa.PublicKey, lookup bool, getter ServiceAccountTokenGetter) authenticator.Token {
return &jwtTokenAuthenticator{keys, lookup, getter}
}
type jwtTokenAuthenticator struct {
keys []*rsa.PublicKey
lookup bool
client client.Interface
getter ServiceAccountTokenGetter
}
func (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) {
@ -199,7 +198,7 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool
if j.lookup {
// Make sure token hasn't been invalidated by deletion of the secret
secret, err := j.client.Secrets(namespace).Get(secretName)
secret, err := j.getter.GetSecret(namespace, secretName)
if err != nil {
return nil, false, errors.New("Token has been invalidated")
}
@ -208,7 +207,7 @@ func (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool
}
// Make sure service account still exists (name and UID)
serviceAccount, err := j.client.ServiceAccounts(namespace).Get(serviceAccountName)
serviceAccount, err := j.getter.GetServiceAccount(namespace, serviceAccountName)
if err != nil {
return nil, false, err
}

View File

@ -214,7 +214,8 @@ func TestTokenGenerateAndValidate(t *testing.T) {
}
for k, tc := range testCases {
authenticator := JWTTokenAuthenticator(tc.Keys, tc.Client != nil, tc.Client)
getter := NewGetterFromClient(tc.Client)
authenticator := JWTTokenAuthenticator(tc.Keys, tc.Client != nil, getter)
user, ok, err := authenticator.AuthenticateToken(token)
if (err != nil) != tc.ExpectedErr {

View File

@ -0,0 +1,81 @@
/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 serviceaccount
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret"
secretetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/secret/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/serviceaccount"
serviceaccountetcd "github.com/GoogleCloudPlatform/kubernetes/pkg/registry/serviceaccount/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
)
// ServiceAccountTokenGetter defines functions to retrieve a named service account and secret
type ServiceAccountTokenGetter interface {
GetServiceAccount(namespace, name string) (*api.ServiceAccount, error)
GetSecret(namespace, name string) (*api.Secret, error)
}
// clientGetter implements ServiceAccountTokenGetter using a client.Interface
type clientGetter struct {
client client.Interface
}
// NewGetterFromClient returns a ServiceAccountTokenGetter that
// uses the specified client to retrieve service accounts and secrets.
// The client should NOT authenticate using a service account token
// the returned getter will be used to retrieve, or recursion will result.
func NewGetterFromClient(c client.Interface) ServiceAccountTokenGetter {
return clientGetter{c}
}
func (c clientGetter) GetServiceAccount(namespace, name string) (*api.ServiceAccount, error) {
return c.client.ServiceAccounts(namespace).Get(name)
}
func (c clientGetter) GetSecret(namespace, name string) (*api.Secret, error) {
return c.client.Secrets(namespace).Get(name)
}
// registryGetter implements ServiceAccountTokenGetter using a service account and secret registry
type registryGetter struct {
serviceAccounts serviceaccount.Registry
secrets secret.Registry
}
// NewGetterFromRegistries returns a ServiceAccountTokenGetter that
// uses the specified registries to retrieve service accounts and secrets.
func NewGetterFromRegistries(serviceAccounts serviceaccount.Registry, secrets secret.Registry) ServiceAccountTokenGetter {
return &registryGetter{serviceAccounts, secrets}
}
func (r *registryGetter) GetServiceAccount(namespace, name string) (*api.ServiceAccount, error) {
ctx := api.WithNamespace(api.NewContext(), namespace)
return r.serviceAccounts.GetServiceAccount(ctx, name)
}
func (r *registryGetter) GetSecret(namespace, name string) (*api.Secret, error) {
ctx := api.WithNamespace(api.NewContext(), namespace)
return r.secrets.GetSecret(ctx, name)
}
// NewGetterFromEtcdHelper returns a ServiceAccountTokenGetter that
// uses the specified helper to retrieve service accounts and secrets.
func NewGetterFromEtcdHelper(helper tools.EtcdHelper) ServiceAccountTokenGetter {
return NewGetterFromRegistries(
serviceaccount.NewRegistry(serviceaccountetcd.NewStorage(helper)),
secret.NewRegistry(secretetcd.NewStorage(helper)),
)
}

View File

@ -366,7 +366,8 @@ func startServiceAccountTestServer(t *testing.T) (*client.Client, client.Config,
return nil, false, nil
})
serviceAccountKey, err := rsa.GenerateKey(rand.Reader, 2048)
serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{&serviceAccountKey.PublicKey}, true, rootClient)
serviceAccountTokenGetter := serviceaccount.NewGetterFromClient(rootClient)
serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator([]*rsa.PublicKey{&serviceAccountKey.PublicKey}, true, serviceAccountTokenGetter)
authenticator := union.New(
bearertoken.New(rootTokenAuth),
bearertoken.New(serviceAccountTokenAuth),