Merge pull request #69848 from mikedanese/projadmission

migrate service account volume to a projected volume when BoundServiceAccountTokenVolumes are enabled
This commit is contained in:
k8s-ci-robot 2018-11-16 22:46:23 -08:00 committed by GitHub
commit ca696fef26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 551 additions and 296 deletions

View File

@ -17,6 +17,9 @@ spec:
scheduler.alpha.kubernetes.io/critical-pod: '' scheduler.alpha.kubernetes.io/critical-pod: ''
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
containers: containers:
- image: k8s.gcr.io/cluster-proportional-autoscaler-amd64:1.1.2-r2 - image: k8s.gcr.io/cluster-proportional-autoscaler-amd64:1.1.2-r2
name: autoscaler name: autoscaler

View File

@ -59,6 +59,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
containers: containers:
- image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1 - image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1
name: heapster name: heapster

View File

@ -59,6 +59,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
containers: containers:
- image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1 - image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1
name: heapster name: heapster

View File

@ -59,6 +59,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
containers: containers:
- image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1 - image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1
name: heapster name: heapster

View File

@ -46,6 +46,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
containers: containers:
- image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1 - image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1
name: heapster name: heapster

View File

@ -46,6 +46,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
containers: containers:
- image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1 - image: k8s.gcr.io/heapster-amd64:v1.6.0-beta.1
name: heapster name: heapster

View File

@ -80,6 +80,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
containers: containers:
- name: autoscaler - name: autoscaler
image: k8s.gcr.io/cluster-proportional-autoscaler-amd64:1.3.0 image: k8s.gcr.io/cluster-proportional-autoscaler-amd64:1.3.0

View File

@ -86,6 +86,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
tolerations: tolerations:
- key: "CriticalAddonsOnly" - key: "CriticalAddonsOnly"
operator: "Exists" operator: "Exists"

View File

@ -86,6 +86,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
tolerations: tolerations:
- key: "CriticalAddonsOnly" - key: "CriticalAddonsOnly"
operator: "Exists" operator: "Exists"

View File

@ -86,6 +86,9 @@ spec:
seccomp.security.alpha.kubernetes.io/pod: 'docker/default' seccomp.security.alpha.kubernetes.io/pod: 'docker/default'
spec: spec:
priorityClassName: system-cluster-critical priorityClassName: system-cluster-critical
securityContext:
supplementalGroups: [ 65534 ]
fsGroup: 65534
tolerations: tolerations:
- key: "CriticalAddonsOnly" - key: "CriticalAddonsOnly"
operator: "Exists" operator: "Exists"

View File

@ -19,6 +19,7 @@ spec:
volumes: volumes:
- 'hostPath' - 'hostPath'
- 'secret' - 'secret'
- 'projected'
# TODO: This only needs a hostPath to read /etc/ssl/certs, # TODO: This only needs a hostPath to read /etc/ssl/certs,
# but it should be able to just include these in the image. # but it should be able to just include these in the image.
allowedHostPaths: allowedHostPaths:

View File

@ -20,6 +20,7 @@ spec:
- 'configMap' - 'configMap'
- 'hostPath' - 'hostPath'
- 'secret' - 'secret'
- 'projected'
allowedHostPaths: allowedHostPaths:
- pathPrefix: /var/log - pathPrefix: /var/log
- pathPrefix: /var/lib/docker/containers - pathPrefix: /var/lib/docker/containers

View File

@ -15,6 +15,7 @@ spec:
volumes: volumes:
- 'nfs' - 'nfs'
- 'secret' # Required for service account credentials. - 'secret' # Required for service account credentials.
- 'projected'
hostNetwork: false hostNetwork: false
hostIPC: false hostIPC: false
hostPID: false hostPID: false

View File

@ -39,6 +39,7 @@ spec:
- 'emptyDir' - 'emptyDir'
- 'configMap' - 'configMap'
- 'secret' - 'secret'
- 'projected'
hostNetwork: false hostNetwork: false
hostIPC: false hostIPC: false
hostPID: false hostPID: false

View File

