@@ -24,17 +24,21 @@ import (
24
24
"strings"
25
25
26
26
"github.com/google/go-cmp/cmp"
27
+
27
28
v1 "k8s.io/api/core/v1"
28
29
apiequality "k8s.io/apimachinery/pkg/api/equality"
29
30
apierrors "k8s.io/apimachinery/pkg/api/errors"
30
31
"k8s.io/apimachinery/pkg/api/meta"
31
32
"k8s.io/apimachinery/pkg/labels"
33
+ utilerrors "k8s.io/apimachinery/pkg/util/errors"
32
34
"k8s.io/apimachinery/pkg/util/sets"
33
35
"k8s.io/apiserver/pkg/admission"
34
36
apiserveradmission "k8s.io/apiserver/pkg/admission/initializer"
35
37
"k8s.io/client-go/informers"
36
38
corev1lister "k8s.io/client-go/listers/core/v1"
39
+ storagelisters "k8s.io/client-go/listers/storage/v1"
37
40
"k8s.io/component-base/featuregate"
41
+ "k8s.io/component-helpers/storage/ephemeral"
38
42
kubeletapis "k8s.io/kubelet/pkg/apis"
39
43
podutil "k8s.io/kubernetes/pkg/api/pod"
40
44
authenticationapi "k8s.io/kubernetes/pkg/apis/authentication"
@@ -43,7 +47,7 @@ import (
43
47
api "k8s.io/kubernetes/pkg/apis/core"
44
48
"k8s.io/kubernetes/pkg/apis/policy"
45
49
"k8s.io/kubernetes/pkg/apis/resource"
46
- storage "k8s.io/kubernetes/pkg/apis/storage"
50
+ "k8s.io/kubernetes/pkg/apis/storage"
47
51
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
48
52
"k8s.io/kubernetes/pkg/features"
49
53
)
@@ -70,13 +74,17 @@ func NewPlugin(nodeIdentifier nodeidentifier.NodeIdentifier) *Plugin {
70
74
// Plugin holds state for and implements the admission plugin.
71
75
type Plugin struct {
72
76
* admission.Handler
73
- nodeIdentifier nodeidentifier.NodeIdentifier
74
- podsGetter corev1lister.PodLister
75
- nodesGetter corev1lister.NodeLister
77
+ nodeIdentifier nodeidentifier.NodeIdentifier
78
+ podsGetter corev1lister.PodLister
79
+ nodesGetter corev1lister.NodeLister
80
+ csiDriverGetter storagelisters.CSIDriverLister
81
+ pvcGetter corev1lister.PersistentVolumeClaimLister
82
+ pvGetter corev1lister.PersistentVolumeLister
76
83
77
84
expansionRecoveryEnabled bool
78
85
dynamicResourceAllocationEnabled bool
79
86
allowInsecureKubeletCertificateSigningRequests bool
87
+ serviceAccountNodeAudienceRestriction bool
80
88
}
81
89
82
90
var (
@@ -90,12 +98,18 @@ func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
90
98
p .expansionRecoveryEnabled = featureGates .Enabled (features .RecoverVolumeExpansionFailure )
91
99
p .dynamicResourceAllocationEnabled = featureGates .Enabled (features .DynamicResourceAllocation )
92
100
p .allowInsecureKubeletCertificateSigningRequests = featureGates .Enabled (features .AllowInsecureKubeletCertificateSigningRequests )
101
+ p .serviceAccountNodeAudienceRestriction = featureGates .Enabled (features .ServiceAccountNodeAudienceRestriction )
93
102
}
94
103
95
104
// SetExternalKubeInformerFactory registers an informer factory into Plugin
96
105
func (p * Plugin ) SetExternalKubeInformerFactory (f informers.SharedInformerFactory ) {
97
106
p .podsGetter = f .Core ().V1 ().Pods ().Lister ()
98
107
p .nodesGetter = f .Core ().V1 ().Nodes ().Lister ()
108
+ if p .serviceAccountNodeAudienceRestriction {
109
+ p .csiDriverGetter = f .Storage ().V1 ().CSIDrivers ().Lister ()
110
+ p .pvcGetter = f .Core ().V1 ().PersistentVolumeClaims ().Lister ()
111
+ p .pvGetter = f .Core ().V1 ().PersistentVolumes ().Lister ()
112
+ }
99
113
}
100
114
101
115
// ValidateInitialization validates the Plugin was initialized properly
@@ -109,6 +123,17 @@ func (p *Plugin) ValidateInitialization() error {
109
123
if p .nodesGetter == nil {
110
124
return fmt .Errorf ("%s requires a node getter" , PluginName )
111
125
}
126
+ if p .serviceAccountNodeAudienceRestriction {
127
+ if p .csiDriverGetter == nil {
128
+ return fmt .Errorf ("%s requires a CSI driver getter" , PluginName )
129
+ }
130
+ if p .pvcGetter == nil {
131
+ return fmt .Errorf ("%s requires a PVC getter" , PluginName )
132
+ }
133
+ if p .pvGetter == nil {
134
+ return fmt .Errorf ("%s requires a PV getter" , PluginName )
135
+ }
136
+ }
112
137
return nil
113
138
}
114
139
@@ -594,6 +619,12 @@ func (p *Plugin) admitServiceAccount(nodeName string, a admission.Attributes) er
594
619
return admission .NewForbidden (a , fmt .Errorf ("node requested token bound to a pod scheduled on a different node" ))
595
620
}
596
621
622
+ if p .serviceAccountNodeAudienceRestriction {
623
+ if err := p .validateNodeServiceAccountAudience (tr , pod ); err != nil {
624
+ return admission .NewForbidden (a , err )
625
+ }
626
+ }
627
+
597
628
// Note: A token may only be bound to one object at a time. By requiring
598
629
// the Pod binding, noderestriction eliminates the opportunity to spoof
599
630
// a Node binding. Instead, kube-apiserver automatically infers and sets
@@ -603,6 +634,106 @@ func (p *Plugin) admitServiceAccount(nodeName string, a admission.Attributes) er
603
634
return nil
604
635
}
605
636
637
+ func (p * Plugin ) validateNodeServiceAccountAudience (tr * authenticationapi.TokenRequest , pod * v1.Pod ) error {
638
+ // ensure all items in tr.Spec.Audiences are present in a volume mount in the pod
639
+ requestedAudience := ""
640
+ switch len (tr .Spec .Audiences ) {
641
+ case 0 :
642
+ requestedAudience = ""
643
+ case 1 :
644
+ requestedAudience = tr .Spec .Audiences [0 ]
645
+ default :
646
+ return fmt .Errorf ("node may only request 0 or 1 audiences" )
647
+ }
648
+
649
+ foundAudiencesInPodSpec , err := p .podReferencesAudience (pod , requestedAudience )
650
+ if err != nil {
651
+ return fmt .Errorf ("error validating audience %q: %w" , requestedAudience , err )
652
+ }
653
+ if ! foundAudiencesInPodSpec {
654
+ return fmt .Errorf ("audience %q not found in pod spec volume" , requestedAudience )
655
+ }
656
+ return nil
657
+ }
658
+
659
+ func (p * Plugin ) podReferencesAudience (pod * v1.Pod , audience string ) (bool , error ) {
660
+ var errs []error
661
+
662
+ for _ , v := range pod .Spec .Volumes {
663
+ if v .Projected != nil {
664
+ for _ , src := range v .Projected .Sources {
665
+ if src .ServiceAccountToken != nil && src .ServiceAccountToken .Audience == audience {
666
+ return true , nil
667
+ }
668
+ }
669
+ }
670
+
671
+ // also allow audiences for CSI token requests
672
+ // - pod --> ephemeral --> pvc --> pv --> csi --> driver --> tokenrequest with audience
673
+ // - pod --> pvc --> pv --> csi --> driver --> tokenrequest with audience
674
+ // - pod --> csi --> driver --> tokenrequest with audience
675
+ var driverName string
676
+ var err error
677
+ switch {
678
+ case v .Ephemeral != nil && v .Ephemeral .VolumeClaimTemplate != nil :
679
+ pvcName := ephemeral .VolumeClaimName (pod , & v )
680
+ driverName , err = p .getCSIFromPVC (pod .Namespace , pvcName )
681
+ case v .PersistentVolumeClaim != nil :
682
+ driverName , err = p .getCSIFromPVC (pod .Namespace , v .PersistentVolumeClaim .ClaimName )
683
+ case v .CSI != nil :
684
+ driverName = v .CSI .Driver
685
+ }
686
+
687
+ if err != nil {
688
+ errs = append (errs , err )
689
+ continue
690
+ }
691
+
692
+ if len (driverName ) > 0 {
693
+ hasAudience , hasAudienceErr := p .csiDriverHasAudience (driverName , audience )
694
+ if hasAudienceErr != nil {
695
+ errs = append (errs , hasAudienceErr )
696
+ continue
697
+ }
698
+ if hasAudience {
699
+ return true , nil
700
+ }
701
+ }
702
+ }
703
+
704
+ return false , utilerrors .NewAggregate (errs )
705
+ }
706
+
707
+ // getCSIFromPVC returns the CSI driver name from the PVC->PV->CSI->Driver chain
708
+ func (p * Plugin ) getCSIFromPVC (namespace , claimName string ) (string , error ) {
709
+ pvc , err := p .pvcGetter .PersistentVolumeClaims (namespace ).Get (claimName )
710
+ if err != nil {
711
+ return "" , err
712
+ }
713
+ pv , err := p .pvGetter .Get (pvc .Spec .VolumeName )
714
+ if err != nil {
715
+ return "" , err
716
+ }
717
+ if pv .Spec .CSI != nil {
718
+ return pv .Spec .CSI .Driver , nil
719
+ }
720
+ return "" , nil
721
+ }
722
+
723
+ func (p * Plugin ) csiDriverHasAudience (driverName , audience string ) (bool , error ) {
724
+ driver , err := p .csiDriverGetter .Get (driverName )
725
+ if err != nil {
726
+ return false , err
727
+ }
728
+
729
+ for _ , tokenRequest := range driver .Spec .TokenRequests {
730
+ if tokenRequest .Audience == audience {
731
+ return true , nil
732
+ }
733
+ }
734
+ return false , nil
735
+ }
736
+
606
737
func (p * Plugin ) admitLease (nodeName string , a admission.Attributes ) error {
607
738
// the request must be against the system namespace reserved for node leases
608
739
if a .GetNamespace () != api .NamespaceNodeLease {
0 commit comments