Merge pull request #21095 from liggitt/sa-namespace
Auto commit by PR queue bot
This commit is contained in:
		| @@ -2250,6 +2250,8 @@ const ( | |||||||
| 	ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" | 	ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" | ||||||
| 	// ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets | 	// ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets | ||||||
| 	ServiceAccountRootCAKey = "ca.crt" | 	ServiceAccountRootCAKey = "ca.crt" | ||||||
|  | 	// ServiceAccountNamespaceKey is the key of the optional namespace to use as the default for namespaced API calls | ||||||
|  | 	ServiceAccountNamespaceKey = "namespace" | ||||||
|  |  | ||||||
| 	// SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg | 	// SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg | ||||||
| 	// | 	// | ||||||
|   | |||||||
| @@ -2721,6 +2721,8 @@ const ( | |||||||
| 	ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" | 	ServiceAccountKubeconfigKey = "kubernetes.kubeconfig" | ||||||
| 	// ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets | 	// ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets | ||||||
| 	ServiceAccountRootCAKey = "ca.crt" | 	ServiceAccountRootCAKey = "ca.crt" | ||||||
|  | 	// ServiceAccountNamespaceKey is the key of the optional namespace to use as the default for namespaced API calls | ||||||
|  | 	ServiceAccountNamespaceKey = "namespace" | ||||||
|  |  | ||||||
| 	// SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg | 	// SecretTypeDockercfg contains a dockercfg file that follows the same format rules as ~/.dockercfg | ||||||
| 	// | 	// | ||||||
|   | |||||||
| @@ -19,8 +19,10 @@ package clientcmd | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
|  | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/golang/glog" | 	"github.com/golang/glog" | ||||||
| 	"github.com/imdario/mergo" | 	"github.com/imdario/mergo" | ||||||
| @@ -325,12 +327,19 @@ func (inClusterClientConfig) ClientConfig() (*client.Config, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (inClusterClientConfig) Namespace() (string, error) { | func (inClusterClientConfig) Namespace() (string, error) { | ||||||
| 	// TODO: generic way to figure out what namespace you are running in? | 	// This way assumes you've set the POD_NAMESPACE environment variable using the downward API. | ||||||
| 	// This way assumes you've set the POD_NAMESPACE environment variable | 	// This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up | ||||||
| 	// using the downward API. |  | ||||||
| 	if ns := os.Getenv("POD_NAMESPACE"); ns != "" { | 	if ns := os.Getenv("POD_NAMESPACE"); ns != "" { | ||||||
| 		return ns, nil | 		return ns, nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Fall back to the namespace associated with the service account token, if available | ||||||
|  | 	if data, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace"); err == nil { | ||||||
|  | 		if ns := strings.TrimSpace(string(data)); len(ns) > 0 { | ||||||
|  | 			return ns, nil | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return "default", nil | 	return "default", nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -325,6 +325,7 @@ func (e *TokensController) createSecret(serviceAccount *api.ServiceAccount) erro | |||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	secret.Data[api.ServiceAccountTokenKey] = []byte(token) | 	secret.Data[api.ServiceAccountTokenKey] = []byte(token) | ||||||
|  | 	secret.Data[api.ServiceAccountNamespaceKey] = []byte(serviceAccount.Namespace) | ||||||
| 	if e.rootCA != nil && len(e.rootCA) > 0 { | 	if e.rootCA != nil && len(e.rootCA) > 0 { | ||||||
| 		secret.Data[api.ServiceAccountRootCAKey] = e.rootCA | 		secret.Data[api.ServiceAccountRootCAKey] = e.rootCA | ||||||
| 	} | 	} | ||||||
| @@ -364,10 +365,12 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *api.ServiceAcco | |||||||
| 	caData := secret.Data[api.ServiceAccountRootCAKey] | 	caData := secret.Data[api.ServiceAccountRootCAKey] | ||||||
| 	needsCA := len(e.rootCA) > 0 && bytes.Compare(caData, e.rootCA) != 0 | 	needsCA := len(e.rootCA) > 0 && bytes.Compare(caData, e.rootCA) != 0 | ||||||
|  |  | ||||||
|  | 	needsNamespace := len(secret.Data[api.ServiceAccountNamespaceKey]) == 0 | ||||||
|  |  | ||||||
| 	tokenData := secret.Data[api.ServiceAccountTokenKey] | 	tokenData := secret.Data[api.ServiceAccountTokenKey] | ||||||
| 	needsToken := len(tokenData) == 0 | 	needsToken := len(tokenData) == 0 | ||||||
|  |  | ||||||
| 	if !needsCA && !needsToken { | 	if !needsCA && !needsToken && !needsNamespace { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -375,6 +378,10 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *api.ServiceAcco | |||||||
| 	if needsCA { | 	if needsCA { | ||||||
| 		secret.Data[api.ServiceAccountRootCAKey] = e.rootCA | 		secret.Data[api.ServiceAccountRootCAKey] = e.rootCA | ||||||
| 	} | 	} | ||||||
|  | 	// Set the namespace | ||||||
|  | 	if needsNamespace { | ||||||
|  | 		secret.Data[api.ServiceAccountNamespaceKey] = []byte(secret.Namespace) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// Generate the token | 	// Generate the token | ||||||
| 	if needsToken { | 	if needsToken { | ||||||
|   | |||||||
| @@ -115,8 +115,9 @@ func createdTokenSecret() *api.Secret { | |||||||
| 		}, | 		}, | ||||||
| 		Type: api.SecretTypeServiceAccountToken, | 		Type: api.SecretTypeServiceAccountToken, | ||||||
| 		Data: map[string][]byte{ | 		Data: map[string][]byte{ | ||||||
| 			"token":  []byte("ABC"), | 			"token":     []byte("ABC"), | ||||||
| 			"ca.crt": []byte("CA Data"), | 			"ca.crt":    []byte("CA Data"), | ||||||
|  | 			"namespace": []byte("default"), | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -136,8 +137,9 @@ func serviceAccountTokenSecret() *api.Secret { | |||||||
| 		}, | 		}, | ||||||
| 		Type: api.SecretTypeServiceAccountToken, | 		Type: api.SecretTypeServiceAccountToken, | ||||||
| 		Data: map[string][]byte{ | 		Data: map[string][]byte{ | ||||||
| 			"token":  []byte("ABC"), | 			"token":     []byte("ABC"), | ||||||
| 			"ca.crt": []byte("CA Data"), | 			"ca.crt":    []byte("CA Data"), | ||||||
|  | 			"namespace": []byte("default"), | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -163,6 +165,20 @@ func serviceAccountTokenSecretWithCAData(data []byte) *api.Secret { | |||||||
| 	return secret | 	return secret | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // serviceAccountTokenSecretWithoutNamespaceData returns an existing ServiceAccountToken secret that lacks namespace data | ||||||
|  | func serviceAccountTokenSecretWithoutNamespaceData() *api.Secret { | ||||||
|  | 	secret := serviceAccountTokenSecret() | ||||||
|  | 	delete(secret.Data, api.ServiceAccountNamespaceKey) | ||||||
|  | 	return secret | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // serviceAccountTokenSecretWithNamespaceData returns an existing ServiceAccountToken secret with the specified namespace data | ||||||
|  | func serviceAccountTokenSecretWithNamespaceData(data []byte) *api.Secret { | ||||||
|  | 	secret := serviceAccountTokenSecret() | ||||||
|  | 	secret.Data[api.ServiceAccountNamespaceKey] = data | ||||||
|  | 	return secret | ||||||
|  | } | ||||||
|  |  | ||||||
| func TestTokenCreation(t *testing.T) { | func TestTokenCreation(t *testing.T) { | ||||||
| 	testcases := map[string]struct { | 	testcases := map[string]struct { | ||||||
| 		ClientObjects []runtime.Object | 		ClientObjects []runtime.Object | ||||||
| @@ -379,6 +395,24 @@ func TestTokenCreation(t *testing.T) { | |||||||
| 				core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), | 				core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | 		"added token secret without namespace data": { | ||||||
|  | 			ClientObjects:          []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, | ||||||
|  | 			ExistingServiceAccount: serviceAccount(tokenSecretReferences()), | ||||||
|  |  | ||||||
|  | 			AddedSecret: serviceAccountTokenSecretWithoutNamespaceData(), | ||||||
|  | 			ExpectedActions: []core.Action{ | ||||||
|  | 				core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		"added token secret with custom namespace data": { | ||||||
|  | 			ClientObjects:          []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, | ||||||
|  | 			ExistingServiceAccount: serviceAccount(tokenSecretReferences()), | ||||||
|  |  | ||||||
|  | 			AddedSecret:     serviceAccountTokenSecretWithNamespaceData([]byte("custom")), | ||||||
|  | 			ExpectedActions: []core.Action{ | ||||||
|  | 			// no update is performed... the custom namespace is preserved | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		"updated secret without serviceaccount": { | 		"updated secret without serviceaccount": { | ||||||
| 			ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, | 			ClientObjects: []runtime.Object{serviceAccountTokenSecret()}, | ||||||
| @@ -422,6 +456,24 @@ func TestTokenCreation(t *testing.T) { | |||||||
| 				core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), | 				core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
|  | 		"updated token secret without namespace data": { | ||||||
|  | 			ClientObjects:          []runtime.Object{serviceAccountTokenSecretWithoutNamespaceData()}, | ||||||
|  | 			ExistingServiceAccount: serviceAccount(tokenSecretReferences()), | ||||||
|  |  | ||||||
|  | 			UpdatedSecret: serviceAccountTokenSecretWithoutNamespaceData(), | ||||||
|  | 			ExpectedActions: []core.Action{ | ||||||
|  | 				core.NewUpdateAction("secrets", api.NamespaceDefault, serviceAccountTokenSecret()), | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		"updated token secret with custom namespace data": { | ||||||
|  | 			ClientObjects:          []runtime.Object{serviceAccountTokenSecretWithNamespaceData([]byte("custom"))}, | ||||||
|  | 			ExistingServiceAccount: serviceAccount(tokenSecretReferences()), | ||||||
|  |  | ||||||
|  | 			UpdatedSecret:   serviceAccountTokenSecretWithNamespaceData([]byte("custom")), | ||||||
|  | 			ExpectedActions: []core.Action{ | ||||||
|  | 			// no update is performed... the custom namespace is preserved | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		"deleted secret without serviceaccount": { | 		"deleted secret without serviceaccount": { | ||||||
| 			DeletedSecret:   serviceAccountTokenSecret(), | 			DeletedSecret:   serviceAccountTokenSecret(), | ||||||
|   | |||||||
| @@ -24,11 +24,14 @@ import ( | |||||||
| 	apierrors "k8s.io/kubernetes/pkg/api/errors" | 	apierrors "k8s.io/kubernetes/pkg/api/errors" | ||||||
| 	"k8s.io/kubernetes/pkg/util" | 	"k8s.io/kubernetes/pkg/util" | ||||||
| 	"k8s.io/kubernetes/pkg/util/wait" | 	"k8s.io/kubernetes/pkg/util/wait" | ||||||
|  | 	"k8s.io/kubernetes/pkg/version" | ||||||
| 	"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" | 	"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount" | ||||||
|  |  | ||||||
| 	. "github.com/onsi/ginkgo" | 	. "github.com/onsi/ginkgo" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | var serviceAccountTokenNamespaceVersion = version.MustParse("v1.2.0") | ||||||
|  |  | ||||||
| var _ = Describe("ServiceAccounts", func() { | var _ = Describe("ServiceAccounts", func() { | ||||||
| 	f := NewFramework("svcaccounts") | 	f := NewFramework("svcaccounts") | ||||||
|  |  | ||||||
| @@ -94,11 +97,28 @@ var _ = Describe("ServiceAccounts", func() { | |||||||
| 			}, | 			}, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		supportsTokenNamespace, _ := serverVersionGTE(serviceAccountTokenNamespaceVersion, f.Client) | ||||||
|  | 		if supportsTokenNamespace { | ||||||
|  | 			pod.Spec.Containers = append(pod.Spec.Containers, api.Container{ | ||||||
|  | 				Name:  "namespace-test", | ||||||
|  | 				Image: "gcr.io/google_containers/mounttest:0.2", | ||||||
|  | 				Args: []string{ | ||||||
|  | 					fmt.Sprintf("--file_content=%s/%s", serviceaccount.DefaultAPITokenMountPath, api.ServiceAccountNamespaceKey), | ||||||
|  | 				}, | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		f.TestContainerOutput("consume service account token", pod, 0, []string{ | 		f.TestContainerOutput("consume service account token", pod, 0, []string{ | ||||||
| 			fmt.Sprintf(`content of file "%s/%s": %s`, serviceaccount.DefaultAPITokenMountPath, api.ServiceAccountTokenKey, tokenContent), | 			fmt.Sprintf(`content of file "%s/%s": %s`, serviceaccount.DefaultAPITokenMountPath, api.ServiceAccountTokenKey, tokenContent), | ||||||
| 		}) | 		}) | ||||||
| 		f.TestContainerOutput("consume service account root CA", pod, 1, []string{ | 		f.TestContainerOutput("consume service account root CA", pod, 1, []string{ | ||||||
| 			fmt.Sprintf(`content of file "%s/%s": %s`, serviceaccount.DefaultAPITokenMountPath, api.ServiceAccountRootCAKey, rootCAContent), | 			fmt.Sprintf(`content of file "%s/%s": %s`, serviceaccount.DefaultAPITokenMountPath, api.ServiceAccountRootCAKey, rootCAContent), | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
|  | 		if supportsTokenNamespace { | ||||||
|  | 			f.TestContainerOutput("consume service account namespace", pod, 2, []string{ | ||||||
|  | 				fmt.Sprintf(`content of file "%s/%s": %s`, serviceaccount.DefaultAPITokenMountPath, api.ServiceAccountNamespaceKey, f.Namespace.Name), | ||||||
|  | 			}) | ||||||
|  | 		} | ||||||
| 	}) | 	}) | ||||||
| }) | }) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 k8s-merge-robot
					k8s-merge-robot