1
16
17 package podsecurity
18
19 import (
20 "context"
21 "errors"
22 "fmt"
23 "io"
24 "sync"
25
26
27 _ "k8s.io/kubernetes/pkg/apis/apps/install"
28 _ "k8s.io/kubernetes/pkg/apis/batch/install"
29 _ "k8s.io/kubernetes/pkg/apis/core/install"
30 "k8s.io/kubernetes/pkg/features"
31
32 admissionv1 "k8s.io/api/admission/v1"
33 appsv1 "k8s.io/api/apps/v1"
34 batchv1 "k8s.io/api/batch/v1"
35 corev1 "k8s.io/api/core/v1"
36 apierrors "k8s.io/apimachinery/pkg/api/errors"
37 "k8s.io/apimachinery/pkg/runtime"
38 "k8s.io/apimachinery/pkg/runtime/schema"
39 "k8s.io/apiserver/pkg/admission"
40 genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
41 "k8s.io/apiserver/pkg/audit"
42 "k8s.io/apiserver/pkg/warning"
43 "k8s.io/client-go/informers"
44 "k8s.io/client-go/kubernetes"
45 corev1listers "k8s.io/client-go/listers/core/v1"
46 "k8s.io/component-base/featuregate"
47 "k8s.io/component-base/metrics/legacyregistry"
48 "k8s.io/kubernetes/pkg/api/legacyscheme"
49 "k8s.io/kubernetes/pkg/apis/apps"
50 "k8s.io/kubernetes/pkg/apis/batch"
51 "k8s.io/kubernetes/pkg/apis/core"
52 podsecurityadmission "k8s.io/pod-security-admission/admission"
53 podsecurityconfigloader "k8s.io/pod-security-admission/admission/api/load"
54 podsecurityadmissionapi "k8s.io/pod-security-admission/api"
55 "k8s.io/pod-security-admission/metrics"
56 "k8s.io/pod-security-admission/policy"
57 )
58
59
60 const PluginName = "PodSecurity"
61
62
63 func Register(plugins *admission.Plugins) {
64 plugins.Register(PluginName, func(reader io.Reader) (admission.Interface, error) {
65 return newPlugin(reader)
66 })
67 }
68
69
70 type Plugin struct {
71 *admission.Handler
72
73 inspectedFeatureGates bool
74
75 client kubernetes.Interface
76 namespaceLister corev1listers.NamespaceLister
77 podLister corev1listers.PodLister
78
79 delegate *podsecurityadmission.Admission
80 }
81
82 var _ admission.ValidationInterface = &Plugin{}
83 var _ genericadmissioninit.WantsExternalKubeInformerFactory = &Plugin{}
84 var _ genericadmissioninit.WantsExternalKubeClientSet = &Plugin{}
85
86 var (
87 defaultRecorder *metrics.PrometheusRecorder
88 defaultRecorderInit sync.Once
89 )
90
91 func getDefaultRecorder() metrics.Recorder {
92
93 defaultRecorderInit.Do(func() {
94 defaultRecorder = metrics.NewPrometheusRecorder(podsecurityadmissionapi.GetAPIVersion())
95 defaultRecorder.MustRegister(legacyregistry.MustRegister)
96 })
97 return defaultRecorder
98 }
99
100
101 func newPlugin(reader io.Reader) (*Plugin, error) {
102 config, err := podsecurityconfigloader.LoadFromReader(reader)
103 if err != nil {
104 return nil, err
105 }
106
107 evaluator, err := policy.NewEvaluator(policy.DefaultChecks())
108 if err != nil {
109 return nil, fmt.Errorf("could not create PodSecurityRegistry: %w", err)
110 }
111
112 return &Plugin{
113 Handler: admission.NewHandler(admission.Create, admission.Update),
114 delegate: &podsecurityadmission.Admission{
115 Configuration: config,
116 Evaluator: evaluator,
117 Metrics: getDefaultRecorder(),
118 PodSpecExtractor: podsecurityadmission.DefaultPodSpecExtractor{},
119 },
120 }, nil
121 }
122
123
124 func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
125 namespaceInformer := f.Core().V1().Namespaces()
126 p.namespaceLister = namespaceInformer.Lister()
127 p.podLister = f.Core().V1().Pods().Lister()
128 p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
129 p.updateDelegate()
130 }
131
132
133 func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
134 p.client = client
135 p.updateDelegate()
136 }
137
138 func (p *Plugin) updateDelegate() {
139
140 if p.namespaceLister == nil {
141 return
142 }
143 if p.podLister == nil {
144 return
145 }
146 if p.client == nil {
147 return
148 }
149 p.delegate.PodLister = podsecurityadmission.PodListerFromInformer(p.podLister)
150 p.delegate.NamespaceGetter = podsecurityadmission.NamespaceGetterFromListerAndClient(p.namespaceLister, p.client)
151 }
152
153 func (c *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
154 c.inspectedFeatureGates = true
155 policy.RelaxPolicyForUserNamespacePods(featureGates.Enabled(features.UserNamespacesPodSecurityStandards))
156 }
157
158
159 func (p *Plugin) ValidateInitialization() error {
160 if !p.inspectedFeatureGates {
161 return fmt.Errorf("%s did not see feature gates", PluginName)
162 }
163 if err := p.delegate.CompleteConfiguration(); err != nil {
164 return fmt.Errorf("%s configuration error: %w", PluginName, err)
165 }
166 if err := p.delegate.ValidateConfiguration(); err != nil {
167 return fmt.Errorf("%s invalid: %w", PluginName, err)
168 }
169 return nil
170 }
171
172 var (
173 applicableResources = map[schema.GroupResource]bool{
174 corev1.Resource("pods"): true,
175 corev1.Resource("namespaces"): true,
176 }
177 )
178
179 func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
180 gr := a.GetResource().GroupResource()
181 if !applicableResources[gr] && !p.delegate.PodSpecExtractor.HasPodSpec(gr) {
182 return nil
183 }
184
185 result := p.delegate.Validate(ctx, &lazyConvertingAttributes{Attributes: a})
186 for _, w := range result.Warnings {
187 warning.AddWarning(ctx, "", w)
188 }
189 if len(result.AuditAnnotations) > 0 {
190 annotations := make([]string, len(result.AuditAnnotations)*2)
191 i := 0
192 for k, v := range result.AuditAnnotations {
193 annotations[i], annotations[i+1] = podsecurityadmissionapi.AuditAnnotationPrefix+k, v
194 i += 2
195 }
196 audit.AddAuditAnnotations(ctx, annotations...)
197 }
198 if !result.Allowed {
199
200 retval := admission.NewForbidden(a, errors.New("Not allowed by PodSecurity")).(*apierrors.StatusError)
201
202 if result.Result != nil {
203 if len(result.Result.Message) > 0 {
204 retval.ErrStatus.Message = result.Result.Message
205 }
206 if len(result.Result.Reason) > 0 {
207 retval.ErrStatus.Reason = result.Result.Reason
208 }
209 if result.Result.Details != nil {
210 retval.ErrStatus.Details = result.Result.Details
211 }
212 if result.Result.Code != 0 {
213 retval.ErrStatus.Code = result.Result.Code
214 }
215 }
216 return retval
217 }
218 return nil
219 }
220
221 type lazyConvertingAttributes struct {
222 admission.Attributes
223
224 convertObjectOnce sync.Once
225 convertedObject runtime.Object
226 convertedObjectError error
227
228 convertOldObjectOnce sync.Once
229 convertedOldObject runtime.Object
230 convertedOldObjectError error
231 }
232
233 func (l *lazyConvertingAttributes) GetObject() (runtime.Object, error) {
234 l.convertObjectOnce.Do(func() {
235 l.convertedObject, l.convertedObjectError = convert(l.Attributes.GetObject())
236 })
237 return l.convertedObject, l.convertedObjectError
238 }
239
240 func (l *lazyConvertingAttributes) GetOldObject() (runtime.Object, error) {
241 l.convertOldObjectOnce.Do(func() {
242 l.convertedOldObject, l.convertedOldObjectError = convert(l.Attributes.GetOldObject())
243 })
244 return l.convertedOldObject, l.convertedOldObjectError
245 }
246
247 func (l *lazyConvertingAttributes) GetOperation() admissionv1.Operation {
248 return admissionv1.Operation(l.Attributes.GetOperation())
249 }
250
251 func (l *lazyConvertingAttributes) GetUserName() string {
252 return l.GetUserInfo().GetName()
253 }
254
255 func convert(in runtime.Object) (runtime.Object, error) {
256 var out runtime.Object
257 switch in.(type) {
258 case *core.Namespace:
259 out = &corev1.Namespace{}
260 case *core.Pod:
261 out = &corev1.Pod{}
262 case *core.ReplicationController:
263 out = &corev1.ReplicationController{}
264 case *core.PodTemplate:
265 out = &corev1.PodTemplate{}
266 case *apps.ReplicaSet:
267 out = &appsv1.ReplicaSet{}
268 case *apps.Deployment:
269 out = &appsv1.Deployment{}
270 case *apps.StatefulSet:
271 out = &appsv1.StatefulSet{}
272 case *apps.DaemonSet:
273 out = &appsv1.DaemonSet{}
274 case *batch.Job:
275 out = &batchv1.Job{}
276 case *batch.CronJob:
277 out = &batchv1.CronJob{}
278 default:
279 return in, fmt.Errorf("unexpected type %T", in)
280 }
281 if err := legacyscheme.Scheme.Convert(in, out, nil); err != nil {
282 return in, err
283 }
284 return out, nil
285 }
286
View as plain text