vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

View File

@ -0,0 +1,66 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"admission.go",
"doc.go",
],
importpath = "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount",
deps = [
"//pkg/api/pod:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//pkg/kubeapiserver/admission/util:go_default_library",
"//pkg/serviceaccount:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/storage/names:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["admission_test.go"],
importpath = "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount",
library = ":go_default_library",
deps = [
"//pkg/apis/core:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,7 @@
approvers:
- liggitt
- deads2k
reviewers:
- liggitt
- deads2k
- enj

View File

@ -0,0 +1,492 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package serviceaccount
import (
"fmt"
"io"
"math/rand"
"strconv"
"time"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/storage/names"
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
"k8s.io/kubernetes/pkg/kubeapiserver/admission/util"
"k8s.io/kubernetes/pkg/serviceaccount"
)
// DefaultServiceAccountName is the name of the default service account to set on pods which do not specify a service account
const DefaultServiceAccountName = "default"
// 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
const EnforceMountableSecretsAnnotation = "kubernetes.io/enforce-mountable-secrets"
// DefaultAPITokenMountPath is the path that ServiceAccountToken secrets are automounted to.
// The token file would then be accessible at /var/run/secrets/kubernetes.io/serviceaccount
const DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
// PluginName is the name of this admission plugin
const PluginName = "ServiceAccount"
// Register registers a plugin
func Register(plugins *admission.Plugins) {
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
serviceAccountAdmission := NewServiceAccount()
return serviceAccountAdmission, nil
})
}
var _ = admission.Interface(&serviceAccount{})
type serviceAccount struct {
*admission.Handler
// LimitSecretReferences rejects pods that reference secrets their service accounts do not reference
LimitSecretReferences bool
// RequireAPIToken determines whether pod creation attempts are rejected if no API token exists for the pod's service account
RequireAPIToken bool
// MountServiceAccountToken creates Volume and VolumeMounts for the first referenced ServiceAccountToken for the pod's service account
MountServiceAccountToken bool
client internalclientset.Interface
serviceAccountLister corelisters.ServiceAccountLister
secretLister corelisters.SecretLister
}
var _ admission.MutationInterface = &serviceAccount{}
var _ admission.ValidationInterface = &serviceAccount{}
var _ = kubeapiserveradmission.WantsInternalKubeClientSet(&serviceAccount{})
var _ = kubeapiserveradmission.WantsInternalKubeInformerFactory(&serviceAccount{})
// NewServiceAccount returns an admission.Interface implementation which limits admission of Pod CREATE requests based on the pod's ServiceAccount:
// 1. If the pod does not specify a ServiceAccount, it sets the pod's ServiceAccount to "default"
// 2. It ensures the ServiceAccount referenced by the pod exists
// 3. If LimitSecretReferences is true, it rejects the pod if the pod references Secret objects which the pod's ServiceAccount does not reference
// 4. If the pod does not contain any ImagePullSecrets, the ImagePullSecrets of the service account are added.
// 5. If MountServiceAccountToken is true, it adds a VolumeMount with the pod's ServiceAccount's api token secret to containers
func NewServiceAccount() *serviceAccount {
return &serviceAccount{
Handler: admission.NewHandler(admission.Create, admission.Update),
// TODO: enable this once we've swept secret usage to account for adding secret references to service accounts
LimitSecretReferences: false,
// Auto mount service account API token secrets
MountServiceAccountToken: true,
// Reject pod creation until a service account token is available
RequireAPIToken: true,
}
}
func (a *serviceAccount) SetInternalKubeClientSet(cl internalclientset.Interface) {
a.client = cl
}
func (a *serviceAccount) SetInternalKubeInformerFactory(f informers.SharedInformerFactory) {
serviceAccountInformer := f.Core().InternalVersion().ServiceAccounts()
a.serviceAccountLister = serviceAccountInformer.Lister()
secretInformer := f.Core().InternalVersion().Secrets()
a.secretLister = secretInformer.Lister()
a.SetReadyFunc(func() bool {
return serviceAccountInformer.Informer().HasSynced() && secretInformer.Informer().HasSynced()
})
}
// ValidateInitialization ensures an authorizer is set.
func (a *serviceAccount) ValidateInitialization() error {
if a.client == nil {
return fmt.Errorf("missing client")
}
if a.secretLister == nil {
return fmt.Errorf("missing secretLister")
}
if a.serviceAccountLister == nil {
return fmt.Errorf("missing serviceAccountLister")
}
return nil
}
func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
if shouldIgnore(a) {
return nil
}
updateInitialized, err := util.IsUpdatingInitializedObject(a)
if err != nil {
return err
}
if updateInitialized {
// related pod spec fields are immutable after the pod is initialized
return nil
}
pod := a.GetObject().(*api.Pod)
// Don't modify the spec of mirror pods.
// That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match)
// That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
return s.Validate(a)
}
// Set the default service account if needed
if len(pod.Spec.ServiceAccountName) == 0 {
pod.Spec.ServiceAccountName = DefaultServiceAccountName
}
serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
if err != nil {
return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
}
if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) {
if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil {
if _, ok := err.(errors.APIStatus); ok {
return err
}
return admission.NewForbidden(a, err)
}
}
if len(pod.Spec.ImagePullSecrets) == 0 {
pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets))
copy(pod.Spec.ImagePullSecrets, serviceAccount.ImagePullSecrets)
}
return s.Validate(a)
}
func (s *serviceAccount) Validate(a admission.Attributes) (err error) {
if shouldIgnore(a) {
return nil
}
updateInitialized, err := util.IsUpdatingInitializedObject(a)
if err != nil {
return err
}
if updateInitialized {
// related pod spec fields are immutable after the pod is initialized
return nil
}
pod := a.GetObject().(*api.Pod)
// Mirror pods have restrictions on what they can reference
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
if len(pod.Spec.ServiceAccountName) != 0 {
return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts"))
}
hasSecrets := false
podutil.VisitPodSecretNames(pod, func(name string) bool {
hasSecrets = true
return false
})
if hasSecrets {
return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference secrets"))
}
return nil
}
// Ensure the referenced service account exists
serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
if err != nil {
return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
}
if s.enforceMountableSecrets(serviceAccount) {
if err := s.limitSecretReferences(serviceAccount, pod); err != nil {
return admission.NewForbidden(a, err)
}
}
return nil
}
func shouldIgnore(a admission.Attributes) bool {
if a.GetResource().GroupResource() != api.Resource("pods") {
return true
}
obj := a.GetObject()
if obj == nil {
return true
}
_, ok := obj.(*api.Pod)
if !ok {
return true
}
return false
}
func shouldAutomount(sa *api.ServiceAccount, pod *api.Pod) bool {
// Pod's preference wins
if pod.Spec.AutomountServiceAccountToken != nil {
return *pod.Spec.AutomountServiceAccountToken
}
// Then service account's
if sa.AutomountServiceAccountToken != nil {
return *sa.AutomountServiceAccountToken
}
// Default to true for backwards compatibility
return true
}
// enforceMountableSecrets indicates whether mountable secrets should be enforced for a particular service account
// A global setting of true will override any flag set on the individual service account
func (s *serviceAccount) enforceMountableSecrets(serviceAccount *api.ServiceAccount) bool {
if s.LimitSecretReferences {
return true
}
if value, ok := serviceAccount.Annotations[EnforceMountableSecretsAnnotation]; ok {
enforceMountableSecretCheck, _ := strconv.ParseBool(value)
return enforceMountableSecretCheck
}
return false
}
// getServiceAccount returns the ServiceAccount for the given namespace and name if it exists
func (s *serviceAccount) getServiceAccount(namespace string, name string) (*api.ServiceAccount, error) {
serviceAccount, err := s.serviceAccountLister.ServiceAccounts(namespace).Get(name)
if err == nil {
return serviceAccount, nil
}
if !errors.IsNotFound(err) {
return nil, err
}
// Could not find in cache, attempt to look up directly
numAttempts := 1
if name == DefaultServiceAccountName {
// If this is the default serviceaccount, attempt more times, since it should be auto-created by the controller
numAttempts = 10
}
retryInterval := time.Duration(rand.Int63n(100)+int64(100)) * time.Millisecond
for i := 0; i < numAttempts; i++ {
if i != 0 {
time.Sleep(retryInterval)
}
serviceAccount, err := s.client.Core().ServiceAccounts(namespace).Get(name, metav1.GetOptions{})
if err == nil {
return serviceAccount, nil
}
if !errors.IsNotFound(err) {
return nil, err
}
}
return nil, errors.NewNotFound(api.Resource("serviceaccount"), name)
}
// getReferencedServiceAccountToken returns the name of the first referenced secret which is a ServiceAccountToken for the service account
func (s *serviceAccount) getReferencedServiceAccountToken(serviceAccount *api.ServiceAccount) (string, error) {
if len(serviceAccount.Secrets) == 0 {
return "", nil
}
tokens, err := s.getServiceAccountTokens(serviceAccount)
if err != nil {
return "", err
}
accountTokens := sets.NewString()
for _, token := range tokens {
accountTokens.Insert(token.Name)
}
// Prefer secrets in the order they're referenced.
for _, secret := range serviceAccount.Secrets {
if accountTokens.Has(secret.Name) {
return secret.Name, nil
}
}
return "", nil
}
// getServiceAccountTokens returns all ServiceAccountToken secrets for the given ServiceAccount
func (s *serviceAccount) getServiceAccountTokens(serviceAccount *api.ServiceAccount) ([]*api.Secret, error) {
secrets, err := s.secretLister.Secrets(serviceAccount.Namespace).List(labels.Everything())
if err != nil {
return nil, err
}
tokens := []*api.Secret{}
for _, secret := range secrets {
if secret.Type != api.SecretTypeServiceAccountToken {
continue
}
if serviceaccount.InternalIsServiceAccountToken(secret, serviceAccount) {
tokens = append(tokens, secret)
}
}
return tokens, nil
}
func (s *serviceAccount) limitSecretReferences(serviceAccount *api.ServiceAccount, pod *api.Pod) error {
// Ensure all secrets the pod references are allowed by the service account
mountableSecrets := sets.NewString()
for _, s := range serviceAccount.Secrets {
mountableSecrets.Insert(s.Name)
}
for _, volume := range pod.Spec.Volumes {
source := volume.VolumeSource
if source.Secret == nil {
continue
}
secretName := source.Secret.SecretName
if !mountableSecrets.Has(secretName) {
return fmt.Errorf("volume with secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", secretName, serviceAccount.Name)
}
}
for _, container := range pod.Spec.InitContainers {
for _, env := range container.Env {
if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
return fmt.Errorf("init container %s with envVar %s referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
}
}
}
}
for _, container := range pod.Spec.Containers {
for _, env := range container.Env {
if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
return fmt.Errorf("container %s with envVar %s referencing secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", container.Name, env.Name, env.ValueFrom.SecretKeyRef.Name, serviceAccount.Name)
}
}
}
}
// limit pull secret references as well
pullSecrets := sets.NewString()
for _, s := range serviceAccount.ImagePullSecrets {
pullSecrets.Insert(s.Name)
}
for i, pullSecretRef := range pod.Spec.ImagePullSecrets {
if !pullSecrets.Has(pullSecretRef.Name) {
return fmt.Errorf(`imagePullSecrets[%d].name="%s" is not allowed because service account %s does not reference that imagePullSecret`, i, pullSecretRef.Name, serviceAccount.Name)
}
}
return nil
}
func (s *serviceAccount) mountServiceAccountToken(serviceAccount *api.ServiceAccount, pod *api.Pod) error {
// Find the name of a referenced ServiceAccountToken secret we can mount
serviceAccountToken, err := s.getReferencedServiceAccountToken(serviceAccount)
if err != nil {
return fmt.Errorf("Error looking up service account token for %s/%s: %v", serviceAccount.Namespace, serviceAccount.Name, err)
}
if len(serviceAccountToken) == 0 {
// We don't have an API token to mount, so return
if s.RequireAPIToken {
// If a token is required, this is considered an error
err := errors.NewServerTimeout(schema.GroupResource{Resource: "serviceaccounts"}, "create pod", 1)
err.ErrStatus.Message = fmt.Sprintf("No API token found for service account %q, retry after the token is automatically created and added to the service account", serviceAccount.Name)
return err
}
return nil
}
// Find the volume and volume name for the ServiceAccountTokenSecret if it already exists
tokenVolumeName := ""
hasTokenVolume := false
allVolumeNames := sets.NewString()
for _, volume := range pod.Spec.Volumes {
allVolumeNames.Insert(volume.Name)
if volume.Secret != nil && volume.Secret.SecretName == serviceAccountToken {
tokenVolumeName = volume.Name
hasTokenVolume = true
break
}
}
// Determine a volume name for the ServiceAccountTokenSecret in case we need it
if len(tokenVolumeName) == 0 {
// Try naming the volume the same as the serviceAccountToken, and uniquify if needed
tokenVolumeName = serviceAccountToken
if allVolumeNames.Has(tokenVolumeName) {
tokenVolumeName = names.SimpleNameGenerator.GenerateName(fmt.Sprintf("%s-", serviceAccountToken))
}
}
// Create the prototypical VolumeMount
volumeMount := api.VolumeMount{
Name: tokenVolumeName,
ReadOnly: true,
MountPath: DefaultAPITokenMountPath,
}
// Ensure every container mounts the APISecret volume
needsTokenVolume := false
for i, container := range pod.Spec.InitContainers {
existingContainerMount := false
for _, volumeMount := range container.VolumeMounts {
// Existing mounts at the default mount path prevent mounting of the API token
if volumeMount.MountPath == DefaultAPITokenMountPath {
existingContainerMount = true
break
}
}
if !existingContainerMount {
pod.Spec.InitContainers[i].VolumeMounts = append(pod.Spec.InitContainers[i].VolumeMounts, volumeMount)
needsTokenVolume = true
}
}
for i, container := range pod.Spec.Containers {
existingContainerMount := false
for _, volumeMount := range container.VolumeMounts {
// Existing mounts at the default mount path prevent mounting of the API token
if volumeMount.MountPath == DefaultAPITokenMountPath {
existingContainerMount = true
break
}
}
if !existingContainerMount {
pod.Spec.Containers[i].VolumeMounts = append(pod.Spec.Containers[i].VolumeMounts, volumeMount)
needsTokenVolume = true
}
}
// Add the volume if a container needs it
if !hasTokenVolume && needsTokenVolume {
volume := api.Volume{
Name: tokenVolumeName,
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{
SecretName: serviceAccountToken,
},
},
}
pod.Spec.Volumes = append(pod.Spec.Volumes, volume)
}
return nil
}

