admission.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. /*
  2. Copyright 2014 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package serviceaccount
  14. import (
  15. "fmt"
  16. "io"
  17. "math/rand"
  18. "strconv"
  19. "strings"
  20. "time"
  21. corev1 "k8s.io/api/core/v1"
  22. "k8s.io/apimachinery/pkg/api/errors"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/labels"
  25. "k8s.io/apimachinery/pkg/runtime/schema"
  26. "k8s.io/apimachinery/pkg/util/sets"
  27. "k8s.io/apiserver/pkg/admission"
  28. genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
  29. "k8s.io/apiserver/pkg/storage/names"
  30. utilfeature "k8s.io/apiserver/pkg/util/feature"
  31. "k8s.io/client-go/informers"
  32. "k8s.io/client-go/kubernetes"
  33. corev1listers "k8s.io/client-go/listers/core/v1"
  34. "k8s.io/component-base/featuregate"
  35. podutil "k8s.io/kubernetes/pkg/api/pod"
  36. api "k8s.io/kubernetes/pkg/apis/core"
  37. kubefeatures "k8s.io/kubernetes/pkg/features"
  38. "k8s.io/kubernetes/pkg/serviceaccount"
  39. )
  40. const (
  41. // DefaultServiceAccountName is the name of the default service account to set on pods which do not specify a service account
  42. DefaultServiceAccountName = "default"
  43. // EnforceMountableSecretsAnnotation is a default annotation that indicates that a service account should enforce mountable secrets.
  44. // The value must be true to have this annotation take effect
  45. EnforceMountableSecretsAnnotation = "kubernetes.io/enforce-mountable-secrets"
  46. // ServiceAccountVolumeName is the prefix name that will be added to volumes that mount ServiceAccount secrets
  47. ServiceAccountVolumeName = "kube-api-access"
  48. // DefaultAPITokenMountPath is the path that ServiceAccountToken secrets are automounted to.
  49. // The token file would then be accessible at /var/run/secrets/kubernetes.io/serviceaccount
  50. DefaultAPITokenMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
  51. // PluginName is the name of this admission plugin
  52. PluginName = "ServiceAccount"
  53. )
  54. // Register registers a plugin
  55. func Register(plugins *admission.Plugins) {
  56. plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
  57. serviceAccountAdmission := NewServiceAccount()
  58. return serviceAccountAdmission, nil
  59. })
  60. }
  61. var _ = admission.Interface(&Plugin{})
  62. // Plugin contains the client used by the admission controller
  63. type Plugin struct {
  64. *admission.Handler
  65. // LimitSecretReferences rejects pods that reference secrets their service accounts do not reference
  66. LimitSecretReferences bool
  67. // RequireAPIToken determines whether pod creation attempts are rejected if no API token exists for the pod's service account
  68. RequireAPIToken bool
  69. // MountServiceAccountToken creates Volume and VolumeMounts for the first referenced ServiceAccountToken for the pod's service account
  70. MountServiceAccountToken bool
  71. client kubernetes.Interface
  72. serviceAccountLister corev1listers.ServiceAccountLister
  73. secretLister corev1listers.SecretLister
  74. generateName func(string) string
  75. featureGate featuregate.FeatureGate
  76. }
  77. var _ admission.MutationInterface = &Plugin{}
  78. var _ admission.ValidationInterface = &Plugin{}
  79. var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
  80. var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
  81. // NewServiceAccount returns an admission.Interface implementation which limits admission of Pod CREATE requests based on the pod's ServiceAccount:
  82. // 1. If the pod does not specify a ServiceAccount, it sets the pod's ServiceAccount to "default"
  83. // 2. It ensures the ServiceAccount referenced by the pod exists
  84. // 3. If LimitSecretReferences is true, it rejects the pod if the pod references Secret objects which the pod's ServiceAccount does not reference
  85. // 4. If the pod does not contain any ImagePullSecrets, the ImagePullSecrets of the service account are added.
  86. // 5. If MountServiceAccountToken is true, it adds a VolumeMount with the pod's ServiceAccount's api token secret to containers
  87. func NewServiceAccount() *Plugin {
  88. return &Plugin{
  89. Handler: admission.NewHandler(admission.Create),
  90. // TODO: enable this once we've swept secret usage to account for adding secret references to service accounts
  91. LimitSecretReferences: false,
  92. // Auto mount service account API token secrets
  93. MountServiceAccountToken: true,
  94. // Reject pod creation until a service account token is available
  95. RequireAPIToken: true,
  96. generateName: names.SimpleNameGenerator.GenerateName,
  97. featureGate: utilfeature.DefaultFeatureGate,
  98. }
  99. }
  100. // SetExternalKubeClientSet sets the client for the plugin
  101. func (s *Plugin) SetExternalKubeClientSet(cl kubernetes.Interface) {
  102. s.client = cl
  103. }
  104. // SetExternalKubeInformerFactory registers informers with the plugin
  105. func (s *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
  106. serviceAccountInformer := f.Core().V1().ServiceAccounts()
  107. s.serviceAccountLister = serviceAccountInformer.Lister()
  108. secretInformer := f.Core().V1().Secrets()
  109. s.secretLister = secretInformer.Lister()
  110. s.SetReadyFunc(func() bool {
  111. return serviceAccountInformer.Informer().HasSynced() && secretInformer.Informer().HasSynced()
  112. })
  113. }
  114. // ValidateInitialization ensures an authorizer is set.
  115. func (s *Plugin) ValidateInitialization() error {
  116. if s.client == nil {
  117. return fmt.Errorf("missing client")
  118. }
  119. if s.secretLister == nil {
  120. return fmt.Errorf("missing secretLister")
  121. }
  122. if s.serviceAccountLister == nil {
  123. return fmt.Errorf("missing serviceAccountLister")
  124. }
  125. return nil
  126. }
  127. // Admit verifies if the pod should be admitted
  128. func (s *Plugin) Admit(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
  129. if shouldIgnore(a) {
  130. return nil
  131. }
  132. pod := a.GetObject().(*api.Pod)
  133. // Don't modify the spec of mirror pods.
  134. // That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match)
  135. // That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either
  136. if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
  137. return s.Validate(a, o)
  138. }
  139. // Set the default service account if needed
  140. if len(pod.Spec.ServiceAccountName) == 0 {
  141. pod.Spec.ServiceAccountName = DefaultServiceAccountName
  142. }
  143. serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
  144. if err != nil {
  145. return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
  146. }
  147. if s.MountServiceAccountToken && shouldAutomount(serviceAccount, pod) {
  148. if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil {
  149. if _, ok := err.(errors.APIStatus); ok {
  150. return err
  151. }
  152. return admission.NewForbidden(a, err)
  153. }
  154. }
  155. if len(pod.Spec.ImagePullSecrets) == 0 {
  156. pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets))
  157. for i := 0; i < len(serviceAccount.ImagePullSecrets); i++ {
  158. pod.Spec.ImagePullSecrets[i].Name = serviceAccount.ImagePullSecrets[i].Name
  159. }
  160. }
  161. return s.Validate(a, o)
  162. }
  163. // Validate the data we obtained
  164. func (s *Plugin) Validate(a admission.Attributes, o admission.ObjectInterfaces) (err error) {
  165. if shouldIgnore(a) {
  166. return nil
  167. }
  168. pod := a.GetObject().(*api.Pod)
  169. // Mirror pods have restrictions on what they can reference
  170. if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
  171. if len(pod.Spec.ServiceAccountName) != 0 {
  172. return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts"))
  173. }
  174. hasSecrets := false
  175. podutil.VisitPodSecretNames(pod, func(name string) bool {
  176. hasSecrets = true
  177. return false
  178. })
  179. if hasSecrets {
  180. return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference secrets"))
  181. }
  182. for _, v := range pod.Spec.Volumes {
  183. if proj := v.Projected; proj != nil {
  184. for _, projSource := range proj.Sources {
  185. if projSource.ServiceAccountToken != nil {
  186. return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not use ServiceAccountToken volume projections"))
  187. }
  188. }
  189. }
  190. }
  191. return nil
  192. }
  193. // Ensure the referenced service account exists
  194. serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName)
  195. if err != nil {
  196. return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err))
  197. }
  198. if s.enforceMountableSecrets(serviceAccount) {
  199. if err := s.limitSecretReferences(serviceAccount, pod); err != nil {
  200. return admission.NewForbidden(a, err)
  201. }
  202. }
  203. return nil
  204. }
  205. func shouldIgnore(a admission.Attributes) bool {
  206. if a.GetResource().GroupResource() != api.Resource("pods") {
  207. return true
  208. }
  209. if a.GetSubresource() != "" {
  210. return true
  211. }
  212. obj := a.GetObject()
  213. if obj == nil {
  214. return true
  215. }
  216. _, ok := obj.(*api.Pod)
  217. if !ok {
  218. return true
  219. }
  220. return false
  221. }
  222. func shouldAutomount(sa *corev1.ServiceAccount, pod *api.Pod) bool {
  223. // Pod's preference wins
  224. if pod.Spec.AutomountServiceAccountToken != nil {
  225. return *pod.Spec.AutomountServiceAccountToken
  226. }
  227. // Then service account's
  228. if sa.AutomountServiceAccountToken != nil {
  229. return *sa.AutomountServiceAccountToken
  230. }
  231. // Default to true for backwards compatibility
  232. return true
  233. }
  234. // enforceMountableSecrets indicates whether mountable secrets should be enforced for a particular service account
  235. // A global setting of true will override any flag set on the individual service account
  236. func (s *Plugin) enforceMountableSecrets(serviceAccount *corev1.ServiceAccount) bool {
  237. if s.LimitSecretReferences {
  238. return true
  239. }
  240. if value, ok := serviceAccount.Annotations[EnforceMountableSecretsAnnotation]; ok {
  241. enforceMountableSecretCheck, _ := strconv.ParseBool(value)
  242. return enforceMountableSecretCheck
  243. }
  244. return false
  245. }
  246. // getServiceAccount returns the ServiceAccount for the given namespace and name if it exists
  247. func (s *Plugin) getServiceAccount(namespace string, name string) (*corev1.ServiceAccount, error) {
  248. serviceAccount, err := s.serviceAccountLister.ServiceAccounts(namespace).Get(name)
  249. if err == nil {
  250. return serviceAccount, nil
  251. }
  252. if !errors.IsNotFound(err) {
  253. return nil, err
  254. }
  255. // Could not find in cache, attempt to look up directly
  256. numAttempts := 1
  257. if name == DefaultServiceAccountName {
  258. // If this is the default serviceaccount, attempt more times, since it should be auto-created by the controller
  259. numAttempts = 10
  260. }
  261. retryInterval := time.Duration(rand.Int63n(100)+int64(100)) * time.Millisecond
  262. for i := 0; i < numAttempts; i++ {
  263. if i != 0 {
  264. time.Sleep(retryInterval)
  265. }
  266. serviceAccount, err := s.client.CoreV1().ServiceAccounts(namespace).Get(name, metav1.GetOptions{})
  267. if err == nil {
  268. return serviceAccount, nil
  269. }
  270. if !errors.IsNotFound(err) {
  271. return nil, err
  272. }
  273. }
  274. return nil, errors.NewNotFound(api.Resource("serviceaccount"), name)
  275. }
  276. // getReferencedServiceAccountToken returns the name of the first referenced secret which is a ServiceAccountToken for the service account
  277. func (s *Plugin) getReferencedServiceAccountToken(serviceAccount *corev1.ServiceAccount) (string, error) {
  278. if len(serviceAccount.Secrets) == 0 {
  279. return "", nil
  280. }
  281. tokens, err := s.getServiceAccountTokens(serviceAccount)
  282. if err != nil {
  283. return "", err
  284. }
  285. accountTokens := sets.NewString()
  286. for _, token := range tokens {
  287. accountTokens.Insert(token.Name)
  288. }
  289. // Prefer secrets in the order they're referenced.
  290. for _, secret := range serviceAccount.Secrets {
  291. if accountTokens.Has(secret.Name) {
  292. return secret.Name, nil
  293. }
  294. }
  295. return "", nil
  296. }
  297. // getServiceAccountTokens returns all ServiceAccountToken secrets for the given ServiceAccount
  298. func (s *Plugin) getServiceAccountTokens(serviceAccount *corev1.ServiceAccount) ([]*corev1.Secret, error) {
  299. secrets, err := s.secretLister.Secrets(serviceAccount.Namespace).List(labels.Everything())
  300. if err != nil {
  301. return nil, err
  302. }
  303. tokens := []*corev1.Secret{}
  304. for _, secret := range secrets {
  305. if secret.Type != corev1.SecretTypeServiceAccountToken {
  306. continue
  307. }
  308. if serviceaccount.IsServiceAccountToken(secret, serviceAccount) {
  309. tokens = append(tokens, secret)
  310. }
  311. }
  312. return tokens, nil
  313. }
  314. func (s *Plugin) limitSecretReferences(serviceAccount *corev1.ServiceAccount, pod *api.Pod) error {
  315. // Ensure all secrets the pod references are allowed by the service account
  316. mountableSecrets := sets.NewString()
  317. for _, s := range serviceAccount.Secrets {
  318. mountableSecrets.Insert(s.Name)
  319. }
  320. for _, volume := range pod.Spec.Volumes {
  321. source := volume.VolumeSource
  322. if source.Secret == nil {
  323. continue
  324. }
  325. secretName := source.Secret.SecretName
  326. if !mountableSecrets.Has(secretName) {
  327. return fmt.Errorf("volume with secret.secretName=\"%s\" is not allowed because service account %s does not reference that secret", secretName, serviceAccount.Name)
  328. }
  329. }
  330. for _, container := range pod.Spec.InitContainers {
  331. for _, env := range container.Env {
  332. if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
  333. if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
  334. 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)
  335. }
  336. }
  337. }
  338. }
  339. for _, container := range pod.Spec.Containers {
  340. for _, env := range container.Env {
  341. if env.ValueFrom != nil && env.ValueFrom.SecretKeyRef != nil {
  342. if !mountableSecrets.Has(env.ValueFrom.SecretKeyRef.Name) {
  343. 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)
  344. }
  345. }
  346. }
  347. }
  348. // limit pull secret references as well
  349. pullSecrets := sets.NewString()
  350. for _, s := range serviceAccount.ImagePullSecrets {
  351. pullSecrets.Insert(s.Name)
  352. }
  353. for i, pullSecretRef := range pod.Spec.ImagePullSecrets {
  354. if !pullSecrets.Has(pullSecretRef.Name) {
  355. return fmt.Errorf(`imagePullSecrets[%d].name="%s" is not allowed because service account %s does not reference that imagePullSecret`, i, pullSecretRef.Name, serviceAccount.Name)
  356. }
  357. }
  358. return nil
  359. }
  360. func (s *Plugin) mountServiceAccountToken(serviceAccount *corev1.ServiceAccount, pod *api.Pod) error {
  361. // Find the name of a referenced ServiceAccountToken secret we can mount
  362. serviceAccountToken, err := s.getReferencedServiceAccountToken(serviceAccount)
  363. if err != nil {
  364. return fmt.Errorf("Error looking up service account token for %s/%s: %v", serviceAccount.Namespace, serviceAccount.Name, err)
  365. }
  366. if len(serviceAccountToken) == 0 {
  367. // We don't have an API token to mount, so return
  368. if s.RequireAPIToken {
  369. // If a token is required, this is considered an error
  370. err := errors.NewServerTimeout(schema.GroupResource{Resource: "serviceaccounts"}, "create pod", 1)
  371. 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)
  372. return err
  373. }
  374. return nil
  375. }
  376. // Find the volume and volume name for the ServiceAccountTokenSecret if it already exists
  377. tokenVolumeName := ""
  378. hasTokenVolume := false
  379. allVolumeNames := sets.NewString()
  380. for _, volume := range pod.Spec.Volumes {
  381. allVolumeNames.Insert(volume.Name)
  382. if (!s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) && volume.Secret != nil && volume.Secret.SecretName == serviceAccountToken) ||
  383. (s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) && strings.HasPrefix(volume.Name, ServiceAccountVolumeName+"-")) {
  384. tokenVolumeName = volume.Name
  385. hasTokenVolume = true
  386. break
  387. }
  388. }
  389. // Determine a volume name for the ServiceAccountTokenSecret in case we need it
  390. if len(tokenVolumeName) == 0 {
  391. if s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) {
  392. tokenVolumeName = s.generateName(ServiceAccountVolumeName + "-")
  393. } else {
  394. // Try naming the volume the same as the serviceAccountToken, and uniquify if needed
  395. tokenVolumeName = serviceAccountToken
  396. if allVolumeNames.Has(tokenVolumeName) {
  397. tokenVolumeName = s.generateName(fmt.Sprintf("%s-", serviceAccountToken))
  398. }
  399. }
  400. }
  401. // Create the prototypical VolumeMount
  402. volumeMount := api.VolumeMount{
  403. Name: tokenVolumeName,
  404. ReadOnly: true,
  405. MountPath: DefaultAPITokenMountPath,
  406. }
  407. // Ensure every container mounts the APISecret volume
  408. needsTokenVolume := false
  409. for i, container := range pod.Spec.InitContainers {
  410. existingContainerMount := false
  411. for _, volumeMount := range container.VolumeMounts {
  412. // Existing mounts at the default mount path prevent mounting of the API token
  413. if volumeMount.MountPath == DefaultAPITokenMountPath {
  414. existingContainerMount = true
  415. break
  416. }
  417. }
  418. if !existingContainerMount {
  419. pod.Spec.InitContainers[i].VolumeMounts = append(pod.Spec.InitContainers[i].VolumeMounts, volumeMount)
  420. needsTokenVolume = true
  421. }
  422. }
  423. for i, container := range pod.Spec.Containers {
  424. existingContainerMount := false
  425. for _, volumeMount := range container.VolumeMounts {
  426. // Existing mounts at the default mount path prevent mounting of the API token
  427. if volumeMount.MountPath == DefaultAPITokenMountPath {
  428. existingContainerMount = true
  429. break
  430. }
  431. }
  432. if !existingContainerMount {
  433. pod.Spec.Containers[i].VolumeMounts = append(pod.Spec.Containers[i].VolumeMounts, volumeMount)
  434. needsTokenVolume = true
  435. }
  436. }
  437. // Add the volume if a container needs it
  438. if !hasTokenVolume && needsTokenVolume {
  439. pod.Spec.Volumes = append(pod.Spec.Volumes, s.createVolume(tokenVolumeName, serviceAccountToken))
  440. }
  441. return nil
  442. }
  443. func (s *Plugin) createVolume(tokenVolumeName, secretName string) api.Volume {
  444. if s.featureGate.Enabled(kubefeatures.BoundServiceAccountTokenVolume) {
  445. return api.Volume{
  446. Name: tokenVolumeName,
  447. VolumeSource: api.VolumeSource{
  448. Projected: &api.ProjectedVolumeSource{
  449. Sources: []api.VolumeProjection{
  450. {
  451. ServiceAccountToken: &api.ServiceAccountTokenProjection{
  452. Path: "token",
  453. ExpirationSeconds: 60 * 60,
  454. },
  455. },
  456. {
  457. ConfigMap: &api.ConfigMapProjection{
  458. LocalObjectReference: api.LocalObjectReference{
  459. Name: "kube-root-ca.crt",
  460. },
  461. Items: []api.KeyToPath{
  462. {
  463. Key: "ca.crt",
  464. Path: "ca.crt",
  465. },
  466. },
  467. },
  468. },
  469. {
  470. DownwardAPI: &api.DownwardAPIProjection{
  471. Items: []api.DownwardAPIVolumeFile{
  472. {
  473. Path: "namespace",
  474. FieldRef: &api.ObjectFieldSelector{
  475. APIVersion: "v1",
  476. FieldPath: "metadata.namespace",
  477. },
  478. },
  479. },
  480. },
  481. },
  482. },
  483. },
  484. },
  485. }
  486. }
  487. return api.Volume{
  488. Name: tokenVolumeName,
  489. VolumeSource: api.VolumeSource{
  490. Secret: &api.SecretVolumeSource{
  491. SecretName: secretName,
  492. },
  493. },
  494. }
  495. }