|  |  |  | @@ -31,7 +31,9 @@ import ( | 
		
	
		
			
				|  |  |  |  | 	"k8s.io/client-go/tools/cache" | 
		
	
		
			
				|  |  |  |  | 	"k8s.io/kubernetes/pkg/api" | 
		
	
		
			
				|  |  |  |  | 	"k8s.io/kubernetes/pkg/api/v1" | 
		
	
		
			
				|  |  |  |  | 	v1authenticationapi "k8s.io/kubernetes/pkg/apis/authentication/v1" | 
		
	
		
			
				|  |  |  |  | 	"k8s.io/kubernetes/pkg/client/clientset_generated/clientset" | 
		
	
		
			
				|  |  |  |  | 	v1authentication "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/authentication/v1" | 
		
	
		
			
				|  |  |  |  | 	v1core "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1" | 
		
	
		
			
				|  |  |  |  | 	"k8s.io/kubernetes/pkg/serviceaccount" | 
		
	
		
			
				|  |  |  |  |  | 
		
	
	
		
			
				
					
					|  |  |  | @@ -109,6 +111,10 @@ type SAControllerClientBuilder struct { | 
		
	
		
			
				|  |  |  |  | 	// to construct a controller client | 
		
	
		
			
				|  |  |  |  | 	CoreClient v1core.CoreV1Interface | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// AuthenticationClient is used to check API tokens to make sure they are valid before | 
		
	
		
			
				|  |  |  |  | 	// building a controller client from them | 
		
	
		
			
				|  |  |  |  | 	AuthenticationClient v1authentication.AuthenticationV1Interface | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// Namespace is the namespace used to host the service accounts that will back the | 
		
	
		
			
				|  |  |  |  | 	// controllers.  It must be highly privileged namespace which normal users cannot inspect. | 
		
	
		
			
				|  |  |  |  | 	Namespace string | 
		
	
	
		
			
				
					
					|  |  |  | @@ -117,29 +123,13 @@ type SAControllerClientBuilder struct { | 
		
	
		
			
				|  |  |  |  | // config returns a complete clientConfig for constructing clients.  This is separate in anticipation of composition | 
		
	
		
			
				|  |  |  |  | // which means that not all clientsets are known here | 
		
	
		
			
				|  |  |  |  | func (b SAControllerClientBuilder) Config(name string) (*restclient.Config, error) { | 
		
	
		
			
				|  |  |  |  | 	clientConfig := restclient.AnonymousClientConfig(b.ClientConfig) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// we need the SA UID to find a matching SA token | 
		
	
		
			
				|  |  |  |  | 	sa, err := b.CoreClient.ServiceAccounts(b.Namespace).Get(name, metav1.GetOptions{}) | 
		
	
		
			
				|  |  |  |  | 	if err != nil && !apierrors.IsNotFound(err) { | 
		
	
		
			
				|  |  |  |  | 	sa, err := b.getOrCreateServiceAccount(name) | 
		
	
		
			
				|  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  | 		return nil, err | 
		
	
		
			
				|  |  |  |  | 	} else if apierrors.IsNotFound(err) { | 
		
	
		
			
				|  |  |  |  | 		// check to see if the namespace exists.  If it isn't a NotFound, just try to create the SA. | 
		
	
		
			
				|  |  |  |  | 		// It'll probably fail, but perhaps that will have a better message. | 
		
	
		
			
				|  |  |  |  | 		if _, err := b.CoreClient.Namespaces().Get(b.Namespace, metav1.GetOptions{}); apierrors.IsNotFound(err) { | 
		
	
		
			
				|  |  |  |  | 			_, err = b.CoreClient.Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: b.Namespace}}) | 
		
	
		
			
				|  |  |  |  | 			if err != nil && !apierrors.IsAlreadyExists(err) { | 
		
	
		
			
				|  |  |  |  | 				return nil, err | 
		
	
		
			
				|  |  |  |  | 			} | 
		
	
		
			
				|  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 		sa, err = b.CoreClient.ServiceAccounts(b.Namespace).Create( | 
		
	
		
			
				|  |  |  |  | 			&v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: b.Namespace, Name: name}}) | 
		
	
		
			
				|  |  |  |  | 		if err != nil { | 
		
	
		
			
				|  |  |  |  | 			return nil, err | 
		
	
		
			
				|  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	var clientConfig *restclient.Config | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	lw := &cache.ListWatch{ | 
		
	
		
			
				|  |  |  |  | 		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { | 
		
	
		
			
				|  |  |  |  | 			options.FieldSelector = fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(v1.SecretTypeServiceAccountToken)}).String() | 
		
	
	
		
			
				
					
					|  |  |  | @@ -159,14 +149,32 @@ func (b SAControllerClientBuilder) Config(name string) (*restclient.Config, erro | 
		
	
		
			
				|  |  |  |  | 				return false, fmt.Errorf("error watching") | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 			case watch.Added, watch.Modified: | 
		
	
		
			
				|  |  |  |  | 				secret := event.Object.(*v1.Secret) | 
		
	
		
			
				|  |  |  |  | 				if !serviceaccount.IsServiceAccountToken(secret, sa) || | 
		
	
		
			
				|  |  |  |  | 					len(secret.Data[v1.ServiceAccountTokenKey]) == 0 { | 
		
	
		
			
				|  |  |  |  | 				secret, ok := event.Object.(*v1.Secret) | 
		
	
		
			
				|  |  |  |  | 				if !ok { | 
		
	
		
			
				|  |  |  |  | 					return false, fmt.Errorf("unexpected object type: %T", event.Object) | 
		
	
		
			
				|  |  |  |  | 				} | 
		
	
		
			
				|  |  |  |  | 				if !serviceaccount.IsServiceAccountToken(secret, sa) { | 
		
	
		
			
				|  |  |  |  | 					return false, nil | 
		
	
		
			
				|  |  |  |  | 				} | 
		
	
		
			
				|  |  |  |  | 				// TODO maybe verify the token is valid | 
		
	
		
			
				|  |  |  |  | 				clientConfig.BearerToken = string(secret.Data[v1.ServiceAccountTokenKey]) | 
		
	
		
			
				|  |  |  |  | 				restclient.AddUserAgent(clientConfig, apiserverserviceaccount.MakeUsername(b.Namespace, name)) | 
		
	
		
			
				|  |  |  |  | 				if len(secret.Data[v1.ServiceAccountTokenKey]) == 0 { | 
		
	
		
			
				|  |  |  |  | 					return false, nil | 
		
	
		
			
				|  |  |  |  | 				} | 
		
	
		
			
				|  |  |  |  | 				validConfig, valid, err := b.getAuthenticatedConfig(sa, string(secret.Data[v1.ServiceAccountTokenKey])) | 
		
	
		
			
				|  |  |  |  | 				if err != nil { | 
		
	
		
			
				|  |  |  |  | 					glog.Warningf("error validating API token for %s/%s in secret %s: %v", sa.Name, sa.Namespace, secret.Name, err) | 
		
	
		
			
				|  |  |  |  | 					// continue watching for good tokens | 
		
	
		
			
				|  |  |  |  | 					return false, nil | 
		
	
		
			
				|  |  |  |  | 				} | 
		
	
		
			
				|  |  |  |  | 				if !valid { | 
		
	
		
			
				|  |  |  |  | 					glog.Warningf("secret %s contained an invalid API token for %s/%s", secret.Name, sa.Name, sa.Namespace) | 
		
	
		
			
				|  |  |  |  | 					// try to delete the secret containing the invalid token | 
		
	
		
			
				|  |  |  |  | 					if err := b.CoreClient.Secrets(secret.Namespace).Delete(secret.Name, &metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { | 
		
	
		
			
				|  |  |  |  | 						glog.Warningf("error deleting secret %s containing invalid API token for %s/%s: %v", secret.Name, sa.Name, sa.Namespace, err) | 
		
	
		
			
				|  |  |  |  | 					} | 
		
	
		
			
				|  |  |  |  | 					// continue watching for good tokens | 
		
	
		
			
				|  |  |  |  | 					return false, nil | 
		
	
		
			
				|  |  |  |  | 				} | 
		
	
		
			
				|  |  |  |  | 				clientConfig = validConfig | 
		
	
		
			
				|  |  |  |  | 				return true, nil | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 			default: | 
		
	
	
		
			
				
					
					|  |  |  | @@ -180,6 +188,69 @@ func (b SAControllerClientBuilder) Config(name string) (*restclient.Config, erro | 
		
	
		
			
				|  |  |  |  | 	return clientConfig, nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | func (b SAControllerClientBuilder) getOrCreateServiceAccount(name string) (*v1.ServiceAccount, error) { | 
		
	
		
			
				|  |  |  |  | 	sa, err := b.CoreClient.ServiceAccounts(b.Namespace).Get(name, metav1.GetOptions{}) | 
		
	
		
			
				|  |  |  |  | 	if err == nil { | 
		
	
		
			
				|  |  |  |  | 		return sa, nil | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  | 	if !apierrors.IsNotFound(err) { | 
		
	
		
			
				|  |  |  |  | 		return nil, err | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// Create the namespace if we can't verify it exists. | 
		
	
		
			
				|  |  |  |  | 	// Tolerate errors, since we don't know whether this component has namespace creation permissions. | 
		
	
		
			
				|  |  |  |  | 	if _, err := b.CoreClient.Namespaces().Get(b.Namespace, metav1.GetOptions{}); err != nil { | 
		
	
		
			
				|  |  |  |  | 		b.CoreClient.Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: b.Namespace}}) | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// Create the service account | 
		
	
		
			
				|  |  |  |  | 	sa, err = b.CoreClient.ServiceAccounts(b.Namespace).Create(&v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: b.Namespace, Name: name}}) | 
		
	
		
			
				|  |  |  |  | 	if apierrors.IsAlreadyExists(err) { | 
		
	
		
			
				|  |  |  |  | 		// If we're racing to init and someone else already created it, re-fetch | 
		
	
		
			
				|  |  |  |  | 		return b.CoreClient.ServiceAccounts(b.Namespace).Get(name, metav1.GetOptions{}) | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  | 	return sa, err | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | func (b SAControllerClientBuilder) getAuthenticatedConfig(sa *v1.ServiceAccount, token string) (*restclient.Config, bool, error) { | 
		
	
		
			
				|  |  |  |  | 	username := apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	clientConfig := restclient.AnonymousClientConfig(b.ClientConfig) | 
		
	
		
			
				|  |  |  |  | 	clientConfig.BearerToken = token | 
		
	
		
			
				|  |  |  |  | 	restclient.AddUserAgent(clientConfig, username) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// Try token review first | 
		
	
		
			
				|  |  |  |  | 	tokenReview := &v1authenticationapi.TokenReview{Spec: v1authenticationapi.TokenReviewSpec{Token: token}} | 
		
	
		
			
				|  |  |  |  | 	if tokenResult, err := b.AuthenticationClient.TokenReviews().Create(tokenReview); err == nil { | 
		
	
		
			
				|  |  |  |  | 		if !tokenResult.Status.Authenticated { | 
		
	
		
			
				|  |  |  |  | 			glog.Warningf("Token for %s/%s did not authenticate correctly", sa.Name, sa.Namespace) | 
		
	
		
			
				|  |  |  |  | 			return nil, false, nil | 
		
	
		
			
				|  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  | 		if tokenResult.Status.User.Username != username { | 
		
	
		
			
				|  |  |  |  | 			glog.Warningf("Token for %s/%s authenticated as unexpected username: %s", sa.Name, sa.Namespace, tokenResult.Status.User.Username) | 
		
	
		
			
				|  |  |  |  | 			return nil, false, nil | 
		
	
		
			
				|  |  |  |  | 		} | 
		
	
		
			
				|  |  |  |  | 		glog.V(4).Infof("Verified credential for %s/%s", sa.Name, sa.Namespace) | 
		
	
		
			
				|  |  |  |  | 		return clientConfig, true, nil | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	// If we couldn't run the token review, the API might be disabled or we might not have permission. | 
		
	
		
			
				|  |  |  |  | 	// Try to make a request to /apis with the token. If we get a 401 we should consider the token invalid. | 
		
	
		
			
				|  |  |  |  | 	clientConfigCopy := *clientConfig | 
		
	
		
			
				|  |  |  |  | 	clientConfigCopy.NegotiatedSerializer = api.Codecs | 
		
	
		
			
				|  |  |  |  | 	client, err := restclient.UnversionedRESTClientFor(&clientConfigCopy) | 
		
	
		
			
				|  |  |  |  | 	if err != nil { | 
		
	
		
			
				|  |  |  |  | 		return nil, false, err | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  | 	err = client.Get().AbsPath("/apis").Do().Error() | 
		
	
		
			
				|  |  |  |  | 	if apierrors.IsUnauthorized(err) { | 
		
	
		
			
				|  |  |  |  | 		glog.Warningf("Token for %s/%s did not authenticate correctly: %v", sa.Name, sa.Namespace, err) | 
		
	
		
			
				|  |  |  |  | 		return nil, false, nil | 
		
	
		
			
				|  |  |  |  | 	} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | 	return clientConfig, true, nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | func (b SAControllerClientBuilder) ConfigOrDie(name string) *restclient.Config { | 
		
	
		
			
				|  |  |  |  | 	clientConfig, err := b.Config(name) | 
		
	
		
			
				|  |  |  |  | 	if err != nil { | 
		
	
	
		
			
				
					
					|  |  |  |   |