Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Look up service accounts from informer before trying live lookup #71816

Merged
merged 1 commit into from Jan 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion cmd/kube-apiserver/app/server.go
Expand Up @@ -504,7 +504,12 @@ func buildGenericConfig(
func BuildAuthenticator(s *options.ServerRunOptions, extclient clientgoclientset.Interface, versionedInformer clientgoinformers.SharedInformerFactory) (authenticator.Request, *spec.SecurityDefinitions, error) {
authenticatorConfig := s.Authentication.ToAuthenticationConfig()
if s.Authentication.ServiceAccounts.Lookup {
authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient(extclient)
authenticatorConfig.ServiceAccountTokenGetter = serviceaccountcontroller.NewGetterFromClient(
extclient,
versionedInformer.Core().V1().Secrets().Lister(),
versionedInformer.Core().V1().ServiceAccounts().Lister(),
versionedInformer.Core().V1().Pods().Lister(),
)
}
authenticatorConfig.BootstrapTokenAuthenticator = bootstrap.NewTokenAuthenticator(
versionedInformer.Core().V1().Secrets().Lister().Secrets(v1.NamespaceSystem),
Expand Down
19 changes: 16 additions & 3 deletions pkg/controller/serviceaccount/tokengetter.go
Expand Up @@ -20,30 +20,43 @@ import (
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
v1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/kubernetes/pkg/serviceaccount"
)

// clientGetter implements ServiceAccountTokenGetter using a clientset.Interface
type clientGetter struct {
client clientset.Interface
client clientset.Interface
secretLister v1listers.SecretLister
serviceAccountLister v1listers.ServiceAccountLister
podLister v1listers.PodLister
}

// 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 clientset.Interface) serviceaccount.ServiceAccountTokenGetter {
return clientGetter{c}
func NewGetterFromClient(c clientset.Interface, secretLister v1listers.SecretLister, serviceAccountLister v1listers.ServiceAccountLister, podLister v1listers.PodLister) serviceaccount.ServiceAccountTokenGetter {
return clientGetter{c, secretLister, serviceAccountLister, podLister}
}

func (c clientGetter) GetServiceAccount(namespace, name string) (*v1.ServiceAccount, error) {
if serviceAccount, err := c.serviceAccountLister.ServiceAccounts(namespace).Get(name); err == nil {
return serviceAccount, nil
}
return c.client.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{})
}

func (c clientGetter) GetPod(namespace, name string) (*v1.Pod, error) {
if pod, err := c.podLister.Pods(namespace).Get(name); err == nil {
return pod, nil
}
return c.client.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
}

func (c clientGetter) GetSecret(namespace, name string) (*v1.Secret, error) {
if secret, err := c.secretLister.Secrets(namespace).Get(name); err == nil {
return secret, nil
}
liggitt marked this conversation as resolved.
Show resolved Hide resolved
return c.client.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
}
2 changes: 2 additions & 0 deletions pkg/serviceaccount/BUILD
Expand Up @@ -57,6 +57,8 @@ go_test(
"//staging/src/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/client-go/util/cert:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2/jwt:go_default_library",
],
Expand Down
36 changes: 35 additions & 1 deletion pkg/serviceaccount/jwt_test.go
Expand Up @@ -19,13 +19,16 @@ package serviceaccount_test
import (
"context"
"reflect"
"strings"
liggitt marked this conversation as resolved.
Show resolved Hide resolved
"testing"

"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/authenticator"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
v1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
certutil "k8s.io/client-go/util/cert"
serviceaccountcontroller "k8s.io/kubernetes/pkg/controller/serviceaccount"
"k8s.io/kubernetes/pkg/serviceaccount"
Expand Down Expand Up @@ -277,7 +280,18 @@ func TestTokenGenerateAndValidate(t *testing.T) {

for k, tc := range testCases {
auds := authenticator.Audiences{"api"}
getter := serviceaccountcontroller.NewGetterFromClient(tc.Client)
getter := serviceaccountcontroller.NewGetterFromClient(
tc.Client,
v1listers.NewSecretLister(newIndexer(func(namespace, name string) (interface{}, error) {
return tc.Client.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
})),
v1listers.NewServiceAccountLister(newIndexer(func(namespace, name string) (interface{}, error) {
return tc.Client.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{})
})),
v1listers.NewPodLister(newIndexer(func(namespace, name string) (interface{}, error) {
return tc.Client.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
})),
)
authn := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, tc.Keys, auds, serviceaccount.NewLegacyValidator(tc.Client != nil, getter))

// An invalid, non-JWT token should always fail
Expand Down Expand Up @@ -316,3 +330,23 @@ func TestTokenGenerateAndValidate(t *testing.T) {
}
}
}

func newIndexer(get func(namespace, name string) (interface{}, error)) cache.Indexer {
return &fakeIndexer{get: get}
}

type fakeIndexer struct {
cache.Indexer
get func(namespace, name string) (interface{}, error)
}

func (f *fakeIndexer) GetByKey(key string) (interface{}, bool, error) {
parts := strings.SplitN(key, "/", 2)
namespace := parts[0]
name := ""
if len(parts) == 2 {
name = parts[1]
}
obj, err := f.get(namespace, name)
return obj, err == nil, err
}
2 changes: 2 additions & 0 deletions test/integration/auth/BUILD
Expand Up @@ -78,7 +78,9 @@ go_test(
"//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/tokentest:go_default_library",
"//staging/src/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library",
"//staging/src/k8s.io/client-go/tools/watch:go_default_library",
"//staging/src/k8s.io/client-go/transport:go_default_library",
Expand Down
35 changes: 34 additions & 1 deletion test/integration/auth/svcaccttoken_test.go
Expand Up @@ -39,6 +39,8 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
clientset "k8s.io/client-go/kubernetes"
v1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/pkg/apis/core"
serviceaccountgetter "k8s.io/kubernetes/pkg/controller/serviceaccount"
Expand Down Expand Up @@ -83,7 +85,18 @@ func TestServiceAccountTokenCreate(t *testing.T) {
iss,
[]interface{}{&pk},
aud,
serviceaccount.NewValidator(serviceaccountgetter.NewGetterFromClient(gcs)),
serviceaccount.NewValidator(serviceaccountgetter.NewGetterFromClient(
gcs,
v1listers.NewSecretLister(newIndexer(func(namespace, name string) (interface{}, error) {
return gcs.CoreV1().Secrets(namespace).Get(name, metav1.GetOptions{})
})),
v1listers.NewServiceAccountLister(newIndexer(func(namespace, name string) (interface{}, error) {
return gcs.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{})
})),
v1listers.NewPodLister(newIndexer(func(namespace, name string) (interface{}, error) {
return gcs.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
})),
)),
),
)
tokenGenerator, err := serviceaccount.JWTTokenGenerator(iss, sk)
Expand Down Expand Up @@ -683,3 +696,23 @@ func createDeleteSecret(t *testing.T, cs clientset.Interface, sec *v1.Secret) (*
}
}
}

