k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/plugin/pkg/admission/security/podsecurity/admission.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package podsecurity 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "sync" 25 26 // install conversions for types we need to convert 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 // PluginName is a string with the name of the plugin 60 const PluginName = "PodSecurity" 61 62 // Register registers a plugin 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 // Plugin holds state for and implements the admission plugin. 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 // initialize and register to legacy metrics once 93 defaultRecorderInit.Do(func() { 94 defaultRecorder = metrics.NewPrometheusRecorder(podsecurityadmissionapi.GetAPIVersion()) 95 defaultRecorder.MustRegister(legacyregistry.MustRegister) 96 }) 97 return defaultRecorder 98 } 99 100 // newPlugin creates a new admission plugin. 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 // SetExternalKubeInformerFactory registers an informer 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 // SetExternalKubeClientSet sets the plugin's client 133 func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) { 134 p.client = client 135 p.updateDelegate() 136 } 137 138 func (p *Plugin) updateDelegate() { 139 // return early if we don't have what we need to set up the admission delegate 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 // ValidateInitialization ensures all required options are set 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 // start with a generic forbidden error 200 retval := admission.NewForbidden(a, errors.New("Not allowed by PodSecurity")).(*apierrors.StatusError) 201 // use message/reason/details/code from admission library if populated 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 }