Merge pull request #626 from ericchiang/storage-kubernetes-guess-namespace-from-service-account-token
storage/kubernetes: guess namespace from the service account token
This commit is contained in:
		@@ -320,6 +320,32 @@ func loadKubeConfig(kubeConfigPath string) (cluster k8sapi.Cluster, user k8sapi.
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func namespaceFromServiceAccountJWT(s string) (string, error) {
 | 
				
			||||||
 | 
						// The service account token is just a JWT. Parse it as such.
 | 
				
			||||||
 | 
						parts := strings.Split(s, ".")
 | 
				
			||||||
 | 
						if len(parts) < 2 {
 | 
				
			||||||
 | 
							// It's extremely important we don't log the actual service account token.
 | 
				
			||||||
 | 
							return "", fmt.Errorf("malformed service account token: expected 3 parts got %d", len(parts))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						payload, err := base64.RawURLEncoding.DecodeString(parts[1])
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("malformed service account token: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var data struct {
 | 
				
			||||||
 | 
							// The claim Kubernetes uses to identify which namespace a service account belongs to.
 | 
				
			||||||
 | 
							//
 | 
				
			||||||
 | 
							// See: https://github.com/kubernetes/kubernetes/blob/v1.4.3/pkg/serviceaccount/jwt.go#L42
 | 
				
			||||||
 | 
							Namespace string `json:"kubernetes.io/serviceaccount/namespace"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := json.Unmarshal(payload, &data); err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("malformed service account token: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if data.Namespace == "" {
 | 
				
			||||||
 | 
							return "", errors.New(`jwt claim "kubernetes.io/serviceaccount/namespace" not found`)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return data.Namespace, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func inClusterConfig() (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, err error) {
 | 
					func inClusterConfig() (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, err error) {
 | 
				
			||||||
	host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
 | 
						host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
 | 
				
			||||||
	if len(host) == 0 || len(port) == 0 {
 | 
						if len(host) == 0 || len(port) == 0 {
 | 
				
			||||||
@@ -330,17 +356,20 @@ func inClusterConfig() (cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace
 | 
				
			|||||||
		Server:               "https://" + host + ":" + port,
 | 
							Server:               "https://" + host + ":" + port,
 | 
				
			||||||
		CertificateAuthority: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
 | 
							CertificateAuthority: "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if namespace = os.Getenv("KUBERNETES_POD_NAMESPACE"); namespace == "" {
 | 
					 | 
				
			||||||
		err = fmt.Errorf("unable to load in-cluster configuration, KUBERNETES_POD_NAMESPACE must be defined")
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
 | 
						token, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token")
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	user = k8sapi.AuthInfo{Token: string(token)}
 | 
						user = k8sapi.AuthInfo{Token: string(token)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if namespace = os.Getenv("KUBERNETES_POD_NAMESPACE"); namespace == "" {
 | 
				
			||||||
 | 
							namespace, err = namespaceFromServiceAccountJWT(user.Token)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								err = fmt.Errorf("failed to inspect service account token: %v", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										60
									
								
								storage/kubernetes/client_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								storage/kubernetes/client_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
				
			|||||||
 | 
					package kubernetes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestNamespaceFromServiceAccountJWT(t *testing.T) {
 | 
				
			||||||
 | 
						namespace, err := namespaceFromServiceAccountJWT(serviceAccountToken)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						wantNamespace := "dex-test-namespace"
 | 
				
			||||||
 | 
						if namespace != wantNamespace {
 | 
				
			||||||
 | 
							t.Errorf("expected namespace %q got %q", wantNamespace, namespace)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var serviceAccountToken = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZXgtdGVzdC1uYW1lc3BhY2UiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlY3JldC5uYW1lIjoiZG90aGVyb2JvdC1zZWNyZXQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiZG90aGVyb2JvdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjQyYjJhOTRmLTk4MjAtMTFlNi1iZDc0LTJlZmQzOGYxMjYxYyIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZXgtdGVzdC1uYW1lc3BhY2U6ZG90aGVyb2JvdCJ9.KViBpPwCiBwxDvAjYUUXoVvLVwqV011aLlYQpNtX12Bh8M-QAFch-3RWlo_SR00bcdFg_nZo9JKACYlF_jHMEsf__PaYms9r7vEaSg0jPfkqnL2WXZktzQRyLBr0n-bxeUrbwIWsKOAC0DfFB5nM8XoXljRmq8yAx8BAdmQp7MIFb4EOV9nYthhua6pjzYyaFSiDiYTjw7HtXOvoL8oepodJ3-37pUKS8vdBvnvUoqC4M1YAhkO5L36JF6KV_RfmG8GPEdNQfXotHcsR-3jKi1n8S5l7Xd-rhrGOhSGQizH3dORzo9GvBAhYeqbq1O-NLzm2EQUiMQayIUx7o4g3Kw"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The following program was used to generate the example token. Since we don't want to
 | 
				
			||||||
 | 
					// import Kubernetes, just leave it as a comment.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/rand"
 | 
				
			||||||
 | 
						"crypto/rsa"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/serviceaccount"
 | 
				
			||||||
 | 
						"k8s.io/kubernetes/pkg/util/uuid"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						key, err := rsa.GenerateKey(rand.Reader, 2048)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sa := api.ServiceAccount{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Namespace: "dex-test-namespace",
 | 
				
			||||||
 | 
								Name:      "dotherobot",
 | 
				
			||||||
 | 
								UID:       uuid.NewUUID(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						secret := api.Secret{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{
 | 
				
			||||||
 | 
								Namespace: "dex-test-namespace",
 | 
				
			||||||
 | 
								Name:      "dotherobot-secret",
 | 
				
			||||||
 | 
								UID:       uuid.NewUUID(),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						token, err := serviceaccount.JWTTokenGenerator(key).GenerateToken(sa, secret)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fmt.Println(token)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
		Reference in New Issue
	
	Block a user