Provide current namespace to InClusterConfig

pull/6/head
Jordan Liggitt 2016-02-11 14:46:56 -05:00
parent f0061c7105
commit 20216fa607
6 changed files with 100 additions and 8 deletions

View File

@ -2218,6 +2218,8 @@ const (
ServiceAccountKubeconfigKey = "kubernetes.kubeconfig"
// ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets
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
//

View File

@ -2685,6 +2685,8 @@ const (
ServiceAccountKubeconfigKey = "kubernetes.kubeconfig"
// ServiceAccountRootCAKey is the key of the optional root certificate authority for SecretTypeServiceAccountToken secrets
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
//

View File

@ -19,8 +19,10 @@ package clientcmd
import (
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"strings"
"github.com/golang/glog"
"github.com/imdario/mergo"
@ -325,12 +327,19 @@ func (inClusterClientConfig) ClientConfig() (*client.Config, 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 using the downward API.
// This check has to be done first for backwards compatibility with the way InClusterConfig was originally set up
if ns := os.Getenv("POD_NAMESPACE"); ns != "" {
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
}

View File

@ -325,6 +325,7 @@ func (e *TokensController) createSecret(serviceAccount *api.ServiceAccount) erro
return err
}
secret.Data[api.ServiceAccountTokenKey] = []byte(token)
secret.Data[api.ServiceAccountNamespaceKey] = []byte(serviceAccount.Namespace)
if e.rootCA != nil && len(e.rootCA) > 0 {
secret.Data[api.ServiceAccountRootCAKey] = e.rootCA
}
@ -364,10 +365,12 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *api.ServiceAcco
caData := secret.Data[api.ServiceAccountRootCAKey]
needsCA := len(e.rootCA) > 0 && bytes.Compare(caData, e.rootCA) != 0
needsNamespace := len(secret.Data[api.ServiceAccountNamespaceKey]) == 0
tokenData := secret.Data[api.ServiceAccountTokenKey]
needsToken := len(tokenData) == 0
if !needsCA && !needsToken {
if !needsCA && !needsToken && !needsNamespace {
return nil
}
@ -375,6 +378,10 @@ func (e *TokensController) generateTokenIfNeeded(serviceAccount *api.ServiceAcco
if needsCA {
secret.Data[api.ServiceAccountRootCAKey] = e.rootCA
}
// Set the namespace
if needsNamespace {
secret.Data[api.ServiceAccountNamespaceKey] = []byte(secret.Namespace)
}
// Generate the token
if needsToken {

View File

@ -115,8 +115,9 @@ func createdTokenSecret() *api.Secret {
},
Type: api.SecretTypeServiceAccountToken,
Data: map[string][]byte{
"token": []byte("ABC"),
"ca.crt": []byte("CA Data"),
"token": []byte("ABC"),
"ca.crt": []byte("CA Data"),
"namespace": []byte("default"),
},
}
}
@ -136,8 +137,9 @@ func serviceAccountTokenSecret() *api.Secret {
},
Type: api.SecretTypeServiceAccountToken,
Data: map[string][]byte{
"token": []byte("ABC"),
"ca.crt": []byte("CA Data"),
"token": []byte("ABC"),
"ca.crt": []byte("CA Data"),
"namespace": []byte("default"),
},
}
}
@ -163,6 +165,20 @@ func serviceAccountTokenSecretWithCAData(data []byte) *api.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) {
testcases := map[string]struct {
ClientObjects []runtime.Object
@ -379,6 +395,24 @@ func TestTokenCreation(t *testing.T) {
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": {
ClientObjects: []runtime.Object{serviceAccountTokenSecret()},
@ -422,6 +456,24 @@ func TestTokenCreation(t *testing.T) {
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": {
DeletedSecret: serviceAccountTokenSecret(),

View File

@ -24,11 +24,14 @@ import (
apierrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/wait"
"k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/plugin/pkg/admission/serviceaccount"
. "github.com/onsi/ginkgo"
)
var serviceAccountTokenNamespaceVersion = version.MustParse("v1.2.0")
var _ = Describe("ServiceAccounts", func() {
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{
fmt.Sprintf(`content of file "%s/%s": %s`, serviceaccount.DefaultAPITokenMountPath, api.ServiceAccountTokenKey, tokenContent),
})
f.TestContainerOutput("consume service account root CA", pod, 1, []string{
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),
})
}
})
})