mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-14 21:53:52 +00:00
Add automountServiceAccountToken field to PodSpec and ServiceAccount types
This commit is contained in:
@@ -1887,6 +1887,9 @@ type PodSpec struct {
|
||||
// ServiceAccountName is the name of the ServiceAccount to use to run this pod
|
||||
// The pod will be allowed to use secrets referenced by the ServiceAccount
|
||||
ServiceAccountName string
|
||||
// AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.
|
||||
// +optional
|
||||
AutomountServiceAccountToken *bool
|
||||
|
||||
// NodeName is a request to schedule this pod onto a specific node. If it is non-empty,
|
||||
// the scheduler simply schedules this pod onto that node, assuming that it fits resource
|
||||
@@ -2425,6 +2428,11 @@ type ServiceAccount struct {
|
||||
// can be mounted in the pod, but ImagePullSecrets are only accessed by the kubelet.
|
||||
// +optional
|
||||
ImagePullSecrets []LocalObjectReference
|
||||
|
||||
// AutomountServiceAccountToken indicates whether pods running as this service account should have an API token automatically mounted.
|
||||
// Can be overridden at the pod level.
|
||||
// +optional
|
||||
AutomountServiceAccountToken *bool
|
||||
}
|
||||
|
||||
// ServiceAccountList is a list of ServiceAccount objects
|
||||
|
@@ -2160,6 +2160,9 @@ type PodSpec struct {
|
||||
// +k8s:conversion-gen=false
|
||||
// +optional
|
||||
DeprecatedServiceAccount string `json:"serviceAccount,omitempty" protobuf:"bytes,9,opt,name=serviceAccount"`
|
||||
// AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.
|
||||
// +optional
|
||||
AutomountServiceAccountToken *bool `json:"automountServiceAccountToken,omitempty" protobuf:"varint,21,opt,name=automountServiceAccountToken"`
|
||||
|
||||
// NodeName is a request to schedule this pod onto a specific node. If it is non-empty,
|
||||
// the scheduler simply schedules this pod onto that node, assuming that it fits resource
|
||||
@@ -2801,6 +2804,11 @@ type ServiceAccount struct {
|
||||
// More info: http://kubernetes.io/docs/user-guide/secrets#manually-specifying-an-imagepullsecret
|
||||
// +optional
|
||||
ImagePullSecrets []LocalObjectReference `json:"imagePullSecrets,omitempty" protobuf:"bytes,3,rep,name=imagePullSecrets"`
|
||||
|
||||
// AutomountServiceAccountToken indicates whether pods running as this service account should have an API token automatically mounted.
|
||||
// Can be overridden at the pod level.
|
||||
// +optional
|
||||
AutomountServiceAccountToken *bool `json:"automountServiceAccountToken,omitempty" protobuf:"varint,4,opt,name=automountServiceAccountToken"`
|
||||
}
|
||||
|
||||
// ServiceAccountList is a list of ServiceAccount objects
|
||||
|
@@ -222,7 +222,7 @@ func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if s.MountServiceAccountToken {
|
||||
if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) {
|
||||
if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil {
|
||||
if _, ok := err.(errors.APIStatus); ok {
|
||||
return err
|
||||
@@ -239,6 +239,19 @@ func (s *serviceAccount) Admit(a admission.Attributes) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@@ -35,6 +35,8 @@ import (
|
||||
|
||||
var serviceAccountTokenNamespaceVersion = utilversion.MustParseSemantic("v1.2.0")
|
||||
|
||||
var serviceAccountTokenAutomountVersion = utilversion.MustParseSemantic("v1.6.0-alpha.2")
|
||||
|
||||
var _ = framework.KubeDescribe("ServiceAccounts", func() {
|
||||
f := framework.NewDefaultFramework("svcaccounts")
|
||||
|
||||
@@ -239,4 +241,145 @@ var _ = framework.KubeDescribe("ServiceAccounts", func() {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
It("should allow opting out of API token automount [Conformance]", func() {
|
||||
framework.SkipUnlessServerVersionGTE(serviceAccountTokenAutomountVersion, f.ClientSet.Discovery())
|
||||
|
||||
var err error
|
||||
trueValue := true
|
||||
falseValue := false
|
||||
mountSA := &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "mount"}, AutomountServiceAccountToken: &trueValue}
|
||||
nomountSA := &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: "nomount"}, AutomountServiceAccountToken: &falseValue}
|
||||
mountSA, err = f.ClientSet.Core().ServiceAccounts(f.Namespace.Name).Create(mountSA)
|
||||
framework.ExpectNoError(err)
|
||||
nomountSA, err = f.ClientSet.Core().ServiceAccounts(f.Namespace.Name).Create(nomountSA)
|
||||
framework.ExpectNoError(err)
|
||||
|
||||
// Standard get, update retry loop
|
||||
framework.ExpectNoError(wait.Poll(time.Millisecond*500, framework.ServiceAccountProvisionTimeout, func() (bool, error) {
|
||||
By("getting the auto-created API token")
|
||||
sa, err := f.ClientSet.Core().ServiceAccounts(f.Namespace.Name).Get(mountSA.Name, metav1.GetOptions{})
|
||||
if apierrors.IsNotFound(err) {
|
||||
framework.Logf("mount service account was not found")
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
framework.Logf("error getting mount service account: %v", err)
|
||||
return false, err
|
||||
}
|
||||
if len(sa.Secrets) == 0 {
|
||||
framework.Logf("mount service account has no secret references")
|
||||
return false, nil
|
||||
}
|
||||
for _, secretRef := range sa.Secrets {
|
||||
secret, err := f.ClientSet.Core().Secrets(f.Namespace.Name).Get(secretRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
framework.Logf("Error getting secret %s: %v", secretRef.Name, err)
|
||||
continue
|
||||
}
|
||||
if secret.Type == v1.SecretTypeServiceAccountToken {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
framework.Logf("default service account has no secret references to valid service account tokens")
|
||||
return false, nil
|
||||
}))
|
||||
|
||||
testcases := []struct {
|
||||
PodName string
|
||||
ServiceAccountName string
|
||||
AutomountPodSpec *bool
|
||||
ExpectTokenVolume bool
|
||||
}{
|
||||
{
|
||||
PodName: "pod-service-account-defaultsa",
|
||||
ServiceAccountName: "default",
|
||||
AutomountPodSpec: nil,
|
||||
ExpectTokenVolume: true, // default is true
|
||||
},
|
||||
{
|
||||
PodName: "pod-service-account-mountsa",
|
||||
ServiceAccountName: mountSA.Name,
|
||||
AutomountPodSpec: nil,
|
||||
ExpectTokenVolume: true,
|
||||
},
|
||||
{
|
||||
PodName: "pod-service-account-nomountsa",
|
||||
ServiceAccountName: nomountSA.Name,
|
||||
AutomountPodSpec: nil,
|
||||
ExpectTokenVolume: false,
|
||||
},
|
||||
|
||||
// Make sure pod spec trumps when opting in
|
||||
{
|
||||
PodName: "pod-service-account-defaultsa-mountspec",
|
||||
ServiceAccountName: "default",
|
||||
AutomountPodSpec: &trueValue,
|
||||
ExpectTokenVolume: true,
|
||||
},
|
||||
{
|
||||
PodName: "pod-service-account-mountsa-mountspec",
|
||||
ServiceAccountName: mountSA.Name,
|
||||
AutomountPodSpec: &trueValue,
|
||||
ExpectTokenVolume: true,
|
||||
},
|
||||
{
|
||||
PodName: "pod-service-account-nomountsa-mountspec",
|
||||
ServiceAccountName: nomountSA.Name,
|
||||
AutomountPodSpec: &trueValue,
|
||||
ExpectTokenVolume: true, // pod spec trumps
|
||||
},
|
||||
|
||||
// Make sure pod spec trumps when opting out
|
||||
{
|
||||
PodName: "pod-service-account-defaultsa-nomountspec",
|
||||
ServiceAccountName: "default",
|
||||
AutomountPodSpec: &falseValue,
|
||||
ExpectTokenVolume: false, // pod spec trumps
|
||||
},
|
||||
{
|
||||
PodName: "pod-service-account-mountsa-nomountspec",
|
||||
ServiceAccountName: mountSA.Name,
|
||||
AutomountPodSpec: &falseValue,
|
||||
ExpectTokenVolume: false, // pod spec trumps
|
||||
},
|
||||
{
|
||||
PodName: "pod-service-account-nomountsa-nomountspec",
|
||||
ServiceAccountName: nomountSA.Name,
|
||||
AutomountPodSpec: &falseValue,
|
||||
ExpectTokenVolume: false, // pod spec trumps
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: tc.PodName},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{Name: "token-test", Image: "gcr.io/google_containers/mounttest:0.7"}},
|
||||
RestartPolicy: v1.RestartPolicyNever,
|
||||
ServiceAccountName: tc.ServiceAccountName,
|
||||
AutomountServiceAccountToken: tc.AutomountPodSpec,
|
||||
},
|
||||
}
|
||||
createdPod, err := f.ClientSet.Core().Pods(f.Namespace.Name).Create(pod)
|
||||
framework.ExpectNoError(err)
|
||||
framework.Logf("created pod %s", tc.PodName)
|
||||
|
||||
hasServiceAccountTokenVolume := false
|
||||
for _, c := range createdPod.Spec.Containers {
|
||||
for _, vm := range c.VolumeMounts {
|
||||
if vm.MountPath == serviceaccount.DefaultAPITokenMountPath {
|
||||
hasServiceAccountTokenVolume = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hasServiceAccountTokenVolume != tc.ExpectTokenVolume {
|
||||
framework.Failf("%s: expected volume=%v, got %v (%#v)", tc.PodName, tc.ExpectTokenVolume, hasServiceAccountTokenVolume, createdPod)
|
||||
} else {
|
||||
framework.Logf("pod %s service account token volume mount: %v", tc.PodName, hasServiceAccountTokenVolume)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user