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 != "" { if s.ServiceAccountKeyFile == "" && s.TLSPrivateKeyFile != "" {
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 { if err != nil {
glog.Fatalf("Invalid Authentication Config: %v", err) 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"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator/bearertoken" "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/serviceaccount"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/password/passwordfile"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/basicauth" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/request/basicauth"
@ -32,7 +32,7 @@ import (
) )
// NewAuthenticator returns an authenticator.Request or an error // 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 var authenticators []authenticator.Request
if len(basicAuthFile) > 0 { if len(basicAuthFile) > 0 {
@ -60,7 +60,7 @@ func NewAuthenticator(basicAuthFile, clientCAFile, tokenFile, serviceAccountKeyF
} }
if len(serviceAccountKeyFile) > 0 { if len(serviceAccountKeyFile) > 0 {
serviceAccountAuth, err := newServiceAccountAuthenticator(serviceAccountKeyFile, serviceAccountLookup, client) serviceAccountAuth, err := newServiceAccountAuthenticator(serviceAccountKeyFile, serviceAccountLookup, helper)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -98,12 +98,20 @@ func newAuthenticatorFromTokenFile(tokenAuthFile string) (authenticator.Request,
} }
// newServiceAccountAuthenticator returns an authenticator.Request or an error // 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) publicKey, err := serviceaccount.ReadPublicKey(keyfile)
if err != nil { if err != nil {
return nil, err 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 return bearertoken.New(tokenAuthenticator), nil
} }

View File

@ -29,7 +29,6 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
) )
const ( const (
@ -124,15 +123,15 @@ func (j *jwtTokenGenerator) GenerateToken(serviceAccount api.ServiceAccount, sec
// JWTTokenAuthenticator authenticates tokens as JWT tokens produced by JWTTokenGenerator // 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) // 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 // 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, client client.Interface) authenticator.Token { func JWTTokenAuthenticator(keys []*rsa.PublicKey, lookup bool, getter ServiceAccountTokenGetter) authenticator.Token {
return &jwtTokenAuthenticator{keys, lookup, client} return &jwtTokenAuthenticator{keys, lookup, getter}
} }
type jwtTokenAuthenticator struct { type jwtTokenAuthenticator struct {
keys []*rsa.PublicKey keys []*rsa.PublicKey
lookup bool lookup bool
client client.Interface getter ServiceAccountTokenGetter
} }
func (j *jwtTokenAuthenticator) AuthenticateToken(token string) (user.Info, bool, error) { 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 { if j.lookup {
// Make sure token hasn't been invalidated by deletion of the secret // 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 { if err != nil {
return nil, false, errors.New("Token has been invalidated") 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) // 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 { if err != nil {
return nil, false, err return nil, false, err
} }

View File

@ -214,7 +214,8 @@ func TestTokenGenerateAndValidate(t *testing.T) {
} }
for k, tc := range testCases { 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) user, ok, err := authenticator.AuthenticateToken(token)
if (err != nil) != tc.ExpectedErr { 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 return nil, false, nil
}) })
serviceAccountKey, err := rsa.GenerateKey(rand.Reader, 2048) 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( authenticator := union.New(
bearertoken.New(rootTokenAuth), bearertoken.New(rootTokenAuth),
bearertoken.New(serviceAccountTokenAuth), bearertoken.New(serviceAccountTokenAuth),