package serviceaccounts import ( "fmt" "time" kapi "k8s.io/kubernetes/pkg/api" kclientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/restclient" kclient "k8s.io/kubernetes/pkg/client/unversioned" adapter "k8s.io/kubernetes/pkg/client/unversioned/adapters/internalclientset" "github.com/openshift/origin/pkg/client" ) // TokenRetriever defined an interface for getting an API token for a service account type TokenRetriever interface { GetToken(serviceAccountNamespace, serviceAccountName string) (token string, err error) } // ClientLookupTokenRetriever uses its client to look up a service account token type ClientLookupTokenRetriever struct { Client kclientset.Interface } // GetToken returns a token for the named service account or an error if none existed after a timeout func (s *ClientLookupTokenRetriever) GetToken(namespace, name string) (string, error) { for i := 0; i < 30; i++ { // Wait on subsequent retries if i != 0 { time.Sleep(time.Second) } // Get the service account serviceAccount, err := s.Client.Core().ServiceAccounts(namespace).Get(name) if err != nil { continue } // Get the secrets // TODO: JTL: create one directly once we have that ability for _, secretRef := range serviceAccount.Secrets { secret, err2 := s.Client.Core().Secrets(namespace).Get(secretRef.Name) if err2 != nil { // Tolerate fetch errors on a particular secret continue } if IsValidServiceAccountToken(serviceAccount, secret) { // Return a valid token return string(secret.Data[kapi.ServiceAccountTokenKey]), nil } } } return "", fmt.Errorf("Could not get token for %s/%s", namespace, name) } // Clients returns an OpenShift and Kubernetes client with the credentials of the named service account // TODO: change return types to client.Interface/kclientset.Interface to allow auto-reloading credentials func Clients(config restclient.Config, tokenRetriever TokenRetriever, namespace, name string) (*restclient.Config, *client.Client, *kclient.Client, *kclientset.Clientset, error) { // Clear existing auth info config.Username = "" config.Password = "" config.CertFile = "" config.CertData = []byte{} config.KeyFile = "" config.KeyData = []byte{} config.BearerToken = "" kubeUserAgent := "" openshiftUserAgent := "" // they specified, don't mess with it if len(config.UserAgent) > 0 { kubeUserAgent = config.UserAgent openshiftUserAgent = config.UserAgent } else { kubeUserAgent = fmt.Sprintf("%s system:serviceaccount:%s:%s", restclient.DefaultKubernetesUserAgent(), namespace, name) openshiftUserAgent = fmt.Sprintf("%s system:serviceaccount:%s:%s", client.DefaultOpenShiftUserAgent(), namespace, name) } // For now, just initialize the token once // TODO: refetch the token if the client encounters 401 errors token, err := tokenRetriever.GetToken(namespace, name) if err != nil { return nil, nil, nil, nil, err } config.BearerToken = token config.UserAgent = openshiftUserAgent c, err := client.New(&config) if err != nil { return nil, nil, nil, nil, err } config.UserAgent = kubeUserAgent kc, err := kclient.New(&config) if err != nil { return nil, nil, nil, nil, err } kcset := adapter.FromUnversionedClient(kc) return &config, c, kc, kcset, nil } // IsValidServiceAccountToken returns true if the given secret contains a service account token valid for the given service account func IsValidServiceAccountToken(serviceAccount *kapi.ServiceAccount, secret *kapi.Secret) bool { if secret.Type != kapi.SecretTypeServiceAccountToken { return false } if secret.Namespace != serviceAccount.Namespace { return false } if secret.Annotations[kapi.ServiceAccountNameKey] != serviceAccount.Name { return false } if secret.Annotations[kapi.ServiceAccountUIDKey] != string(serviceAccount.UID) { return false } if len(secret.Data[kapi.ServiceAccountTokenKey]) == 0 { return false } return true }