admission.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. /*
  2. Copyright 2017 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 noderestriction
  14. import (
  15. "context"
  16. "fmt"
  17. "io"
  18. "strings"
  19. v1 "k8s.io/api/core/v1"
  20. apiequality "k8s.io/apimachinery/pkg/api/equality"
  21. "k8s.io/apimachinery/pkg/api/errors"
  22. "k8s.io/apimachinery/pkg/api/meta"
  23. "k8s.io/apimachinery/pkg/labels"
  24. "k8s.io/apimachinery/pkg/util/diff"
  25. "k8s.io/apimachinery/pkg/util/sets"
  26. "k8s.io/apiserver/pkg/admission"
  27. apiserveradmission "k8s.io/apiserver/pkg/admission/initializer"
  28. "k8s.io/client-go/informers"
  29. corev1lister "k8s.io/client-go/listers/core/v1"
  30. "k8s.io/component-base/featuregate"
  31. "k8s.io/klog"
  32. podutil "k8s.io/kubernetes/pkg/api/pod"
  33. authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
  34. coordapi "k8s.io/kubernetes/pkg/apis/coordination"
  35. api "k8s.io/kubernetes/pkg/apis/core"
  36. "k8s.io/kubernetes/pkg/apis/policy"
  37. storage "k8s.io/kubernetes/pkg/apis/storage"
  38. "k8s.io/kubernetes/pkg/auth/nodeidentifier"
  39. "k8s.io/kubernetes/pkg/features"
  40. kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
  41. )
  42. // PluginName is a string with the name of the plugin
  43. const PluginName = "NodeRestriction"
  44. // Register registers a plugin
  45. func Register(plugins *admission.Plugins) {
  46. plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
  47. return NewPlugin(nodeidentifier.NewDefaultNodeIdentifier()), nil
  48. })
  49. }
  50. // NewPlugin creates a new NodeRestriction admission plugin.
  51. // This plugin identifies requests from nodes
  52. func NewPlugin(nodeIdentifier nodeidentifier.NodeIdentifier) *Plugin {
  53. return &Plugin{
  54. Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
  55. nodeIdentifier: nodeIdentifier,
  56. }
  57. }
  58. // Plugin holds state for and implements the admission plugin.
  59. type Plugin struct {
  60. *admission.Handler
  61. nodeIdentifier nodeidentifier.NodeIdentifier
  62. podsGetter corev1lister.PodLister
  63. nodesGetter corev1lister.NodeLister
  64. tokenRequestEnabled bool
  65. csiNodeInfoEnabled bool
  66. expandPersistentVolumesEnabled bool
  67. }
  68. var (
  69. _ admission.Interface = &Plugin{}
  70. _ apiserveradmission.WantsExternalKubeInformerFactory = &Plugin{}
  71. _ apiserveradmission.WantsFeatures = &Plugin{}
  72. )
  73. // InspectFeatureGates allows setting bools without taking a dep on a global variable
  74. func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
  75. p.tokenRequestEnabled = featureGates.Enabled(features.TokenRequest)
  76. p.csiNodeInfoEnabled = featureGates.Enabled(features.CSINodeInfo)
  77. p.expandPersistentVolumesEnabled = featureGates.Enabled(features.ExpandPersistentVolumes)
  78. }
  79. // SetExternalKubeInformerFactory registers an informer factory into Plugin
  80. func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
  81. p.podsGetter = f.Core().V1().Pods().Lister()
  82. p.nodesGetter = f.Core().V1().Nodes().Lister()
  83. }
  84. // ValidateInitialization validates the Plugin was initialized properly
  85. func (p *Plugin) ValidateInitialization() error {
  86. if p.nodeIdentifier == nil {
  87. return fmt.Errorf("%s requires a node identifier", PluginName)
  88. }
  89. if p.podsGetter == nil {
  90. return fmt.Errorf("%s requires a pod getter", PluginName)
  91. }
  92. if p.nodesGetter == nil {
  93. return fmt.Errorf("%s requires a node getter", PluginName)
  94. }
  95. return nil
  96. }
  97. var (
  98. podResource = api.Resource("pods")
  99. nodeResource = api.Resource("nodes")
  100. pvcResource = api.Resource("persistentvolumeclaims")
  101. svcacctResource = api.Resource("serviceaccounts")
  102. leaseResource = coordapi.Resource("leases")
  103. csiNodeResource = storage.Resource("csinodes")
  104. )
  105. // Admit checks the admission policy and triggers corresponding actions
  106. func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
  107. nodeName, isNode := p.nodeIdentifier.NodeIdentity(a.GetUserInfo())
  108. // Our job is just to restrict nodes
  109. if !isNode {
  110. return nil
  111. }
  112. if len(nodeName) == 0 {
  113. // disallow requests we cannot match to a particular node
  114. return admission.NewForbidden(a, fmt.Errorf("could not determine node from user %q", a.GetUserInfo().GetName()))
  115. }
  116. // TODO: if node doesn't exist and this isn't a create node request, then reject.
  117. switch a.GetResource().GroupResource() {
  118. case podResource:
  119. switch a.GetSubresource() {
  120. case "":
  121. return p.admitPod(nodeName, a)
  122. case "status":
  123. return p.admitPodStatus(nodeName, a)
  124. case "eviction":
  125. return p.admitPodEviction(nodeName, a)
  126. default:
  127. return admission.NewForbidden(a, fmt.Errorf("unexpected pod subresource %q, only 'status' and 'eviction' are allowed", a.GetSubresource()))
  128. }
  129. case nodeResource:
  130. return p.admitNode(nodeName, a)
  131. case pvcResource:
  132. switch a.GetSubresource() {
  133. case "status":
  134. return p.admitPVCStatus(nodeName, a)
  135. default:
  136. return admission.NewForbidden(a, fmt.Errorf("may only update PVC status"))
  137. }
  138. case svcacctResource:
  139. if p.tokenRequestEnabled {
  140. return p.admitServiceAccount(nodeName, a)
  141. }
  142. return nil
  143. case leaseResource:
  144. return p.admitLease(nodeName, a)
  145. case csiNodeResource:
  146. if p.csiNodeInfoEnabled {
  147. return p.admitCSINode(nodeName, a)
  148. }
  149. return admission.NewForbidden(a, fmt.Errorf("disabled by feature gates %s", features.CSINodeInfo))
  150. default:
  151. return nil
  152. }
  153. }
  154. // admitPod allows creating or deleting a pod if it is assigned to the
  155. // current node and fulfills related criteria.
  156. func (p *Plugin) admitPod(nodeName string, a admission.Attributes) error {
  157. switch a.GetOperation() {
  158. case admission.Create:
  159. return p.admitPodCreate(nodeName, a)
  160. case admission.Delete:
  161. // get the existing pod
  162. existingPod, err := p.podsGetter.Pods(a.GetNamespace()).Get(a.GetName())
  163. if errors.IsNotFound(err) {
  164. return err
  165. }
  166. if err != nil {
  167. return admission.NewForbidden(a, err)
  168. }
  169. // only allow a node to delete a pod bound to itself
  170. if existingPod.Spec.NodeName != nodeName {
  171. return admission.NewForbidden(a, fmt.Errorf("node %q can only delete pods with spec.nodeName set to itself", nodeName))
  172. }
  173. return nil
  174. default:
  175. return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q, node %q can only create and delete mirror pods", a.GetOperation(), nodeName))
  176. }
  177. }
  178. func (p *Plugin) admitPodCreate(nodeName string, a admission.Attributes) error {
  179. // require a pod object
  180. pod, ok := a.GetObject().(*api.Pod)
  181. if !ok {
  182. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  183. }
  184. // only allow nodes to create mirror pods
  185. if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; !isMirrorPod {
  186. return admission.NewForbidden(a, fmt.Errorf("pod does not have %q annotation, node %q can only create mirror pods", api.MirrorPodAnnotationKey, nodeName))
  187. }
  188. // only allow nodes to create a pod bound to itself
  189. if pod.Spec.NodeName != nodeName {
  190. return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with spec.nodeName set to itself", nodeName))
  191. }
  192. if len(pod.OwnerReferences) > 1 {
  193. return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with a single owner reference set to itself", nodeName))
  194. }
  195. if len(pod.OwnerReferences) == 1 {
  196. owner := pod.OwnerReferences[0]
  197. if owner.APIVersion != v1.SchemeGroupVersion.String() ||
  198. owner.Kind != "Node" ||
  199. owner.Name != nodeName {
  200. return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with an owner reference set to itself", nodeName))
  201. }
  202. if owner.Controller == nil || !*owner.Controller {
  203. return admission.NewForbidden(a, fmt.Errorf("node %q can only create pods with a controller owner reference set to itself", nodeName))
  204. }
  205. if owner.BlockOwnerDeletion != nil && *owner.BlockOwnerDeletion {
  206. return admission.NewForbidden(a, fmt.Errorf("node %q must not set blockOwnerDeletion on an owner reference", nodeName))
  207. }
  208. // Verify the node UID.
  209. node, err := p.nodesGetter.Get(nodeName)
  210. if errors.IsNotFound(err) {
  211. return err
  212. }
  213. if err != nil {
  214. return admission.NewForbidden(a, fmt.Errorf("error looking up node %s to verify uid: %v", nodeName, err))
  215. }
  216. if owner.UID != node.UID {
  217. return admission.NewForbidden(a, fmt.Errorf("node %s UID mismatch: expected %s got %s", nodeName, owner.UID, node.UID))
  218. }
  219. }
  220. // don't allow a node to create a pod that references any other API objects
  221. if pod.Spec.ServiceAccountName != "" {
  222. return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference a service account", nodeName))
  223. }
  224. hasSecrets := false
  225. podutil.VisitPodSecretNames(pod, func(name string) (shouldContinue bool) { hasSecrets = true; return false })
  226. if hasSecrets {
  227. return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference secrets", nodeName))
  228. }
  229. hasConfigMaps := false
  230. podutil.VisitPodConfigmapNames(pod, func(name string) (shouldContinue bool) { hasConfigMaps = true; return false })
  231. if hasConfigMaps {
  232. return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference configmaps", nodeName))
  233. }
  234. for _, v := range pod.Spec.Volumes {
  235. if v.PersistentVolumeClaim != nil {
  236. return admission.NewForbidden(a, fmt.Errorf("node %q can not create pods that reference persistentvolumeclaims", nodeName))
  237. }
  238. }
  239. return nil
  240. }
  241. // admitPodStatus allows to update the status of a pod if it is
  242. // assigned to the current node.
  243. func (p *Plugin) admitPodStatus(nodeName string, a admission.Attributes) error {
  244. switch a.GetOperation() {
  245. case admission.Update:
  246. // require an existing pod
  247. oldPod, ok := a.GetOldObject().(*api.Pod)
  248. if !ok {
  249. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
  250. }
  251. // only allow a node to update status of a pod bound to itself
  252. if oldPod.Spec.NodeName != nodeName {
  253. return admission.NewForbidden(a, fmt.Errorf("node %q can only update pod status for pods with spec.nodeName set to itself", nodeName))
  254. }
  255. newPod, ok := a.GetObject().(*api.Pod)
  256. if !ok {
  257. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  258. }
  259. if !labels.Equals(oldPod.Labels, newPod.Labels) {
  260. return admission.NewForbidden(a, fmt.Errorf("node %q cannot update labels through pod status", nodeName))
  261. }
  262. return nil
  263. default:
  264. return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
  265. }
  266. }
  267. // admitPodEviction allows to evict a pod if it is assigned to the current node.
  268. func (p *Plugin) admitPodEviction(nodeName string, a admission.Attributes) error {
  269. switch a.GetOperation() {
  270. case admission.Create:
  271. // require eviction to an existing pod object
  272. eviction, ok := a.GetObject().(*policy.Eviction)
  273. if !ok {
  274. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  275. }
  276. // use pod name from the admission attributes, if set, rather than from the submitted Eviction object
  277. podName := a.GetName()
  278. if len(podName) == 0 {
  279. if len(eviction.Name) == 0 {
  280. return admission.NewForbidden(a, fmt.Errorf("could not determine pod from request data"))
  281. }
  282. podName = eviction.Name
  283. }
  284. // get the existing pod
  285. existingPod, err := p.podsGetter.Pods(a.GetNamespace()).Get(podName)
  286. if errors.IsNotFound(err) {
  287. return err
  288. }
  289. if err != nil {
  290. return admission.NewForbidden(a, err)
  291. }
  292. // only allow a node to evict a pod bound to itself
  293. if existingPod.Spec.NodeName != nodeName {
  294. return admission.NewForbidden(a, fmt.Errorf("node %s can only evict pods with spec.nodeName set to itself", nodeName))
  295. }
  296. return nil
  297. default:
  298. return admission.NewForbidden(a, fmt.Errorf("unexpected operation %s", a.GetOperation()))
  299. }
  300. }
  301. func (p *Plugin) admitPVCStatus(nodeName string, a admission.Attributes) error {
  302. switch a.GetOperation() {
  303. case admission.Update:
  304. if !p.expandPersistentVolumesEnabled {
  305. return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update persistentvolumeclaim metadata", nodeName))
  306. }
  307. oldPVC, ok := a.GetOldObject().(*api.PersistentVolumeClaim)
  308. if !ok {
  309. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetOldObject()))
  310. }
  311. newPVC, ok := a.GetObject().(*api.PersistentVolumeClaim)
  312. if !ok {
  313. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  314. }
  315. // make copies for comparison
  316. oldPVC = oldPVC.DeepCopy()
  317. newPVC = newPVC.DeepCopy()
  318. // zero out resourceVersion to avoid comparing differences,
  319. // since the new object could leave it empty to indicate an unconditional update
  320. oldPVC.ObjectMeta.ResourceVersion = ""
  321. newPVC.ObjectMeta.ResourceVersion = ""
  322. oldPVC.Status.Capacity = nil
  323. newPVC.Status.Capacity = nil
  324. oldPVC.Status.Conditions = nil
  325. newPVC.Status.Conditions = nil
  326. // TODO(apelisse): We don't have a good mechanism to
  327. // verify that only the things that should have changed
  328. // have changed. Ignore it for now.
  329. oldPVC.ObjectMeta.ManagedFields = nil
  330. newPVC.ObjectMeta.ManagedFields = nil
  331. // ensure no metadata changed. nodes should not be able to relabel, add finalizers/owners, etc
  332. if !apiequality.Semantic.DeepEqual(oldPVC, newPVC) {
  333. return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update fields other than status.capacity and status.conditions: %v", nodeName, diff.ObjectReflectDiff(oldPVC, newPVC)))
  334. }
  335. return nil
  336. default:
  337. return admission.NewForbidden(a, fmt.Errorf("unexpected operation %q", a.GetOperation()))
  338. }
  339. }
  340. func (p *Plugin) admitNode(nodeName string, a admission.Attributes) error {
  341. requestedName := a.GetName()
  342. if a.GetOperation() == admission.Create {
  343. node, ok := a.GetObject().(*api.Node)
  344. if !ok {
  345. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  346. }
  347. // Don't allow a node to create its Node API object with the config source set.
  348. // We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
  349. if node.Spec.ConfigSource != nil {
  350. return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to create pods with a non-nil configSource", nodeName))
  351. }
  352. // Don't allow a node to register with labels outside the allowed set.
  353. // This would allow a node to add or modify its labels in a way that would let it steer privileged workloads to itself.
  354. modifiedLabels := getModifiedLabels(node.Labels, nil)
  355. if forbiddenLabels := p.getForbiddenCreateLabels(modifiedLabels); len(forbiddenLabels) > 0 {
  356. return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to set the following labels: %s", nodeName, strings.Join(forbiddenLabels.List(), ", ")))
  357. }
  358. // check and warn if nodes set labels on create that would have been forbidden on update
  359. // TODO(liggitt): in 1.19, expand getForbiddenCreateLabels to match getForbiddenUpdateLabels and drop this
  360. if forbiddenUpdateLabels := p.getForbiddenUpdateLabels(modifiedLabels); len(forbiddenUpdateLabels) > 0 {
  361. klog.Warningf("node %q added disallowed labels on node creation: %s", nodeName, strings.Join(forbiddenUpdateLabels.List(), ", "))
  362. }
  363. }
  364. if requestedName != nodeName {
  365. return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to modify node %q", nodeName, requestedName))
  366. }
  367. if a.GetOperation() == admission.Update {
  368. node, ok := a.GetObject().(*api.Node)
  369. if !ok {
  370. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  371. }
  372. oldNode, ok := a.GetOldObject().(*api.Node)
  373. if !ok {
  374. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  375. }
  376. // Don't allow a node to update the config source on its Node API object.
  377. // We scope node access to things listed in the Node.Spec, so allowing this would allow a view escalation.
  378. // We only do the check if the new node's configSource is non-nil; old kubelets might drop the field during a status update.
  379. if node.Spec.ConfigSource != nil && !apiequality.Semantic.DeepEqual(node.Spec.ConfigSource, oldNode.Spec.ConfigSource) {
  380. return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to update configSource to a new non-nil configSource", nodeName))
  381. }
  382. // Don't allow a node to update its own taints. This would allow a node to remove or modify its
  383. // taints in a way that would let it steer disallowed workloads to itself.
  384. if !apiequality.Semantic.DeepEqual(node.Spec.Taints, oldNode.Spec.Taints) {
  385. return admission.NewForbidden(a, fmt.Errorf("node %q is not allowed to modify taints", nodeName))
  386. }
  387. // Don't allow a node to update labels outside the allowed set.
  388. // This would allow a node to add or modify its labels in a way that would let it steer privileged workloads to itself.
  389. modifiedLabels := getModifiedLabels(node.Labels, oldNode.Labels)
  390. if forbiddenUpdateLabels := p.getForbiddenUpdateLabels(modifiedLabels); len(forbiddenUpdateLabels) > 0 {
  391. return admission.NewForbidden(a, fmt.Errorf("is not allowed to modify labels: %s", strings.Join(forbiddenUpdateLabels.List(), ", ")))
  392. }
  393. }
  394. return nil
  395. }
  396. // getModifiedLabels returns the set of label keys that are different between the two maps
  397. func getModifiedLabels(a, b map[string]string) sets.String {
  398. modified := sets.NewString()
  399. for k, v1 := range a {
  400. if v2, ok := b[k]; !ok || v1 != v2 {
  401. modified.Insert(k)
  402. }
  403. }
  404. for k, v1 := range b {
  405. if v2, ok := a[k]; !ok || v1 != v2 {
  406. modified.Insert(k)
  407. }
  408. }
  409. return modified
  410. }
  411. func isKubernetesLabel(key string) bool {
  412. namespace := getLabelNamespace(key)
  413. if namespace == "kubernetes.io" || strings.HasSuffix(namespace, ".kubernetes.io") {
  414. return true
  415. }
  416. if namespace == "k8s.io" || strings.HasSuffix(namespace, ".k8s.io") {
  417. return true
  418. }
  419. return false
  420. }
  421. func getLabelNamespace(key string) string {
  422. if parts := strings.SplitN(key, "/", 2); len(parts) == 2 {
  423. return parts[0]
  424. }
  425. return ""
  426. }
  427. // getForbiddenCreateLabels returns the set of labels that may not be set by the node.
  428. // TODO(liggitt): in 1.19, expand to match getForbiddenUpdateLabels()
  429. func (p *Plugin) getForbiddenCreateLabels(modifiedLabels sets.String) sets.String {
  430. if len(modifiedLabels) == 0 {
  431. return nil
  432. }
  433. forbiddenLabels := sets.NewString()
  434. for label := range modifiedLabels {
  435. namespace := getLabelNamespace(label)
  436. // forbid kubelets from setting node-restriction labels
  437. if namespace == v1.LabelNamespaceNodeRestriction || strings.HasSuffix(namespace, "."+v1.LabelNamespaceNodeRestriction) {
  438. forbiddenLabels.Insert(label)
  439. }
  440. }
  441. return forbiddenLabels
  442. }
  443. // getForbiddenLabels returns the set of labels that may not be set by the node on update.
  444. func (p *Plugin) getForbiddenUpdateLabels(modifiedLabels sets.String) sets.String {
  445. if len(modifiedLabels) == 0 {
  446. return nil
  447. }
  448. forbiddenLabels := sets.NewString()
  449. for label := range modifiedLabels {
  450. namespace := getLabelNamespace(label)
  451. // forbid kubelets from setting node-restriction labels
  452. if namespace == v1.LabelNamespaceNodeRestriction || strings.HasSuffix(namespace, "."+v1.LabelNamespaceNodeRestriction) {
  453. forbiddenLabels.Insert(label)
  454. }
  455. // forbid kubelets from setting unknown kubernetes.io and k8s.io labels on update
  456. if isKubernetesLabel(label) && !kubeletapis.IsKubeletLabel(label) {
  457. // TODO: defer to label policy once available
  458. forbiddenLabels.Insert(label)
  459. }
  460. }
  461. return forbiddenLabels
  462. }
  463. func (p *Plugin) admitServiceAccount(nodeName string, a admission.Attributes) error {
  464. if a.GetOperation() != admission.Create {
  465. return nil
  466. }
  467. if a.GetSubresource() != "token" {
  468. return nil
  469. }
  470. tr, ok := a.GetObject().(*authenticationapi.TokenRequest)
  471. if !ok {
  472. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  473. }
  474. // TokenRequests from a node must have a pod binding. That pod must be
  475. // scheduled on the node.
  476. ref := tr.Spec.BoundObjectRef
  477. if ref == nil ||
  478. ref.APIVersion != "v1" ||
  479. ref.Kind != "Pod" ||
  480. ref.Name == "" {
  481. return admission.NewForbidden(a, fmt.Errorf("node requested token not bound to a pod"))
  482. }
  483. if ref.UID == "" {
  484. return admission.NewForbidden(a, fmt.Errorf("node requested token with a pod binding without a uid"))
  485. }
  486. pod, err := p.podsGetter.Pods(a.GetNamespace()).Get(ref.Name)
  487. if errors.IsNotFound(err) {
  488. return err
  489. }
  490. if err != nil {
  491. return admission.NewForbidden(a, err)
  492. }
  493. if ref.UID != pod.UID {
  494. return admission.NewForbidden(a, fmt.Errorf("the UID in the bound object reference (%s) does not match the UID in record (%s). The object might have been deleted and then recreated", ref.UID, pod.UID))
  495. }
  496. if pod.Spec.NodeName != nodeName {
  497. return admission.NewForbidden(a, fmt.Errorf("node requested token bound to a pod scheduled on a different node"))
  498. }
  499. return nil
  500. }
  501. func (p *Plugin) admitLease(nodeName string, a admission.Attributes) error {
  502. // the request must be against the system namespace reserved for node leases
  503. if a.GetNamespace() != api.NamespaceNodeLease {
  504. return admission.NewForbidden(a, fmt.Errorf("can only access leases in the %q system namespace", api.NamespaceNodeLease))
  505. }
  506. // the request must come from a node with the same name as the lease
  507. if a.GetOperation() == admission.Create {
  508. // a.GetName() won't return the name on create, so we drill down to the proposed object
  509. lease, ok := a.GetObject().(*coordapi.Lease)
  510. if !ok {
  511. return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
  512. }
  513. if lease.Name != nodeName {
  514. return admission.NewForbidden(a, fmt.Errorf("can only access node lease with the same name as the requesting node"))
  515. }
  516. } else {
  517. if a.GetName() != nodeName {
  518. return admission.NewForbidden(a, fmt.Errorf("can only access node lease with the same name as the requesting node"))
  519. }
  520. }
  521. return nil
  522. }
  523. func (p *Plugin) admitCSINode(nodeName string, a admission.Attributes) error {
  524. // the request must come from a node with the same name as the CSINode object
  525. if a.GetOperation() == admission.Create {
  526. // a.GetName() won't return the name on create, so we drill down to the proposed object
  527. accessor, err := meta.Accessor(a.GetObject())
  528. if err != nil {
  529. return admission.NewForbidden(a, fmt.Errorf("unable to access the object name"))
  530. }
  531. if accessor.GetName() != nodeName {
  532. return admission.NewForbidden(a, fmt.Errorf("can only access CSINode with the same name as the requesting node"))
  533. }
  534. } else {
  535. if a.GetName() != nodeName {
  536. return admission.NewForbidden(a, fmt.Errorf("can only access CSINode with the same name as the requesting node"))
  537. }
  538. }
  539. return nil
  540. }