@ -16,6 +16,7 @@ go_library(
deps = [ deps = [
"//pkg/api/pod:go_default_library", "//pkg/api/pod:go_default_library",
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubeapiserver/admission/util:go_default_library", "//pkg/kubeapiserver/admission/util:go_default_library",
"//pkg/serviceaccount:go_default_library", "//pkg/serviceaccount:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
@ -27,6 +28,7 @@ go_library(
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library", "//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
@ -40,12 +42,14 @@ go_test(
deps = [ deps = [
"//pkg/apis/core:go_default_library", "//pkg/apis/core:go_default_library",
"//pkg/controller:go_default_library", "//pkg/controller:go_default_library",
"//pkg/features:go_default_library",
"//pkg/kubelet/types:go_default_library", "//pkg/kubelet/types:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library", "//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",

View File

@ -21,6 +21,7 @@ import (
"io" "io"
"math/rand" "math/rand"
"strconv" "strconv"
"strings"
"time" "time"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -32,28 +33,34 @@ import (
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer" genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
corev1listers "k8s.io/client-go/listers/core/v1" corev1listers "k8s.io/client-go/listers/core/v1"
podutil "k8s.io/kubernetes/pkg/api/pod" podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
kubefeatures "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util" "k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
"k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/serviceaccount"
) )
const (
// DefaultServiceAccountName is the name of the default service account to set on pods which do not specify a service account // DefaultServiceAccountName is the name of the default service account to set on pods which do not specify a service account
const DefaultServiceAccountName = "default" DefaultServiceAccountName = "default"
// EnforceMountableSecretsAnnotation is a default annotation that indicates that a service account should enforce mountable secrets. // EnforceMountableSecretsAnnotation is a default annotation that indicates that a service account should enforce mountable secrets.
// The value must be true to have this annotation take effect // The value must be true to have this annotation take effect
const EnforceMountableSecretsAnnotation = "kubernetes.io/enforce-mountable-secrets" EnforceMountableSecretsAnnotation = "kubernetes.io/enforce-mountable-secrets"
ServiceAccountVolumeName = "kube-api-access"
// DefaultAPITokenMountPath is the path that ServiceAccountToken secrets are automounted to. // DefaultAPITokenMountPath is the path that ServiceAccountToken secrets are automounted to.
// The token file would then be accessible at /var/run/secrets/kubernetes.io/serviceaccount // The token file would then be accessible at /var/run/secrets/kubernetes.io/serviceaccount
const DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount" DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
// PluginName is the name of this admission plugin // PluginName is the name of this admission plugin
const PluginName = "ServiceAccount" PluginName = "ServiceAccount"
)
// Register registers a plugin // Register registers a plugin
func Register(plugins *admission.Plugins) { func Register(plugins *admission.Plugins) {
@ -79,6 +86,10 @@ type serviceAccount struct {
serviceAccountLister corev1listers.ServiceAccountLister serviceAccountLister corev1listers.ServiceAccountLister
secretLister corev1listers.SecretLister secretLister corev1listers.SecretLister
generateName func(string) string
featureGate utilfeature.FeatureGate
} }
var _ admission.MutationInterface = &serviceAccount{} var _ admission.MutationInterface = &serviceAccount{}
@ -101,6 +112,10 @@ func NewServiceAccount() *serviceAccount {
MountServiceAccountToken: true, MountServiceAccountToken: true,
// Reject pod creation until a service account token is available // Reject pod creation until a service account token is available
RequireAPIToken: true, RequireAPIToken: true,
generateName: names.SimpleNameGenerator.GenerateName,
featureGate: utilfeature.DefaultFeatureGate,
} }
} }
@ -434,7 +449,8 @@ func (s *serviceAccount) mountServiceAccountToken(serviceAccount *corev1.Service
allVolumeNames := sets.NewString() allVolumeNames := sets.NewString()
for _, volume := range pod.Spec.Volumes { for _, volume := range pod.Spec.Volumes {
allVolumeNames.Insert(volume.Name) allVolumeNames.Insert(volume.Name)
if volume.Secret != nil && volume.Secret.SecretName == serviceAccountToken { if (!s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) && volume.Secret != nil && volume.Secret.SecretName == serviceAccountToken) ||
(s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) && strings.HasPrefix(volume.Name, ServiceAccountVolumeName+"-")) {
tokenVolumeName = volume.Name tokenVolumeName = volume.Name
hasTokenVolume = true hasTokenVolume = true
break break
@ -443,10 +459,14 @@ func (s *serviceAccount) mountServiceAccountToken(serviceAccount *corev1.Service
// Determine a volume name for the ServiceAccountTokenSecret in case we need it // Determine a volume name for the ServiceAccountTokenSecret in case we need it
if len(tokenVolumeName) == 0 { if len(tokenVolumeName) == 0 {
if s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) {
tokenVolumeName = s.generateName(ServiceAccountVolumeName + "-")
} else {
// Try naming the volume the same as the serviceAccountToken, and uniquify if needed // Try naming the volume the same as the serviceAccountToken, and uniquify if needed
tokenVolumeName = serviceAccountToken tokenVolumeName = serviceAccountToken
if allVolumeNames.Has(tokenVolumeName) { if allVolumeNames.Has(tokenVolumeName) {
tokenVolumeName = names.SimpleNameGenerator.GenerateName(fmt.Sprintf("%s-", serviceAccountToken)) tokenVolumeName = s.generateName(fmt.Sprintf("%s-", serviceAccountToken))
}
} }
} }
@ -490,15 +510,61 @@ func (s *serviceAccount) mountServiceAccountToken(serviceAccount *corev1.Service
// Add the volume if a container needs it // Add the volume if a container needs it
if !hasTokenVolume && needsTokenVolume { if !hasTokenVolume && needsTokenVolume {
volume := api.Volume{ pod.Spec.Volumes = append(pod.Spec.Volumes, s.createVolume(tokenVolumeName, serviceAccountToken))
Name: tokenVolumeName,
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: serviceAccountToken,
},
},
}
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
} }
return nil return nil
} }
func (s *serviceAccount) createVolume(tokenVolumeName, secretName string) api.Volume {
if s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) {
return api.Volume{
Name: tokenVolumeName,
VolumeSource: api.VolumeSource{
Projected: &api.ProjectedVolumeSource{
Sources: []api.VolumeProjection{
{
ServiceAccountToken: &api.ServiceAccountTokenProjection{
Path: "token",
ExpirationSeconds: 60 * 60,
},
},
{
ConfigMap: &api.ConfigMapProjection{
LocalObjectReference: api.LocalObjectReference{
Name: "kube-root-ca.crt",
},
Items: []api.KeyToPath{
{
Key: "ca.crt",
Path: "ca.crt",
},
},
},
},
{
DownwardAPI: &api.DownwardAPIProjection{
Items: []api.DownwardAPIVolumeFile{
{
Path: "namespace",
FieldRef: &api.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.namespace",
},
},
},
},
},
},
},
},
}
}
return api.Volume{
Name: tokenVolumeName,
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: secretName,
},
},
}
}

View File

@ -28,15 +28,31 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake" "k8s.io/client-go/kubernetes/fake"
corev1listers "k8s.io/client-go/listers/core/v1" corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
api "k8s.io/kubernetes/pkg/apis/core" api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/controller"
kubefeatures "k8s.io/kubernetes/pkg/features"
kubelet "k8s.io/kubernetes/pkg/kubelet/types" kubelet "k8s.io/kubernetes/pkg/kubelet/types"
) )
var (
deprecationDisabledFeature = utilfeature.NewFeatureGate()
deprecationEnabledFeature = utilfeature.NewFeatureGate()
)
func init() {
if err := deprecationDisabledFeature.Add(map[utilfeature.Feature]utilfeature.FeatureSpec{kubefeatures.BoundServiceAccountTokenVolume: {Default: false}}); err != nil {
panic(err)
}
if err := deprecationEnabledFeature.Add(map[utilfeature.Feature]utilfeature.FeatureSpec{kubefeatures.BoundServiceAccountTokenVolume: {Default: true}}); err != nil {
panic(err)
}
}
func TestIgnoresNonCreate(t *testing.T) { func TestIgnoresNonCreate(t *testing.T) {
for _, op := range []admission.Operation{admission.Delete, admission.Connect} { for _, op := range []admission.Operation{admission.Delete, admission.Connect} {
handler := NewServiceAccount() handler := NewServiceAccount()
@ -267,29 +283,30 @@ func TestDeniesInvalidServiceAccount(t *testing.T) {
} }
func TestAutomountsAPIToken(t *testing.T) { func TestAutomountsAPIToken(t *testing.T) {
testBoundServiceAccountTokenVolumePhases(t, func(t *testing.T, applyFeatures func(*serviceAccount) *serviceAccount) {
admit := applyFeatures(NewServiceAccount())
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetExternalKubeInformerFactory(informerFactory)
admit.generateName = testGenerateName
admit.MountServiceAccountToken = true
admit.RequireAPIToken = true
ns := "myns" ns := "myns"
tokenName := "token-name"
serviceAccountName := DefaultServiceAccountName serviceAccountName := DefaultServiceAccountName
serviceAccountUID := "12345" serviceAccountUID := "12345"
expectedVolume := api.Volume{ tokenName := "token-name"
Name: tokenName, if admit.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) {
VolumeSource: api.VolumeSource{ tokenName = generatedVolumeName
Secret: &api.SecretVolumeSource{SecretName: tokenName},
},
} }
expectedVolume := admit.createVolume(tokenName, tokenName)
expectedVolumeMount := api.VolumeMount{ expectedVolumeMount := api.VolumeMount{
Name: tokenName, Name: tokenName,
ReadOnly: true, ReadOnly: true,
MountPath: DefaultAPITokenMountPath, MountPath: DefaultAPITokenMountPath,
} }
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetExternalKubeInformerFactory(informerFactory)
admit.MountServiceAccountToken = true
admit.RequireAPIToken = true
// Add the default service account for the ns with a token into the cache // Add the default service account for the ns with a token into the cache
informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{ informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -420,9 +437,11 @@ func TestAutomountsAPIToken(t *testing.T) {
if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) { if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) {
t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0])
} }
})
} }
func TestRespectsExistingMount(t *testing.T) { func TestRespectsExistingMount(t *testing.T) {
testBoundServiceAccountTokenVolumePhases(t, func(t *testing.T, applyFeatures func(*serviceAccount) *serviceAccount) {
ns := "myns" ns := "myns"
tokenName := "token-name" tokenName := "token-name"
serviceAccountName := DefaultServiceAccountName serviceAccountName := DefaultServiceAccountName
@ -434,7 +453,7 @@ func TestRespectsExistingMount(t *testing.T) {
MountPath: DefaultAPITokenMountPath, MountPath: DefaultAPITokenMountPath,
} }
admit := NewServiceAccount() admit := applyFeatures(NewServiceAccount())
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc()) informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetExternalKubeInformerFactory(informerFactory) admit.SetExternalKubeInformerFactory(informerFactory)
admit.MountServiceAccountToken = true admit.MountServiceAccountToken = true
@ -527,6 +546,7 @@ func TestRespectsExistingMount(t *testing.T) {
if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) { if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) {
t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0]) t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.InitContainers[0].VolumeMounts[0])
} }
})
} }
func TestAllowsReferencedSecret(t *testing.T) { func TestAllowsReferencedSecret(t *testing.T) {
@ -950,7 +970,8 @@ func newSecret(secretType corev1.SecretType, namespace, name, serviceAccountName
} }
func TestGetServiceAccountTokens(t *testing.T) { func TestGetServiceAccountTokens(t *testing.T) {
admit := NewServiceAccount() testBoundServiceAccountTokenVolumePhases(t, func(t *testing.T, applyFeatures func(*serviceAccount) *serviceAccount) {
admit := applyFeatures(NewServiceAccount())
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
admit.secretLister = corev1listers.NewSecretLister(indexer) admit.secretLister = corev1listers.NewSecretLister(indexer)
@ -989,4 +1010,134 @@ func TestGetServiceAccountTokens(t *testing.T) {
if e, a := matchingSAToken.Name, tokens[0].Name; e != a { if e, a := matchingSAToken.Name, tokens[0].Name; e != a {
t.Errorf("expected token %s, got %s", e, a) t.Errorf("expected token %s, got %s", e, a)
} }
})
}
func TestAutomountIsBackwardsCompatible(t *testing.T) {
ns := "myns"
tokenName := "token-name"
serviceAccountName := DefaultServiceAccountName
serviceAccountUID := "12345"
defaultTokenName := "default-token-abc123"
expectedVolume := api.Volume{
Name: defaultTokenName,
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: defaultTokenName,
},
},
}
expectedVolumeMount := api.VolumeMount{
Name: defaultTokenName,
ReadOnly: true,
MountPath: DefaultAPITokenMountPath,
}
admit := NewServiceAccount()
admit.generateName = testGenerateName
admit.featureGate = deprecationEnabledFeature
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetExternalKubeInformerFactory(informerFactory)
admit.MountServiceAccountToken = true
admit.RequireAPIToken = true
// Add the default service account for the ns with a token into the cache
informerFactory.Core().V1().ServiceAccounts().Informer().GetStore().Add(&corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName,
Namespace: ns,
UID: types.UID(serviceAccountUID),
},
Secrets: []corev1.ObjectReference{
{Name: tokenName},
},
})
// Add a token for the service account into the cache
informerFactory.Core().V1().Secrets().Informer().GetStore().Add(&corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: tokenName,
Namespace: ns,
Annotations: map[string]string{
corev1.ServiceAccountNameKey: serviceAccountName,
corev1.ServiceAccountUIDKey: serviceAccountUID,
},
},
Type: corev1.SecretTypeServiceAccountToken,
Data: map[string][]byte{
api.ServiceAccountTokenKey: []byte("token-data"),
},
})
pod := &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "c-1",
VolumeMounts: []api.VolumeMount{
{
Name: defaultTokenName,
MountPath: DefaultAPITokenMountPath,
ReadOnly: true,
},
},
},
},
Volumes: []api.Volume{
{
Name: defaultTokenName,
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: defaultTokenName,
},
},
},
},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, false, nil)
err := admit.Admit(attrs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if pod.Spec.ServiceAccountName != DefaultServiceAccountName {
t.Errorf("Expected service account %s assigned, got %s", DefaultServiceAccountName, pod.Spec.ServiceAccountName)
}
_ = expectedVolume
_ = expectedVolumeMount
if len(pod.Spec.Volumes) != 1 {
t.Fatalf("Expected 1 volume, got %d", len(pod.Spec.Volumes))
}
if !reflect.DeepEqual(expectedVolume, pod.Spec.Volumes[0]) {
t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolume, pod.Spec.Volumes[0])
}
if len(pod.Spec.Containers[0].VolumeMounts) != 1 {
t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.Containers[0].VolumeMounts))
}
if !reflect.DeepEqual(expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0]) {
t.Fatalf("Expected\n\t%#v\ngot\n\t%#v", expectedVolumeMount, pod.Spec.Containers[0].VolumeMounts[0])
}
}
func testGenerateName(n string) string {
return n + "abc123"
}
var generatedVolumeName = testGenerateName(ServiceAccountVolumeName + "-")
func testBoundServiceAccountTokenVolumePhases(t *testing.T, f func(*testing.T, func(*serviceAccount) *serviceAccount)) {
t.Run("BoundServiceAccountTokenVolume disabled", func(t *testing.T) {
f(t, func(s *serviceAccount) *serviceAccount {
s.featureGate = deprecationDisabledFeature
return s
})
})
t.Run("BoundServiceAccountTokenVolume enabled", func(t *testing.T) {
f(t, func(s *serviceAccount) *serviceAccount {
s.featureGate = deprecationEnabledFeature
return s
})
})
} }