View File

@ -0,0 +1,966 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package serviceaccount
import (
"reflect"
"strings"
"testing"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/admission"
"k8s.io/client-go/tools/cache"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
corelisters "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
"k8s.io/kubernetes/pkg/controller"
kubelet "k8s.io/kubernetes/pkg/kubelet/types"
)
func TestIgnoresNonCreate(t *testing.T) {
for _, op := range []admission.Operation{admission.Delete, admission.Connect} {
handler := NewServiceAccount()
if handler.Handles(op) {
t.Errorf("Expected not to handle operation %s", op)
}
}
}
func TestIgnoresUpdateOfInitializedPod(t *testing.T) {
pod := &api.Pod{}
oldPod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, oldPod, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, nil)
handler := NewServiceAccount()
err := handler.Admit(attrs)
if err != nil {
t.Errorf("Expected update of initialized pod allowed, got err: %v", err)
}
}
func TestIgnoresNonPodResource(t *testing.T) {
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("CustomResource").WithVersion("version"), "", admission.Create, nil)
err := NewServiceAccount().Admit(attrs)
if err != nil {
t.Errorf("Expected non-pod resource allowed, got err: %v", err)
}
}
func TestIgnoresNilObject(t *testing.T) {
attrs := admission.NewAttributesRecord(nil, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := NewServiceAccount().Admit(attrs)
if err != nil {
t.Errorf("Expected nil object allowed allowed, got err: %v", err)
}
}
func TestIgnoresNonPodObject(t *testing.T) {
obj := &api.Namespace{}
attrs := admission.NewAttributesRecord(obj, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := NewServiceAccount().Admit(attrs)
if err != nil {
t.Errorf("Expected non pod object allowed, got err: %v", err)
}
}
func TestIgnoresMirrorPod(t *testing.T) {
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
kubelet.ConfigMirrorAnnotationKey: "true",
},
},
Spec: api.PodSpec{
Volumes: []api.Volume{
{VolumeSource: api.VolumeSource{}},
},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := NewServiceAccount().Admit(attrs)
if err != nil {
t.Errorf("Expected mirror pod without service account or secrets allowed, got err: %v", err)
}
}
func TestRejectsMirrorPodWithServiceAccount(t *testing.T) {
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
kubelet.ConfigMirrorAnnotationKey: "true",
},
},
Spec: api.PodSpec{
ServiceAccountName: "default",
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := NewServiceAccount().Admit(attrs)
if err == nil {
t.Errorf("Expected a mirror pod to be prevented from referencing a service account")
}
}
func TestRejectsMirrorPodWithSecretVolumes(t *testing.T) {
pod := &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Annotations: map[string]string{
kubelet.ConfigMirrorAnnotationKey: "true",
},
},
Spec: api.PodSpec{
Volumes: []api.Volume{
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{}}},
},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), "myns", "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := NewServiceAccount().Admit(attrs)
if err == nil {
t.Errorf("Expected a mirror pod to be prevented from referencing a secret volume")
}
}
func TestAssignsDefaultServiceAccountAndToleratesMissingAPIToken(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.MountServiceAccountToken = true
admit.RequireAPIToken = false
// Add the default service account for the ns into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
})
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, 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)
}
}
func TestAssignsDefaultServiceAccountAndRejectsMissingAPIToken(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.MountServiceAccountToken = true
admit.RequireAPIToken = true
// Add the default service account for the ns into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
})
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := admit.Admit(attrs)
if err == nil || !errors.IsServerTimeout(err) {
t.Errorf("Expected server timeout error for missing API token: %v", err)
}
}
func TestFetchesUncachedServiceAccount(t *testing.T) {
ns := "myns"
// Build a test client that the admission plugin can use to look up the service account missing from its cache
client := fake.NewSimpleClientset(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
})
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.client = client
admit.RequireAPIToken = false
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, 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)
}
}
func TestDeniesInvalidServiceAccount(t *testing.T) {
ns := "myns"
// Build a test client that the admission plugin can use to look up the service account missing from its cache
client := fake.NewSimpleClientset()
admit := NewServiceAccount()
admit.SetInternalKubeClientSet(client)
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := admit.Admit(attrs)
if err == nil {
t.Errorf("Expected error for missing service account, got none")
}
}
func TestAutomountsAPIToken(t *testing.T) {
ns := "myns"
tokenName := "token-name"
serviceAccountName := DefaultServiceAccountName
serviceAccountUID := "12345"
expectedVolume := api.Volume{
Name: tokenName,
VolumeSource: api.VolumeSource{
Secret: &api.SecretVolumeSource{SecretName: tokenName},
},
}
expectedVolumeMount := api.VolumeMount{
Name: tokenName,
ReadOnly: true,
MountPath: DefaultAPITokenMountPath,
}
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.MountServiceAccountToken = true
admit.RequireAPIToken = true
// Add the default service account for the ns with a token into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName,
Namespace: ns,
UID: types.UID(serviceAccountUID),
},
Secrets: []api.ObjectReference{
{Name: tokenName},
},
})
// Add a token for the service account into the cache
informerFactory.Core().InternalVersion().Secrets().Informer().GetStore().Add(&api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: tokenName,
Namespace: ns,
Annotations: map[string]string{
api.ServiceAccountNameKey: serviceAccountName,
api.ServiceAccountUIDKey: serviceAccountUID,
},
},
Type: api.SecretTypeServiceAccountToken,
Data: map[string][]byte{
api.ServiceAccountTokenKey: []byte("token-data"),
},
})
pod := &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{},
},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, 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)
}
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])
}
// Test ServiceAccount admission plugin applies the same changes if the
// operation is an update to an uninitialized pod.
oldPod := &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
// the volumeMount in the oldPod shouldn't affect the result.
VolumeMounts: []api.VolumeMount{
{
Name: "wrong-" + tokenName,
ReadOnly: true,
MountPath: DefaultAPITokenMountPath,
},
},
},
},
},
}
// oldPod is not intialized.
oldPod.Initializers = &metav1.Initializers{Pending: []metav1.Initializer{{Name: "init"}}}
pod = &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{},
},
},
}
attrs = admission.NewAttributesRecord(pod, oldPod, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Update, 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)
}
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])
}
// testing InitContainers
pod = &api.Pod{
Spec: api.PodSpec{
InitContainers: []api.Container{
{},
},
},
}
attrs = admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); 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)
}
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.InitContainers[0].VolumeMounts) != 1 {
t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.InitContainers[0].VolumeMounts))
}
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])
}
}
func TestRespectsExistingMount(t *testing.T) {
ns := "myns"
tokenName := "token-name"
serviceAccountName := DefaultServiceAccountName
serviceAccountUID := "12345"
expectedVolumeMount := api.VolumeMount{
Name: "my-custom-mount",
ReadOnly: false,
MountPath: DefaultAPITokenMountPath,
}
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.MountServiceAccountToken = true
admit.RequireAPIToken = true
// Add the default service account for the ns with a token into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName,
Namespace: ns,
UID: types.UID(serviceAccountUID),
},
Secrets: []api.ObjectReference{
{Name: tokenName},
},
})
// Add a token for the service account into the cache
informerFactory.Core().InternalVersion().Secrets().Informer().GetStore().Add(&api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: tokenName,
Namespace: ns,
Annotations: map[string]string{
api.ServiceAccountNameKey: serviceAccountName,
api.ServiceAccountUIDKey: serviceAccountUID,
},
},
Type: api.SecretTypeServiceAccountToken,
Data: map[string][]byte{
api.ServiceAccountTokenKey: []byte("token-data"),
},
})
// Define a pod with a container that already mounts a volume at the API token path
// Admission should respect that
// Additionally, no volume should be created if no container is going to use it
pod := &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
VolumeMounts: []api.VolumeMount{
expectedVolumeMount,
},
},
},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, 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)
}
if len(pod.Spec.Volumes) != 0 {
t.Fatalf("Expected 0 volumes (shouldn't create a volume for a secret we don't need), got %d", len(pod.Spec.Volumes))
}
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])
}
// check init containers
pod = &api.Pod{
Spec: api.PodSpec{
InitContainers: []api.Container{
{
VolumeMounts: []api.VolumeMount{
expectedVolumeMount,
},
},
},
},
}
attrs = admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); 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)
}
if len(pod.Spec.Volumes) != 0 {
t.Fatalf("Expected 0 volumes (shouldn't create a volume for a secret we don't need), got %d", len(pod.Spec.Volumes))
}
if len(pod.Spec.InitContainers[0].VolumeMounts) != 1 {
t.Fatalf("Expected 1 volume mount, got %d", len(pod.Spec.InitContainers[0].VolumeMounts))
}
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])
}
}
func TestAllowsReferencedSecret(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.LimitSecretReferences = true
admit.RequireAPIToken = false
// Add the default service account for the ns with a secret reference into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
Secrets: []api.ObjectReference{
{Name: "foo"},
},
})
pod1 := &api.Pod{
Spec: api.PodSpec{
Volumes: []api.Volume{
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}},
},
},
}
attrs := admission.NewAttributesRecord(pod1, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); err != nil {
t.Errorf("Unexpected error: %v", err)
}
pod2 := &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "container-1",
Env: []api.EnvVar{
{
Name: "env-1",
ValueFrom: &api.EnvVarSource{
SecretKeyRef: &api.SecretKeySelector{
LocalObjectReference: api.LocalObjectReference{Name: "foo"},
},
},
},
},
},
},
},
}
attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); err != nil {
t.Errorf("Unexpected error: %v", err)
}
pod2 = &api.Pod{
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Name: "container-1",
Env: []api.EnvVar{
{
Name: "env-1",
ValueFrom: &api.EnvVarSource{
SecretKeyRef: &api.SecretKeySelector{
LocalObjectReference: api.LocalObjectReference{Name: "foo"},
},
},
},
},
},
},
},
}
attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestRejectsUnreferencedSecretVolumes(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.LimitSecretReferences = true
admit.RequireAPIToken = false
// Add the default service account for the ns into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
})
pod1 := &api.Pod{
Spec: api.PodSpec{
Volumes: []api.Volume{
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}},
},
},
}
attrs := admission.NewAttributesRecord(pod1, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); err == nil {
t.Errorf("Expected rejection for using a secret the service account does not reference")
}
pod2 := &api.Pod{
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "container-1",
Env: []api.EnvVar{
{
Name: "env-1",
ValueFrom: &api.EnvVarSource{
SecretKeyRef: &api.SecretKeySelector{
LocalObjectReference: api.LocalObjectReference{Name: "foo"},
},
},
},
},
},
},
},
}
attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); err == nil || !strings.Contains(err.Error(), "with envVar") {
t.Errorf("Unexpected error: %v", err)
}
pod2 = &api.Pod{
Spec: api.PodSpec{
InitContainers: []api.Container{
{
Name: "container-1",
Env: []api.EnvVar{
{
Name: "env-1",
ValueFrom: &api.EnvVarSource{
SecretKeyRef: &api.SecretKeySelector{
LocalObjectReference: api.LocalObjectReference{Name: "foo"},
},
},
},
},
},
},
},
}
attrs = admission.NewAttributesRecord(pod2, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); err == nil || !strings.Contains(err.Error(), "with envVar") {
t.Errorf("Unexpected error: %v", err)
}
}
func TestAllowUnreferencedSecretVolumesForPermissiveSAs(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.LimitSecretReferences = false
admit.RequireAPIToken = false
// Add the default service account for the ns into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
Annotations: map[string]string{EnforceMountableSecretsAnnotation: "true"},
},
})
pod := &api.Pod{
Spec: api.PodSpec{
Volumes: []api.Volume{
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "foo"}}},
},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := admit.Admit(attrs)
if err == nil {
t.Errorf("Expected rejection for using a secret the service account does not reference")
}
}
func TestAllowsReferencedImagePullSecrets(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.LimitSecretReferences = true
admit.RequireAPIToken = false
// Add the default service account for the ns with a secret reference into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
ImagePullSecrets: []api.LocalObjectReference{
{Name: "foo"},
},
})
pod := &api.Pod{
Spec: api.PodSpec{
ImagePullSecrets: []api.LocalObjectReference{{Name: "foo"}},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := admit.Admit(attrs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
}
func TestRejectsUnreferencedImagePullSecrets(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.LimitSecretReferences = true
admit.RequireAPIToken = false
// Add the default service account for the ns into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
})
pod := &api.Pod{
Spec: api.PodSpec{
ImagePullSecrets: []api.LocalObjectReference{{Name: "foo"}},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := admit.Admit(attrs)
if err == nil {
t.Errorf("Expected rejection for using a secret the service account does not reference")
}
}
func TestDoNotAddImagePullSecrets(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.LimitSecretReferences = true
admit.RequireAPIToken = false
// Add the default service account for the ns with a secret reference into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(&api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
ImagePullSecrets: []api.LocalObjectReference{
{Name: "foo"},
{Name: "bar"},
},
})
pod := &api.Pod{
Spec: api.PodSpec{
ImagePullSecrets: []api.LocalObjectReference{{Name: "foo"}},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := admit.Admit(attrs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(pod.Spec.ImagePullSecrets) != 1 || pod.Spec.ImagePullSecrets[0].Name != "foo" {
t.Errorf("unexpected image pull secrets: %v", pod.Spec.ImagePullSecrets)
}
}
func TestAddImagePullSecrets(t *testing.T) {
ns := "myns"
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.LimitSecretReferences = true
admit.RequireAPIToken = false
sa := &api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
},
ImagePullSecrets: []api.LocalObjectReference{
{Name: "foo"},
{Name: "bar"},
},
}
// Add the default service account for the ns with a secret reference into the cache
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(sa)
pod := &api.Pod{}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
err := admit.Admit(attrs)
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if len(pod.Spec.ImagePullSecrets) != 2 || !reflect.DeepEqual(sa.ImagePullSecrets, pod.Spec.ImagePullSecrets) {
t.Errorf("expected %v, got %v", sa.ImagePullSecrets, pod.Spec.ImagePullSecrets)
}
pod.Spec.ImagePullSecrets[1] = api.LocalObjectReference{Name: "baz"}
if reflect.DeepEqual(sa.ImagePullSecrets, pod.Spec.ImagePullSecrets) {
t.Errorf("accidentally mutated the ServiceAccount.ImagePullSecrets: %v", sa.ImagePullSecrets)
}
}
func TestMultipleReferencedSecrets(t *testing.T) {
var (
ns = "myns"
serviceAccountName = "mysa"
serviceAccountUID = "mysauid"
token1 = "token1"
token2 = "token2"
)
admit := NewServiceAccount()
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
admit.SetInternalKubeInformerFactory(informerFactory)
admit.MountServiceAccountToken = true
admit.RequireAPIToken = true
sa := &api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: serviceAccountName,
UID: types.UID(serviceAccountUID),
Namespace: ns,
},
Secrets: []api.ObjectReference{
{Name: token1},
{Name: token2},
},
}
informerFactory.Core().InternalVersion().ServiceAccounts().Informer().GetStore().Add(sa)
// Add two tokens for the service account into the cache.
informerFactory.Core().InternalVersion().Secrets().Informer().GetStore().Add(&api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: token2,
Namespace: ns,
Annotations: map[string]string{
api.ServiceAccountNameKey: serviceAccountName,
api.ServiceAccountUIDKey: serviceAccountUID,
},
},
Type: api.SecretTypeServiceAccountToken,
Data: map[string][]byte{
api.ServiceAccountTokenKey: []byte("token-data"),
},
})
informerFactory.Core().InternalVersion().Secrets().Informer().GetStore().Add(&api.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: token1,
Namespace: ns,
Annotations: map[string]string{
api.ServiceAccountNameKey: serviceAccountName,
api.ServiceAccountUIDKey: serviceAccountUID,
},
},
Type: api.SecretTypeServiceAccountToken,
Data: map[string][]byte{
api.ServiceAccountTokenKey: []byte("token-data"),
},
})
pod := &api.Pod{
Spec: api.PodSpec{
ServiceAccountName: serviceAccountName,
Containers: []api.Container{
{Name: "container-1"},
},
},
}
attrs := admission.NewAttributesRecord(pod, nil, api.Kind("Pod").WithVersion("version"), ns, "myname", api.Resource("pods").WithVersion("version"), "", admission.Create, nil)
if err := admit.Admit(attrs); err != nil {
t.Fatal(err)
}
if n := len(pod.Spec.Volumes); n != 1 {
t.Fatalf("expected 1 volume mount, got %d", n)
}
if name := pod.Spec.Volumes[0].Name; name != token1 {
t.Errorf("expected first referenced secret to be mounted, got %q", name)
}
}
func newSecret(secretType api.SecretType, namespace, name, serviceAccountName, serviceAccountUID string) *api.Secret {
return &api.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: name,
Annotations: map[string]string{
api.ServiceAccountNameKey: serviceAccountName,
api.ServiceAccountUIDKey: serviceAccountUID,
},
},
Type: secretType,
}
}
func TestGetServiceAccountTokens(t *testing.T) {
admit := NewServiceAccount()
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
admit.secretLister = corelisters.NewSecretLister(indexer)
ns := "namespace"
serviceAccountUID := "12345"
sa := &api.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: DefaultServiceAccountName,
Namespace: ns,
UID: types.UID(serviceAccountUID),
},
}
nonSATokenSecret := newSecret(api.SecretTypeDockercfg, ns, "nonSATokenSecret", DefaultServiceAccountName, serviceAccountUID)
indexer.Add(nonSATokenSecret)
differentSAToken := newSecret(api.SecretTypeServiceAccountToken, ns, "differentSAToken", "someOtherSA", "someOtherUID")
indexer.Add(differentSAToken)
matchingSAToken := newSecret(api.SecretTypeServiceAccountToken, ns, "matchingSAToken", DefaultServiceAccountName, serviceAccountUID)
indexer.Add(matchingSAToken)
tokens, err := admit.getServiceAccountTokens(sa)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(tokens) != 1 {
names := make([]string, 0, len(tokens))
for _, token := range tokens {
names = append(names, token.Name)
}
t.Fatalf("expected only 1 token, got %v", names)
}
if e, a := matchingSAToken.Name, tokens[0].Name; e != a {
t.Errorf("expected token %s, got %s", e, a)
}
}

View File

@ -0,0 +1,19 @@
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// serviceaccount enforces all pods having an associated serviceaccount,
// and all containers mounting the API token for that serviceaccount at a known location
package serviceaccount // import "k8s.io/kubernetes/plugin/pkg/admission/serviceaccount"