mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-14 10:53:34 +00:00
vendor files
This commit is contained in:
66
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/BUILD
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/BUILD
generated
vendored
Normal 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"],
|
||||
)
|
7
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/OWNERS
generated
vendored
Normal file
7
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/OWNERS
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
approvers:
|
||||
- liggitt
|
||||
- deads2k
|
||||
reviewers:
|
||||
- liggitt
|
||||
- deads2k
|
||||
- enj
|
492
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission.go
generated
vendored
Normal file
492
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission.go
generated
vendored
Normal 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
|
||||
}
|
966
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission_test.go
generated
vendored
Normal file
966
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/admission_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/kubernetes/plugin/pkg/admission/serviceaccount/doc.go
generated
vendored
Normal 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"
|
Reference in New Issue
Block a user