func newIndexer(get func(namespace, name string) (interface{}, error)) cache.Indexer {
return &fakeIndexer{get: get}
}

type fakeIndexer struct {
cache.Indexer
get func(namespace, name string) (interface{}, error)
}

func (f *fakeIndexer) GetByKey(key string) (interface{}, bool, error) {
parts := strings.SplitN(key, "/", 2)
namespace := parts[0]
name := ""
if len(parts) == 2 {
name = parts[1]
}
obj, err := f.get(namespace, name)
return obj, err == nil, err
}
13 changes: 10 additions & 3 deletions test/integration/serviceaccount/service_account_test.go
Expand Up @@ -363,6 +363,10 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie
// TODO: remove rootClient after we refactor pkg/admission to use the clientset.
rootClientset := clientset.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}, BearerToken: rootToken})
externalRootClientset := kubernetes.NewForConfigOrDie(&restclient.Config{Host: apiServer.URL, ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}, BearerToken: rootToken})

externalInformers := informers.NewSharedInformerFactory(externalRootClientset, controller.NoResyncPeriodFunc())
informers := informers.NewSharedInformerFactory(rootClientset, controller.NoResyncPeriodFunc())

// Set up two authenticators:
// 1. A token authenticator that maps the rootToken to the "root" user
// 2. A ServiceAccountToken authenticator that validates ServiceAccount tokens
Expand All @@ -373,7 +377,12 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie
return nil, false, nil
})
serviceAccountKey, _ := rsa.GenerateKey(rand.Reader, 2048)
serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(rootClientset)
serviceAccountTokenGetter := serviceaccountcontroller.NewGetterFromClient(
rootClientset,
externalInformers.Core().V1().Secrets().Lister(),
externalInformers.Core().V1().ServiceAccounts().Lister(),
externalInformers.Core().V1().Pods().Lister(),
)
serviceAccountTokenAuth := serviceaccount.JWTTokenAuthenticator(serviceaccount.LegacyIssuer, []interface{}{&serviceAccountKey.PublicKey}, nil, serviceaccount.NewLegacyValidator(true, serviceAccountTokenGetter))
authenticator := union.New(
bearertoken.New(rootTokenAuth),
Expand Down Expand Up @@ -418,9 +427,7 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie
// Set up admission plugin to auto-assign serviceaccounts to pods
serviceAccountAdmission := serviceaccountadmission.NewServiceAccount()
serviceAccountAdmission.SetExternalKubeClientSet(externalRootClientset)
externalInformers := informers.NewSharedInformerFactory(externalRootClientset, controller.NoResyncPeriodFunc())
serviceAccountAdmission.SetExternalKubeInformerFactory(externalInformers)
informers := informers.NewSharedInformerFactory(rootClientset, controller.NoResyncPeriodFunc())

masterConfig := framework.NewMasterConfig()
masterConfig.GenericConfig.EnableIndex = true
Expand Down