2019-01-12 04:58:27 +00:00
/ *
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 (
2019-09-27 21:51:53 +00:00
"context"
2019-01-12 04:58:27 +00:00
"fmt"
"io"
"math/rand"
"strconv"
2019-08-30 18:33:25 +00:00
"strings"
2019-01-12 04:58:27 +00:00
"time"
corev1 "k8s.io/api/core/v1"
"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"
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
corev1listers "k8s.io/client-go/listers/core/v1"
2019-08-30 18:33:25 +00:00
"k8s.io/component-base/featuregate"
2019-01-12 04:58:27 +00:00
podutil "k8s.io/kubernetes/pkg/api/pod"
api "k8s.io/kubernetes/pkg/apis/core"
2019-12-12 01:27:03 +00:00
"k8s.io/kubernetes/pkg/features"
2019-01-12 04:58:27 +00:00
"k8s.io/kubernetes/pkg/serviceaccount"
)
const (
// DefaultServiceAccountName is the name of the default service account to set on pods which do not specify a service account
DefaultServiceAccountName = "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
EnforceMountableSecretsAnnotation = "kubernetes.io/enforce-mountable-secrets"
2019-08-30 18:33:25 +00:00
// ServiceAccountVolumeName is the prefix name that will be added to volumes that mount ServiceAccount secrets
2019-01-12 04:58:27 +00:00
ServiceAccountVolumeName = "kube-api-access"
// DefaultAPITokenMountPath is the path that ServiceAccountToken secrets are automounted to.
// The token file would then be accessible at /var/run/secrets/kubernetes.io/serviceaccount
DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
// PluginName is the name of this admission plugin
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
} )
}
2019-08-30 18:33:25 +00:00
var _ = admission . Interface ( & Plugin { } )
2019-01-12 04:58:27 +00:00
2019-08-30 18:33:25 +00:00
// Plugin contains the client used by the admission controller
type Plugin struct {
2019-01-12 04:58:27 +00:00
* 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 kubernetes . Interface
serviceAccountLister corev1listers . ServiceAccountLister
secretLister corev1listers . SecretLister
generateName func ( string ) string
2019-12-12 01:27:03 +00:00
boundServiceAccountTokenVolume bool
2019-01-12 04:58:27 +00:00
}
2019-08-30 18:33:25 +00:00
var _ admission . MutationInterface = & Plugin { }
var _ admission . ValidationInterface = & Plugin { }
2019-12-12 01:27:03 +00:00
var _ genericadmissioninitializer . WantsFeatures = & Plugin { }
2019-08-30 18:33:25 +00:00
var _ = genericadmissioninitializer . WantsExternalKubeClientSet ( & Plugin { } )
var _ = genericadmissioninitializer . WantsExternalKubeInformerFactory ( & Plugin { } )
2019-01-12 04:58:27 +00:00
// 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
2019-08-30 18:33:25 +00:00
func NewServiceAccount ( ) * Plugin {
return & Plugin {
2019-04-07 17:07:55 +00:00
Handler : admission . NewHandler ( admission . Create ) ,
2019-01-12 04:58:27 +00:00
// 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 ,
generateName : names . SimpleNameGenerator . GenerateName ,
}
}
2019-12-12 01:27:03 +00:00
// InspectFeatureGates allows setting bools without taking a dep on a global variable
func ( s * Plugin ) InspectFeatureGates ( featureGates featuregate . FeatureGate ) {
s . boundServiceAccountTokenVolume = featureGates . Enabled ( features . BoundServiceAccountTokenVolume )
}
2019-08-30 18:33:25 +00:00
// SetExternalKubeClientSet sets the client for the plugin
func ( s * Plugin ) SetExternalKubeClientSet ( cl kubernetes . Interface ) {
s . client = cl
2019-01-12 04:58:27 +00:00
}
2019-08-30 18:33:25 +00:00
// SetExternalKubeInformerFactory registers informers with the plugin
func ( s * Plugin ) SetExternalKubeInformerFactory ( f informers . SharedInformerFactory ) {
2019-01-12 04:58:27 +00:00
serviceAccountInformer := f . Core ( ) . V1 ( ) . ServiceAccounts ( )
2019-08-30 18:33:25 +00:00
s . serviceAccountLister = serviceAccountInformer . Lister ( )
2019-01-12 04:58:27 +00:00
secretInformer := f . Core ( ) . V1 ( ) . Secrets ( )
2019-08-30 18:33:25 +00:00
s . secretLister = secretInformer . Lister ( )
2019-01-12 04:58:27 +00:00
2019-08-30 18:33:25 +00:00
s . SetReadyFunc ( func ( ) bool {
2019-01-12 04:58:27 +00:00
return serviceAccountInformer . Informer ( ) . HasSynced ( ) && secretInformer . Informer ( ) . HasSynced ( )
} )
}
// ValidateInitialization ensures an authorizer is set.
2019-08-30 18:33:25 +00:00
func ( s * Plugin ) ValidateInitialization ( ) error {
if s . client == nil {
2019-01-12 04:58:27 +00:00
return fmt . Errorf ( "missing client" )
}
2019-08-30 18:33:25 +00:00
if s . secretLister == nil {
2019-01-12 04:58:27 +00:00
return fmt . Errorf ( "missing secretLister" )
}
2019-08-30 18:33:25 +00:00
if s . serviceAccountLister == nil {
2019-01-12 04:58:27 +00:00
return fmt . Errorf ( "missing serviceAccountLister" )
}
return nil
}
2019-08-30 18:33:25 +00:00
// Admit verifies if the pod should be admitted
2019-09-27 21:51:53 +00:00
func ( s * Plugin ) Admit ( ctx context . Context , a admission . Attributes , o admission . ObjectInterfaces ) ( err error ) {
2019-01-12 04:58:27 +00:00
if shouldIgnore ( a ) {
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 {
2019-09-27 21:51:53 +00:00
return s . Validate ( ctx , a , o )
2019-01-12 04:58:27 +00:00
}
// 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 ) )
for i := 0 ; i < len ( serviceAccount . ImagePullSecrets ) ; i ++ {
pod . Spec . ImagePullSecrets [ i ] . Name = serviceAccount . ImagePullSecrets [ i ] . Name
}
}
2019-09-27 21:51:53 +00:00
return s . Validate ( ctx , a , o )
2019-01-12 04:58:27 +00:00
}
2019-08-30 18:33:25 +00:00
// Validate the data we obtained
2019-09-27 21:51:53 +00:00
func ( s * Plugin ) Validate ( ctx context . Context , a admission . Attributes , o admission . ObjectInterfaces ) ( err error ) {
2019-01-12 04:58:27 +00:00
if shouldIgnore ( a ) {
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
2020-08-10 17:43:49 +00:00
} , podutil . AllContainers )
2019-01-12 04:58:27 +00:00
if hasSecrets {
return admission . NewForbidden ( a , fmt . Errorf ( "a mirror pod may not reference secrets" ) )
}
for _ , v := range pod . Spec . Volumes {
if proj := v . Projected ; proj != nil {
for _ , projSource := range proj . Sources {
if projSource . ServiceAccountToken != nil {
return admission . NewForbidden ( a , fmt . Errorf ( "a mirror pod may not use ServiceAccountToken volume projections" ) )
}
}
}
}
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
}
2019-04-07 17:07:55 +00:00
if a . GetSubresource ( ) != "" {
return true
}
2019-01-12 04:58:27 +00:00
obj := a . GetObject ( )
if obj == nil {
return true
}
_ , ok := obj . ( * api . Pod )
if ! ok {
return true
}
return false
}
func shouldAutomount ( sa * corev1 . 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
2019-08-30 18:33:25 +00:00
func ( s * Plugin ) enforceMountableSecrets ( serviceAccount * corev1 . ServiceAccount ) bool {
2019-01-12 04:58:27 +00:00
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
2019-08-30 18:33:25 +00:00
func ( s * Plugin ) getServiceAccount ( namespace string , name string ) ( * corev1 . ServiceAccount , error ) {
2019-01-12 04:58:27 +00:00
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 )
}
2020-03-26 21:07:15 +00:00
serviceAccount , err := s . client . CoreV1 ( ) . ServiceAccounts ( namespace ) . Get ( context . TODO ( ) , name , metav1 . GetOptions { } )
2019-01-12 04:58:27 +00:00
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
2019-08-30 18:33:25 +00:00
func ( s * Plugin ) getReferencedServiceAccountToken ( serviceAccount * corev1 . ServiceAccount ) ( string , error ) {
2019-01-12 04:58:27 +00:00
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
2019-08-30 18:33:25 +00:00
func ( s * Plugin ) getServiceAccountTokens ( serviceAccount * corev1 . ServiceAccount ) ( [ ] * corev1 . Secret , error ) {
2019-01-12 04:58:27 +00:00
secrets , err := s . secretLister . Secrets ( serviceAccount . Namespace ) . List ( labels . Everything ( ) )
if err != nil {
return nil , err
}
tokens := [ ] * corev1 . Secret { }
for _ , secret := range secrets {
if secret . Type != corev1 . SecretTypeServiceAccountToken {
continue
}
if serviceaccount . IsServiceAccountToken ( secret , serviceAccount ) {
tokens = append ( tokens , secret )
}
}
return tokens , nil
}
2019-08-30 18:33:25 +00:00
func ( s * Plugin ) limitSecretReferences ( serviceAccount * corev1 . ServiceAccount , pod * api . Pod ) error {
2019-01-12 04:58:27 +00:00
// 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
}
2019-08-30 18:33:25 +00:00
func ( s * Plugin ) mountServiceAccountToken ( serviceAccount * corev1 . ServiceAccount , pod * api . Pod ) error {
2020-03-26 21:07:15 +00:00
var (
// serviceAccountToken holds the name of a secret containing a legacy service account token
serviceAccountToken string
err error
)
if ! s . boundServiceAccountTokenVolume {
// 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 )
}
2019-01-12 04:58:27 +00:00
}
2020-03-26 21:07:15 +00:00
if len ( serviceAccountToken ) == 0 && ! s . boundServiceAccountTokenVolume {
2019-01-12 04:58:27 +00:00
// 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 := ""
2019-08-30 18:33:25 +00:00
hasTokenVolume := false
2019-01-12 04:58:27 +00:00
allVolumeNames := sets . NewString ( )
for _ , volume := range pod . Spec . Volumes {
allVolumeNames . Insert ( volume . Name )
2019-12-12 01:27:03 +00:00
if ( ! s . boundServiceAccountTokenVolume && volume . Secret != nil && volume . Secret . SecretName == serviceAccountToken ) ||
( s . boundServiceAccountTokenVolume && strings . HasPrefix ( volume . Name , ServiceAccountVolumeName + "-" ) ) {
2019-01-12 04:58:27 +00:00
tokenVolumeName = volume . Name
2019-08-30 18:33:25 +00:00
hasTokenVolume = true
2019-01-12 04:58:27 +00:00
break
}
}
// Determine a volume name for the ServiceAccountTokenSecret in case we need it
if len ( tokenVolumeName ) == 0 {
2019-12-12 01:27:03 +00:00
if s . boundServiceAccountTokenVolume {
2019-08-30 18:33:25 +00:00
tokenVolumeName = s . generateName ( ServiceAccountVolumeName + "-" )
} else {
// Try naming the volume the same as the serviceAccountToken, and uniquify if needed
2020-08-10 17:43:49 +00:00
// Replace dots because volumeMountName can't contain it
tokenVolumeName = strings . Replace ( serviceAccountToken , "." , "-" , - 1 )
2019-08-30 18:33:25 +00:00
if allVolumeNames . Has ( tokenVolumeName ) {
2020-08-10 17:43:49 +00:00
tokenVolumeName = s . generateName ( fmt . Sprintf ( "%s-" , tokenVolumeName ) )
2019-08-30 18:33:25 +00:00
}
2019-01-12 04:58:27 +00:00
}
}
// 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
2019-08-30 18:33:25 +00:00
if ! hasTokenVolume && needsTokenVolume {
2019-01-12 04:58:27 +00:00
pod . Spec . Volumes = append ( pod . Spec . Volumes , s . createVolume ( tokenVolumeName , serviceAccountToken ) )
}
return nil
}
2019-08-30 18:33:25 +00:00
func ( s * Plugin ) createVolume ( tokenVolumeName , secretName string ) api . Volume {
2019-12-12 01:27:03 +00:00
if s . boundServiceAccountTokenVolume {
2019-08-30 18:33:25 +00:00
return api . Volume {
Name : tokenVolumeName ,
VolumeSource : api . VolumeSource {
2020-08-10 17:43:49 +00:00
Projected : TokenVolumeSource ( ) ,
2019-08-30 18:33:25 +00:00
} ,
}
}
2019-01-12 04:58:27 +00:00
return api . Volume {
Name : tokenVolumeName ,
VolumeSource : api . VolumeSource {
Secret : & api . SecretVolumeSource {
SecretName : secretName ,
} ,
} ,
}
}
2020-08-10 17:43:49 +00:00
// TokenVolumeSource returns the projected volume source for service account token.
func TokenVolumeSource ( ) * api . ProjectedVolumeSource {
return & api . ProjectedVolumeSource {
Sources : [ ] api . VolumeProjection {
{
ServiceAccountToken : & api . ServiceAccountTokenProjection {
Path : "token" ,
ExpirationSeconds : serviceaccount . WarnOnlyBoundTokenExpirationSeconds ,
} ,
} ,
{
ConfigMap : & api . ConfigMapProjection {
LocalObjectReference : api . LocalObjectReference {
Name : "kube-root-ca.crt" ,
} ,
Items : [ ] api . KeyToPath {
{
Key : "ca.crt" ,
Path : "ca.crt" ,
} ,
} ,
} ,
} ,
{
DownwardAPI : & api . DownwardAPIProjection {
Items : [ ] api . DownwardAPIVolumeFile {
{
Path : "namespace" ,
FieldRef : & api . ObjectFieldSelector {
APIVersion : "v1" ,
FieldPath : "metadata.namespace" ,
} ,
} ,
} ,
} ,
} ,
} ,
}
}