mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	PodSecurity: admission: admission library
Co-authored-by: Jordan Liggitt <liggitt@google.com>
This commit is contained in:
		
				
					committed by
					
						
						Jordan Liggitt
					
				
			
			
				
	
			
			
			
						parent
						
							29f5ebf1fe
						
					
				
				
					commit
					02a6187757
				
			
							
								
								
									
										549
									
								
								staging/src/k8s.io/pod-security-admission/admission/admission.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										549
									
								
								staging/src/k8s.io/pod-security-admission/admission/admission.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,549 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2021 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package admission
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/klog/v2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						admissionv1 "k8s.io/api/admission/v1"
 | 
				
			||||||
 | 
						appsv1 "k8s.io/api/apps/v1"
 | 
				
			||||||
 | 
						batchv1 "k8s.io/api/batch/v1"
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission"
 | 
				
			||||||
 | 
						admissionapi "k8s.io/pod-security-admission/admission/api"
 | 
				
			||||||
 | 
						"k8s.io/pod-security-admission/admission/api/validation"
 | 
				
			||||||
 | 
						"k8s.io/pod-security-admission/api"
 | 
				
			||||||
 | 
						"k8s.io/pod-security-admission/metrics"
 | 
				
			||||||
 | 
						"k8s.io/pod-security-admission/policy"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						namespaceMaxPodsToCheck  = 3000
 | 
				
			||||||
 | 
						namespacePodCheckTimeout = 1 * time.Second
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Admission implements the core admission logic for the Pod Security Admission controller.
 | 
				
			||||||
 | 
					// The admission logic can be
 | 
				
			||||||
 | 
					type Admission struct {
 | 
				
			||||||
 | 
						Configuration *admissionapi.PodSecurityConfiguration
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Getting policy checks per level/version
 | 
				
			||||||
 | 
						Evaluator policy.Evaluator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Metrics
 | 
				
			||||||
 | 
						Metrics metrics.EvaluationRecorder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Arbitrary object --> PodSpec
 | 
				
			||||||
 | 
						PodSpecExtractor PodSpecExtractor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// API connections
 | 
				
			||||||
 | 
						NamespaceGetter NamespaceGetter
 | 
				
			||||||
 | 
						PodLister       PodLister
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defaultPolicy api.Policy
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type NamespaceGetter interface {
 | 
				
			||||||
 | 
						GetNamespace(ctx context.Context, name string) (*corev1.Namespace, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PodLister interface {
 | 
				
			||||||
 | 
						ListPods(ctx context.Context, namespace string) ([]*corev1.Pod, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PodSpecExtractor extracts a PodSpec from pod-controller resources that embed a PodSpec.
 | 
				
			||||||
 | 
					// This interface can be extended to enforce policy on CRDs for custom pod-controllers.
 | 
				
			||||||
 | 
					type PodSpecExtractor interface {
 | 
				
			||||||
 | 
						// HasPodSpec returns true if the given resource type MAY contain an extractable PodSpec.
 | 
				
			||||||
 | 
						HasPodSpec(schema.GroupResource) bool
 | 
				
			||||||
 | 
						// ExtractPodSpec returns a pod spec and metadata to evaluate from the object.
 | 
				
			||||||
 | 
						// An error returned here does not block admission of the pod-spec-containing object and is not returned to the user.
 | 
				
			||||||
 | 
						// If the object has no pod spec, return `nil, nil, nil`.
 | 
				
			||||||
 | 
						ExtractPodSpec(runtime.Object) (*metav1.ObjectMeta, *corev1.PodSpec, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var defaultPodSpecResources = map[schema.GroupResource]bool{
 | 
				
			||||||
 | 
						corev1.Resource("pods"):                   true,
 | 
				
			||||||
 | 
						corev1.Resource("replicationcontrollers"): true,
 | 
				
			||||||
 | 
						corev1.Resource("podtemplates"):           true,
 | 
				
			||||||
 | 
						appsv1.Resource("replicasets"):            true,
 | 
				
			||||||
 | 
						appsv1.Resource("deployments"):            true,
 | 
				
			||||||
 | 
						appsv1.Resource("statefulsets"):           true,
 | 
				
			||||||
 | 
						appsv1.Resource("daemonsets"):             true,
 | 
				
			||||||
 | 
						batchv1.Resource("jobs"):                  true,
 | 
				
			||||||
 | 
						batchv1.Resource("cronjobs"):              true,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DefaultPodSpecExtractor struct{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (DefaultPodSpecExtractor) HasPodSpec(gr schema.GroupResource) bool {
 | 
				
			||||||
 | 
						return defaultPodSpecResources[gr]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (DefaultPodSpecExtractor) ExtractPodSpec(obj runtime.Object) (*metav1.ObjectMeta, *corev1.PodSpec, error) {
 | 
				
			||||||
 | 
						switch o := obj.(type) {
 | 
				
			||||||
 | 
						case *corev1.Pod:
 | 
				
			||||||
 | 
							return &o.ObjectMeta, &o.Spec, nil
 | 
				
			||||||
 | 
						case *corev1.PodTemplate:
 | 
				
			||||||
 | 
							return extractPodSpecFromTemplate(&o.Template)
 | 
				
			||||||
 | 
						case *corev1.ReplicationController:
 | 
				
			||||||
 | 
							return extractPodSpecFromTemplate(o.Spec.Template)
 | 
				
			||||||
 | 
						case *appsv1.ReplicaSet:
 | 
				
			||||||
 | 
							return extractPodSpecFromTemplate(&o.Spec.Template)
 | 
				
			||||||
 | 
						case *appsv1.Deployment:
 | 
				
			||||||
 | 
							return extractPodSpecFromTemplate(&o.Spec.Template)
 | 
				
			||||||
 | 
						case *appsv1.DaemonSet:
 | 
				
			||||||
 | 
							return extractPodSpecFromTemplate(&o.Spec.Template)
 | 
				
			||||||
 | 
						case *appsv1.StatefulSet:
 | 
				
			||||||
 | 
							return extractPodSpecFromTemplate(&o.Spec.Template)
 | 
				
			||||||
 | 
						case *batchv1.Job:
 | 
				
			||||||
 | 
							return extractPodSpecFromTemplate(&o.Spec.Template)
 | 
				
			||||||
 | 
						case *batchv1.CronJob:
 | 
				
			||||||
 | 
							return extractPodSpecFromTemplate(&o.Spec.JobTemplate.Spec.Template)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return nil, nil, fmt.Errorf("unexpected object type: %s", obj.GetObjectKind().GroupVersionKind().String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (DefaultPodSpecExtractor) PodSpecResources() []schema.GroupResource {
 | 
				
			||||||
 | 
						retval := make([]schema.GroupResource, 0, len(defaultPodSpecResources))
 | 
				
			||||||
 | 
						for r := range defaultPodSpecResources {
 | 
				
			||||||
 | 
							retval = append(retval, r)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return retval
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func extractPodSpecFromTemplate(template *corev1.PodTemplateSpec) (*metav1.ObjectMeta, *corev1.PodSpec, error) {
 | 
				
			||||||
 | 
						if template == nil {
 | 
				
			||||||
 | 
							return nil, nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &template.ObjectMeta, &template.Spec, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CompleteConfiguration() sets up default or derived configuration.
 | 
				
			||||||
 | 
					func (a *Admission) CompleteConfiguration() error {
 | 
				
			||||||
 | 
						if a.Configuration != nil {
 | 
				
			||||||
 | 
							if p, err := admissionapi.ToPolicy(a.Configuration.Defaults); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								a.defaultPolicy = p
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if a.PodSpecExtractor == nil {
 | 
				
			||||||
 | 
							a.PodSpecExtractor = &DefaultPodSpecExtractor{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ValidateConfiguration() ensures all required fields are set with valid values.
 | 
				
			||||||
 | 
					func (a *Admission) ValidateConfiguration() error {
 | 
				
			||||||
 | 
						if a.Configuration == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("configuration required")
 | 
				
			||||||
 | 
						} else if errs := validation.ValidatePodSecurityConfiguration(a.Configuration); len(errs) > 0 {
 | 
				
			||||||
 | 
							return errs.ToAggregate()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if p, err := admissionapi.ToPolicy(a.Configuration.Defaults); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							} else if !reflect.DeepEqual(p, a.defaultPolicy) {
 | 
				
			||||||
 | 
								return fmt.Errorf("default policy does not match; CompleteConfiguration() was not called before ValidateConfiguration()")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// TODO: check metrics is non-nil?
 | 
				
			||||||
 | 
						if a.PodSpecExtractor == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("PodSpecExtractor required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if a.Evaluator == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("Evaluator required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if a.NamespaceGetter == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("NamespaceGetter required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if a.PodLister == nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("PodLister required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Validate admits an API request.
 | 
				
			||||||
 | 
					// The objects in admission attributes are expected to be external v1 objects that we care about.
 | 
				
			||||||
 | 
					func (a *Admission) Validate(ctx context.Context, attrs admission.Attributes) admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						var response admissionv1.AdmissionResponse
 | 
				
			||||||
 | 
						switch attrs.GetResource().GroupResource() {
 | 
				
			||||||
 | 
						case corev1.Resource("namespaces"):
 | 
				
			||||||
 | 
							response = a.ValidateNamespace(ctx, attrs)
 | 
				
			||||||
 | 
						case corev1.Resource("pods"):
 | 
				
			||||||
 | 
							response = a.ValidatePod(ctx, attrs)
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							response = a.ValidatePodController(ctx, attrs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: record metrics.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return response
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Admission) ValidateNamespace(ctx context.Context, attrs admission.Attributes) admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						// short-circuit on subresources
 | 
				
			||||||
 | 
						if attrs.GetSubresource() != "" {
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						namespace, ok := attrs.GetObject().(*corev1.Namespace)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							klog.InfoS("failed to assert namespace type", "type", reflect.TypeOf(attrs.GetObject()))
 | 
				
			||||||
 | 
							return internalErrorResponse("failed to decode namespace")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						newPolicy, newErr := a.PolicyToEvaluate(namespace.Labels)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch attrs.GetOperation() {
 | 
				
			||||||
 | 
						case admission.Create:
 | 
				
			||||||
 | 
							// require valid labels on create
 | 
				
			||||||
 | 
							if newErr != nil {
 | 
				
			||||||
 | 
								return invalidResponse(newErr.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						case admission.Update:
 | 
				
			||||||
 | 
							// if update, check if policy labels changed
 | 
				
			||||||
 | 
							oldNamespace, ok := attrs.GetOldObject().(*corev1.Namespace)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								klog.InfoS("failed to assert old namespace type", "type", reflect.TypeOf(attrs.GetOldObject()))
 | 
				
			||||||
 | 
								return internalErrorResponse("failed to decode old namespace")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							oldPolicy, oldErr := a.PolicyToEvaluate(oldNamespace.Labels)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// require valid labels on update if they have changed
 | 
				
			||||||
 | 
							if newErr != nil && (oldErr == nil || newErr.Error() != oldErr.Error()) {
 | 
				
			||||||
 | 
								return invalidResponse(newErr.Error())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Skip dry-running pods:
 | 
				
			||||||
 | 
							// * if the enforce policy is unchanged
 | 
				
			||||||
 | 
							// * if the new enforce policy is privileged
 | 
				
			||||||
 | 
							// * if the new enforce is the same version and level was relaxed
 | 
				
			||||||
 | 
							// * for exempt namespaces
 | 
				
			||||||
 | 
							if newPolicy.Enforce == oldPolicy.Enforce {
 | 
				
			||||||
 | 
								return allowedResponse()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if newPolicy.Enforce.Level == api.LevelPrivileged {
 | 
				
			||||||
 | 
								return allowedResponse()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if newPolicy.Enforce.Version == oldPolicy.Enforce.Version &&
 | 
				
			||||||
 | 
								api.CompareLevels(newPolicy.Enforce.Level, oldPolicy.Enforce.Level) < 1 {
 | 
				
			||||||
 | 
								return allowedResponse()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if a.exemptNamespace(attrs.GetNamespace()) {
 | 
				
			||||||
 | 
								return allowedResponse()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							response := allowedResponse()
 | 
				
			||||||
 | 
							response.Warnings = a.EvaluatePodsInNamespace(ctx, namespace.Name, newPolicy.Enforce)
 | 
				
			||||||
 | 
							return response
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ignoredPodSubresources is a set of ignored Pod subresources.
 | 
				
			||||||
 | 
					// Any other subresource is expected to be a *v1.Pod type and is evaluated.
 | 
				
			||||||
 | 
					// This ensures a version skewed webhook fails safe and denies an unknown pod subresource that allows modifying the pod spec.
 | 
				
			||||||
 | 
					var ignoredPodSubresources = map[string]bool{
 | 
				
			||||||
 | 
						"exec":        true,
 | 
				
			||||||
 | 
						"attach":      true,
 | 
				
			||||||
 | 
						"binding":     true,
 | 
				
			||||||
 | 
						"eviction":    true,
 | 
				
			||||||
 | 
						"log":         true,
 | 
				
			||||||
 | 
						"portforward": true,
 | 
				
			||||||
 | 
						"proxy":       true,
 | 
				
			||||||
 | 
						"status":      true,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Admission) ValidatePod(ctx context.Context, attrs admission.Attributes) admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						// short-circuit on ignored subresources
 | 
				
			||||||
 | 
						if ignoredPodSubresources[attrs.GetSubresource()] {
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// short-circuit on exempt namespaces and users
 | 
				
			||||||
 | 
						if a.exemptNamespace(attrs.GetNamespace()) || a.exemptUser(attrs.GetUserInfo().GetName()) {
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pod, ok := attrs.GetObject().(*corev1.Pod)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							klog.InfoS("failed to assert pod type", "type", reflect.TypeOf(attrs.GetObject()))
 | 
				
			||||||
 | 
							return internalErrorResponse("failed to decode pod")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						enforce := true
 | 
				
			||||||
 | 
						if attrs.GetOperation() == admission.Update {
 | 
				
			||||||
 | 
							oldPod, ok := attrs.GetOldObject().(*corev1.Pod)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								klog.InfoS("failed to assert old pod type", "type", reflect.TypeOf(attrs.GetOldObject()))
 | 
				
			||||||
 | 
								return internalErrorResponse("failed to decode old pod")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !isSignificantPodUpdate(pod, oldPod) {
 | 
				
			||||||
 | 
								// Nothing we care about changed, so always allow the update.
 | 
				
			||||||
 | 
								return allowedResponse()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return a.EvaluatePod(ctx, attrs.GetNamespace(), &pod.ObjectMeta, &pod.Spec, enforce)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Admission) ValidatePodController(ctx context.Context, attrs admission.Attributes) admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						// short-circuit on subresources
 | 
				
			||||||
 | 
						if attrs.GetSubresource() != "" {
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// short-circuit on exempt namespaces and users
 | 
				
			||||||
 | 
						if a.exemptNamespace(attrs.GetNamespace()) || a.exemptUser(attrs.GetUserInfo().GetName()) {
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						podMetadata, podSpec, err := a.PodSpecExtractor.ExtractPodSpec(attrs.GetObject())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							klog.ErrorS(err, "failed to extract pod spec")
 | 
				
			||||||
 | 
							return internalErrorResponse("failed to extract pod template")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if podMetadata == nil && podSpec == nil {
 | 
				
			||||||
 | 
							// if a controller with an optional pod spec does not contain a pod spec, skip validation
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return a.EvaluatePod(ctx, attrs.GetNamespace(), podMetadata, podSpec, false)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EvaluatePod looks up the policy for the pods namespace, and checks it against the given pod(-like) object.
 | 
				
			||||||
 | 
					// The enforce policy is only checked if enforce=true.
 | 
				
			||||||
 | 
					func (a *Admission) EvaluatePod(ctx context.Context, namespaceName string, podMetadata *metav1.ObjectMeta, podSpec *corev1.PodSpec, enforce bool) admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						// short-circuit on exempt runtimeclass
 | 
				
			||||||
 | 
						if a.exemptRuntimeClass(podSpec.RuntimeClassName) {
 | 
				
			||||||
 | 
							return allowedResponse()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						namespace, err := a.NamespaceGetter.GetNamespace(ctx, namespaceName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							klog.ErrorS(err, "failed to fetch pod namespace", "namespace", namespaceName)
 | 
				
			||||||
 | 
							return internalErrorResponse(fmt.Sprintf("failed to lookup namespace %s", namespaceName))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auditAnnotations := map[string]string{}
 | 
				
			||||||
 | 
						nsPolicy, err := a.PolicyToEvaluate(namespace.Labels)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							klog.V(2).InfoS("failed to parse PodSecurity namespace labels", "err", err)
 | 
				
			||||||
 | 
							auditAnnotations["error"] = fmt.Sprintf("Failed to parse policy: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						response := allowedResponse()
 | 
				
			||||||
 | 
						if enforce {
 | 
				
			||||||
 | 
							if result := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(nsPolicy.Enforce, podMetadata, podSpec)); !result.Allowed {
 | 
				
			||||||
 | 
								response = forbiddenResponse(result.ForbiddenDetail())
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: reuse previous evaluation if audit level+version is the same as enforce level+version
 | 
				
			||||||
 | 
						if result := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(nsPolicy.Audit, podMetadata, podSpec)); !result.Allowed {
 | 
				
			||||||
 | 
							auditAnnotations["audit"] = result.ForbiddenDetail()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: reuse previous evaluation if warn level+version is the same as audit or enforce level+version
 | 
				
			||||||
 | 
						if result := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(nsPolicy.Warn, podMetadata, podSpec)); !result.Allowed {
 | 
				
			||||||
 | 
							// TODO: Craft a better user-facing warning message
 | 
				
			||||||
 | 
							response.Warnings = append(response.Warnings, fmt.Sprintf("Pod violates PodSecurity profile %s: %s", nsPolicy.Warn.String(), result.ForbiddenDetail()))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						response.AuditAnnotations = auditAnnotations
 | 
				
			||||||
 | 
						return response
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Admission) EvaluatePodsInNamespace(ctx context.Context, namespace string, enforce api.LevelVersion) []string {
 | 
				
			||||||
 | 
						timeout := namespacePodCheckTimeout
 | 
				
			||||||
 | 
						if deadline, ok := ctx.Deadline(); ok {
 | 
				
			||||||
 | 
							timeRemaining := time.Duration(0.9 * float64(time.Until(deadline))) // Leave a little time to respond.
 | 
				
			||||||
 | 
							if timeout > timeRemaining {
 | 
				
			||||||
 | 
								timeout = timeRemaining
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						deadline := time.Now().Add(timeout)
 | 
				
			||||||
 | 
						ctx, cancel := context.WithDeadline(ctx, deadline)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pods, err := a.PodLister.ListPods(ctx, namespace)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							klog.ErrorS(err, "Failed to list pods", "namespace", namespace)
 | 
				
			||||||
 | 
							return []string{"Failed to list pods"}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var warnings []string
 | 
				
			||||||
 | 
						if len(pods) > namespaceMaxPodsToCheck {
 | 
				
			||||||
 | 
							warnings = append(warnings, fmt.Sprintf("Large namespace: only checking the first %d of %d pods", namespaceMaxPodsToCheck, len(pods)))
 | 
				
			||||||
 | 
							pods = pods[0:namespaceMaxPodsToCheck]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for i, pod := range pods {
 | 
				
			||||||
 | 
							// short-circuit on exempt runtimeclass
 | 
				
			||||||
 | 
							if a.exemptRuntimeClass(pod.Spec.RuntimeClassName) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							r := policy.AggregateCheckResults(a.Evaluator.EvaluatePod(enforce, &pod.ObjectMeta, &pod.Spec))
 | 
				
			||||||
 | 
							if !r.Allowed {
 | 
				
			||||||
 | 
								// TODO: consider aggregating results (e.g. multiple pods failed for the same reasons)
 | 
				
			||||||
 | 
								warnings = append(warnings, fmt.Sprintf("%s: %s", pod.Name, r.ForbiddenReason()))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if time.Now().After(deadline) {
 | 
				
			||||||
 | 
								return append(warnings, fmt.Sprintf("Timeout reached after checking %d pods", i+1))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return warnings
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Admission) PolicyToEvaluate(labels map[string]string) (api.Policy, error) {
 | 
				
			||||||
 | 
						return api.PolicyToEvaluate(labels, a.defaultPolicy)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// allowResponse is the response used when the admission decision is allow.
 | 
				
			||||||
 | 
					func allowedResponse() admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						return admissionv1.AdmissionResponse{Allowed: true}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// forbiddenResponse is the response used when the admission decision is deny for policy violations.
 | 
				
			||||||
 | 
					func forbiddenResponse(msg string) admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						return admissionv1.AdmissionResponse{
 | 
				
			||||||
 | 
							Allowed: false,
 | 
				
			||||||
 | 
							Result: &metav1.Status{
 | 
				
			||||||
 | 
								Status:  metav1.StatusFailure,
 | 
				
			||||||
 | 
								Reason:  metav1.StatusReasonForbidden,
 | 
				
			||||||
 | 
								Message: msg,
 | 
				
			||||||
 | 
								Code:    http.StatusForbidden,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// invalidResponse is the response used for namespace requests when namespace labels are invalid.
 | 
				
			||||||
 | 
					func invalidResponse(msg string) admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						return admissionv1.AdmissionResponse{
 | 
				
			||||||
 | 
							Allowed: false,
 | 
				
			||||||
 | 
							Result: &metav1.Status{
 | 
				
			||||||
 | 
								Status:  metav1.StatusFailure,
 | 
				
			||||||
 | 
								Reason:  metav1.StatusReasonInvalid,
 | 
				
			||||||
 | 
								Message: msg,
 | 
				
			||||||
 | 
								Code:    422,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// internalErrorResponse is the response used for unexpected errors
 | 
				
			||||||
 | 
					func internalErrorResponse(msg string) admissionv1.AdmissionResponse {
 | 
				
			||||||
 | 
						return admissionv1.AdmissionResponse{
 | 
				
			||||||
 | 
							Allowed: false,
 | 
				
			||||||
 | 
							Result: &metav1.Status{
 | 
				
			||||||
 | 
								Status:  metav1.StatusFailure,
 | 
				
			||||||
 | 
								Reason:  metav1.StatusReasonInternalError,
 | 
				
			||||||
 | 
								Message: msg,
 | 
				
			||||||
 | 
								Code:    http.StatusInternalServerError,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isSignificantPodUpdate determines whether a pod update should trigger a policy evaluation.
 | 
				
			||||||
 | 
					// Relevant mutable pod fields as of 1.21 are image and seccomp annotations:
 | 
				
			||||||
 | 
					// * https://github.com/kubernetes/kubernetes/blob/release-1.21/pkg/apis/core/validation/validation.go#L3947-L3949
 | 
				
			||||||
 | 
					func isSignificantPodUpdate(pod, oldPod *corev1.Pod) bool {
 | 
				
			||||||
 | 
						if pod.Annotations[corev1.SeccompPodAnnotationKey] != oldPod.Annotations[corev1.SeccompPodAnnotationKey] {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(pod.Spec.Containers) != len(oldPod.Spec.Containers) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(pod.Spec.InitContainers) != len(oldPod.Spec.InitContainers) {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for i := 0; i < len(pod.Spec.Containers); i++ {
 | 
				
			||||||
 | 
							if isSignificantContainerUpdate(&pod.Spec.Containers[i], &oldPod.Spec.Containers[i], pod.Annotations, oldPod.Annotations) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for i := 0; i < len(pod.Spec.InitContainers); i++ {
 | 
				
			||||||
 | 
							if isSignificantContainerUpdate(&pod.Spec.InitContainers[i], &oldPod.Spec.InitContainers[i], pod.Annotations, oldPod.Annotations) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, c := range pod.Spec.EphemeralContainers {
 | 
				
			||||||
 | 
							var oldC *corev1.Container
 | 
				
			||||||
 | 
							for i, oc := range oldPod.Spec.EphemeralContainers {
 | 
				
			||||||
 | 
								if oc.Name == c.Name {
 | 
				
			||||||
 | 
									oldC = (*corev1.Container)(&oldPod.Spec.EphemeralContainers[i].EphemeralContainerCommon)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if oldC == nil {
 | 
				
			||||||
 | 
								return true // EphemeralContainer added
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if isSignificantContainerUpdate((*corev1.Container)(&c.EphemeralContainerCommon), oldC, pod.Annotations, oldPod.Annotations) {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// isSignificantContainerUpdate determines whether a container update should trigger a policy evaluation.
 | 
				
			||||||
 | 
					func isSignificantContainerUpdate(container, oldContainer *corev1.Container, annotations, oldAnnotations map[string]string) bool {
 | 
				
			||||||
 | 
						if container.Image != oldContainer.Image {
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						seccompKey := corev1.SeccompContainerAnnotationKeyPrefix + container.Name
 | 
				
			||||||
 | 
						return annotations[seccompKey] != oldAnnotations[seccompKey]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (a *Admission) exemptNamespace(namespace string) bool {
 | 
				
			||||||
 | 
						if len(namespace) == 0 {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// TODO: consider optimizing to O(1) lookup
 | 
				
			||||||
 | 
						return containsString(namespace, a.Configuration.Exemptions.Namespaces)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (a *Admission) exemptUser(username string) bool {
 | 
				
			||||||
 | 
						if len(username) == 0 {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// TODO: consider optimizing to O(1) lookup
 | 
				
			||||||
 | 
						return containsString(username, a.Configuration.Exemptions.Usernames)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func (a *Admission) exemptRuntimeClass(runtimeClass *string) bool {
 | 
				
			||||||
 | 
						if runtimeClass == nil || len(*runtimeClass) == 0 {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// TODO: consider optimizing to O(1) lookup
 | 
				
			||||||
 | 
						return containsString(*runtimeClass, a.Configuration.Exemptions.RuntimeClasses)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					func containsString(needle string, haystack []string) bool {
 | 
				
			||||||
 | 
						for _, s := range haystack {
 | 
				
			||||||
 | 
							if s == needle {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,465 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2021 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package admission
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						admissionv1 "k8s.io/api/admission/v1"
 | 
				
			||||||
 | 
						appsv1 "k8s.io/api/apps/v1"
 | 
				
			||||||
 | 
						batchv1 "k8s.io/api/batch/v1"
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/runtime/schema"
 | 
				
			||||||
 | 
						"k8s.io/apiserver/pkg/admission"
 | 
				
			||||||
 | 
						admissionapi "k8s.io/pod-security-admission/admission/api"
 | 
				
			||||||
 | 
						"k8s.io/pod-security-admission/api"
 | 
				
			||||||
 | 
						"k8s.io/pod-security-admission/policy"
 | 
				
			||||||
 | 
						"k8s.io/utils/pointer"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDefaultExtractPodSpec(t *testing.T) {
 | 
				
			||||||
 | 
						metadata := metav1.ObjectMeta{
 | 
				
			||||||
 | 
							Name: "foo-pod",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						spec := corev1.PodSpec{
 | 
				
			||||||
 | 
							Containers: []corev1.Container{{
 | 
				
			||||||
 | 
								Name: "foo-container",
 | 
				
			||||||
 | 
							}},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						objects := []runtime.Object{
 | 
				
			||||||
 | 
							&corev1.Pod{
 | 
				
			||||||
 | 
								ObjectMeta: metadata,
 | 
				
			||||||
 | 
								Spec:       spec,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&corev1.PodTemplate{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "foo-template"},
 | 
				
			||||||
 | 
								Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
									ObjectMeta: metadata,
 | 
				
			||||||
 | 
									Spec:       spec,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&corev1.ReplicationController{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "foo-rc"},
 | 
				
			||||||
 | 
								Spec: corev1.ReplicationControllerSpec{
 | 
				
			||||||
 | 
									Template: &corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
										ObjectMeta: metadata,
 | 
				
			||||||
 | 
										Spec:       spec,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&appsv1.ReplicaSet{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "foo-rs"},
 | 
				
			||||||
 | 
								Spec: appsv1.ReplicaSetSpec{
 | 
				
			||||||
 | 
									Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
										ObjectMeta: metadata,
 | 
				
			||||||
 | 
										Spec:       spec,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&appsv1.Deployment{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "foo-deployment"},
 | 
				
			||||||
 | 
								Spec: appsv1.DeploymentSpec{
 | 
				
			||||||
 | 
									Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
										ObjectMeta: metadata,
 | 
				
			||||||
 | 
										Spec:       spec,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&appsv1.StatefulSet{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "foo-ss"},
 | 
				
			||||||
 | 
								Spec: appsv1.StatefulSetSpec{
 | 
				
			||||||
 | 
									Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
										ObjectMeta: metadata,
 | 
				
			||||||
 | 
										Spec:       spec,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&appsv1.DaemonSet{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "foo-ds"},
 | 
				
			||||||
 | 
								Spec: appsv1.DaemonSetSpec{
 | 
				
			||||||
 | 
									Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
										ObjectMeta: metadata,
 | 
				
			||||||
 | 
										Spec:       spec,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&batchv1.Job{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "foo-job"},
 | 
				
			||||||
 | 
								Spec: batchv1.JobSpec{
 | 
				
			||||||
 | 
									Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
										ObjectMeta: metadata,
 | 
				
			||||||
 | 
										Spec:       spec,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							&batchv1.CronJob{
 | 
				
			||||||
 | 
								ObjectMeta: metav1.ObjectMeta{Name: "foo-cronjob"},
 | 
				
			||||||
 | 
								Spec: batchv1.CronJobSpec{
 | 
				
			||||||
 | 
									JobTemplate: batchv1.JobTemplateSpec{
 | 
				
			||||||
 | 
										Spec: batchv1.JobSpec{
 | 
				
			||||||
 | 
											Template: corev1.PodTemplateSpec{
 | 
				
			||||||
 | 
												ObjectMeta: metadata,
 | 
				
			||||||
 | 
												Spec:       spec,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						extractor := &DefaultPodSpecExtractor{}
 | 
				
			||||||
 | 
						for _, obj := range objects {
 | 
				
			||||||
 | 
							name := obj.(metav1.Object).GetName()
 | 
				
			||||||
 | 
							actualMetadata, actualSpec, err := extractor.ExtractPodSpec(obj)
 | 
				
			||||||
 | 
							assert.NoError(t, err, name)
 | 
				
			||||||
 | 
							assert.Equal(t, &metadata, actualMetadata, "%s: Metadata mismatch", name)
 | 
				
			||||||
 | 
							assert.Equal(t, &spec, actualSpec, "%s: PodSpec mismatch", name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						service := &corev1.Service{
 | 
				
			||||||
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
								Name: "foo-svc",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _, err := extractor.ExtractPodSpec(service)
 | 
				
			||||||
 | 
						assert.Error(t, err, "service should not have an extractable pod spec")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDefaultHasPodSpec(t *testing.T) {
 | 
				
			||||||
 | 
						podLikeResources := []schema.GroupResource{
 | 
				
			||||||
 | 
							corev1.Resource("pods"),
 | 
				
			||||||
 | 
							corev1.Resource("replicationcontrollers"),
 | 
				
			||||||
 | 
							corev1.Resource("podtemplates"),
 | 
				
			||||||
 | 
							appsv1.Resource("replicasets"),
 | 
				
			||||||
 | 
							appsv1.Resource("deployments"),
 | 
				
			||||||
 | 
							appsv1.Resource("statefulsets"),
 | 
				
			||||||
 | 
							appsv1.Resource("daemonsets"),
 | 
				
			||||||
 | 
							batchv1.Resource("jobs"),
 | 
				
			||||||
 | 
							batchv1.Resource("cronjobs"),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						extractor := &DefaultPodSpecExtractor{}
 | 
				
			||||||
 | 
						for _, gr := range podLikeResources {
 | 
				
			||||||
 | 
							assert.True(t, extractor.HasPodSpec(gr), gr.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nonPodResources := []schema.GroupResource{
 | 
				
			||||||
 | 
							corev1.Resource("services"),
 | 
				
			||||||
 | 
							admissionv1.Resource("admissionreviews"),
 | 
				
			||||||
 | 
							appsv1.Resource("foobars"),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, gr := range nonPodResources {
 | 
				
			||||||
 | 
							assert.False(t, extractor.HasPodSpec(gr), gr.String())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type testEvaluator struct {
 | 
				
			||||||
 | 
						lv api.LevelVersion
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *testEvaluator) EvaluatePod(lv api.LevelVersion, meta *metav1.ObjectMeta, spec *corev1.PodSpec) []policy.CheckResult {
 | 
				
			||||||
 | 
						t.lv = lv
 | 
				
			||||||
 | 
						if meta.Annotations["error"] != "" {
 | 
				
			||||||
 | 
							return []policy.CheckResult{{Allowed: false, ForbiddenReason: meta.Annotations["error"]}}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return []policy.CheckResult{{Allowed: true}}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type testPodLister struct {
 | 
				
			||||||
 | 
						called bool
 | 
				
			||||||
 | 
						pods   []*corev1.Pod
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *testPodLister) ListPods(ctx context.Context, namespace string) ([]*corev1.Pod, error) {
 | 
				
			||||||
 | 
						t.called = true
 | 
				
			||||||
 | 
						return t.pods, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestValidateNamespace(t *testing.T) {
 | 
				
			||||||
 | 
						testcases := []struct {
 | 
				
			||||||
 | 
							name                 string
 | 
				
			||||||
 | 
							exemptNamespaces     []string
 | 
				
			||||||
 | 
							exemptRuntimeClasses []string
 | 
				
			||||||
 | 
							// override default policy
 | 
				
			||||||
 | 
							defaultPolicy *api.Policy
 | 
				
			||||||
 | 
							// request subresource
 | 
				
			||||||
 | 
							subresource string
 | 
				
			||||||
 | 
							// labels for the new namespace
 | 
				
			||||||
 | 
							newLabels map[string]string
 | 
				
			||||||
 | 
							// labels for the old namespace (only used if update=true)
 | 
				
			||||||
 | 
							oldLabels map[string]string
 | 
				
			||||||
 | 
							// list of pods to return
 | 
				
			||||||
 | 
							pods []*corev1.Pod
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							expectAllowed  bool
 | 
				
			||||||
 | 
							expectError    string
 | 
				
			||||||
 | 
							expectListPods bool
 | 
				
			||||||
 | 
							expectEvaluate api.LevelVersion
 | 
				
			||||||
 | 
							expectWarnings []string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// creation tests, just validate labels
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "create privileged",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelPrivileged), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "create baseline",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline)},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "create restricted",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline)},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "create malformed level",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: "unknown"},
 | 
				
			||||||
 | 
								expectAllowed:  false,
 | 
				
			||||||
 | 
								expectError:    `must be one of privileged, baseline, restricted`,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "create malformed version",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelPrivileged), api.EnforceVersionLabel: "unknown"},
 | 
				
			||||||
 | 
								expectAllowed:  false,
 | 
				
			||||||
 | 
								expectError:    `must be "latest" or "v1.x"`,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// update tests that don't tighten effective policy, no pod list/evaluate
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update no-op",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update no-op malformed level",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: "unknown"},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: "unknown"},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update no-op malformed version",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "unknown"},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "unknown"},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update relax level identical version",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update relax level explicit latest",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "latest"},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "latest"},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update relax level implicit latest",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline)},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted)},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update to explicit privileged",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelPrivileged)},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update to implicit privileged",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:             "update exempt to restricted",
 | 
				
			||||||
 | 
								exemptNamespaces: []string{"test"},
 | 
				
			||||||
 | 
								newLabels:        map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								oldLabels:        map[string]string{},
 | 
				
			||||||
 | 
								expectAllowed:    true,
 | 
				
			||||||
 | 
								expectListPods:   false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// update tests that introduce labels errors
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update malformed level",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: "unknown"},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								expectAllowed:  false,
 | 
				
			||||||
 | 
								expectError:    `must be one of privileged, baseline, restricted`,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update malformed version",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelPrivileged), api.EnforceVersionLabel: "unknown"},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								expectAllowed:  false,
 | 
				
			||||||
 | 
								expectError:    `must be "latest" or "v1.x"`,
 | 
				
			||||||
 | 
								expectListPods: false,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// update tests that tighten effective policy
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:           "update to implicit restricted",
 | 
				
			||||||
 | 
								newLabels:      map[string]string{},
 | 
				
			||||||
 | 
								oldLabels:      map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline), api.EnforceVersionLabel: "v1.0"},
 | 
				
			||||||
 | 
								defaultPolicy:  &api.Policy{Enforce: api.LevelVersion{Level: api.LevelRestricted, Version: api.LatestVersion()}},
 | 
				
			||||||
 | 
								expectAllowed:  true,
 | 
				
			||||||
 | 
								expectListPods: true,
 | 
				
			||||||
 | 
								expectEvaluate: api.LevelVersion{Level: api.LevelRestricted, Version: api.LatestVersion()},
 | 
				
			||||||
 | 
								expectWarnings: []string{"noruntimeclasspod: message", "runtimeclass1pod: message", "runtimeclass2pod: message"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name:                 "update with runtimeclass exempt pods",
 | 
				
			||||||
 | 
								exemptRuntimeClasses: []string{"runtimeclass1"},
 | 
				
			||||||
 | 
								newLabels:            map[string]string{api.EnforceLevelLabel: string(api.LevelRestricted)},
 | 
				
			||||||
 | 
								oldLabels:            map[string]string{api.EnforceLevelLabel: string(api.LevelBaseline)},
 | 
				
			||||||
 | 
								expectAllowed:        true,
 | 
				
			||||||
 | 
								expectListPods:       true,
 | 
				
			||||||
 | 
								expectEvaluate:       api.LevelVersion{Level: api.LevelRestricted, Version: api.LatestVersion()},
 | 
				
			||||||
 | 
								expectWarnings:       []string{"noruntimeclasspod: message", "runtimeclass2pod: message"},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: test for aggregating pods with identical warnings
 | 
				
			||||||
 | 
							// TODO: test for bounding evalution time with a warning
 | 
				
			||||||
 | 
							// TODO: test for bounding pod count with a warning
 | 
				
			||||||
 | 
							// TODO: test for prioritizing evaluating pods from unique controllers
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, tc := range testcases {
 | 
				
			||||||
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								newObject := &corev1.Namespace{
 | 
				
			||||||
 | 
									ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
										Name:   "test",
 | 
				
			||||||
 | 
										Labels: tc.newLabels,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								var operation = admission.Create
 | 
				
			||||||
 | 
								var oldObject runtime.Object
 | 
				
			||||||
 | 
								if tc.oldLabels != nil {
 | 
				
			||||||
 | 
									operation = admission.Update
 | 
				
			||||||
 | 
									oldObject = &corev1.Namespace{
 | 
				
			||||||
 | 
										ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
 | 
											Name:   "test",
 | 
				
			||||||
 | 
											Labels: tc.oldLabels,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								attrs := admission.NewAttributesRecord(
 | 
				
			||||||
 | 
									newObject,
 | 
				
			||||||
 | 
									oldObject,
 | 
				
			||||||
 | 
									schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Namespace"},
 | 
				
			||||||
 | 
									newObject.Name,
 | 
				
			||||||
 | 
									newObject.Name,
 | 
				
			||||||
 | 
									schema.GroupVersionResource{Group: "", Version: "v1", Resource: "namespaces"},
 | 
				
			||||||
 | 
									tc.subresource,
 | 
				
			||||||
 | 
									operation,
 | 
				
			||||||
 | 
									nil,
 | 
				
			||||||
 | 
									false,
 | 
				
			||||||
 | 
									nil,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								defaultPolicy := api.Policy{
 | 
				
			||||||
 | 
									Enforce: api.LevelVersion{
 | 
				
			||||||
 | 
										Level:   api.LevelPrivileged,
 | 
				
			||||||
 | 
										Version: api.LatestVersion(),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if tc.defaultPolicy != nil {
 | 
				
			||||||
 | 
									defaultPolicy = *tc.defaultPolicy
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pods := tc.pods
 | 
				
			||||||
 | 
								if pods == nil {
 | 
				
			||||||
 | 
									pods = []*corev1.Pod{
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ObjectMeta: metav1.ObjectMeta{Name: "noruntimeclasspod", Annotations: map[string]string{"error": "message"}},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ObjectMeta: metav1.ObjectMeta{Name: "runtimeclass1pod", Annotations: map[string]string{"error": "message"}},
 | 
				
			||||||
 | 
											Spec:       corev1.PodSpec{RuntimeClassName: pointer.String("runtimeclass1")},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											ObjectMeta: metav1.ObjectMeta{Name: "runtimeclass2pod", Annotations: map[string]string{"error": "message"}},
 | 
				
			||||||
 | 
											Spec:       corev1.PodSpec{RuntimeClassName: pointer.String("runtimeclass2")},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								podLister := &testPodLister{pods: pods}
 | 
				
			||||||
 | 
								evaluator := &testEvaluator{}
 | 
				
			||||||
 | 
								a := &Admission{
 | 
				
			||||||
 | 
									PodLister: podLister,
 | 
				
			||||||
 | 
									Evaluator: evaluator,
 | 
				
			||||||
 | 
									Configuration: &admissionapi.PodSecurityConfiguration{
 | 
				
			||||||
 | 
										Exemptions: admissionapi.PodSecurityExemptions{
 | 
				
			||||||
 | 
											Namespaces:     tc.exemptNamespaces,
 | 
				
			||||||
 | 
											RuntimeClasses: tc.exemptRuntimeClasses,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									defaultPolicy: defaultPolicy,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								result := a.ValidateNamespace(context.TODO(), attrs)
 | 
				
			||||||
 | 
								if result.Allowed != tc.expectAllowed {
 | 
				
			||||||
 | 
									t.Errorf("expected allowed=%v, got %v", tc.expectAllowed, result.Allowed)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								resultError := ""
 | 
				
			||||||
 | 
								if result.Result != nil {
 | 
				
			||||||
 | 
									resultError = result.Result.Message
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (len(resultError) > 0) != (len(tc.expectError) > 0) {
 | 
				
			||||||
 | 
									t.Errorf("expected error=%v, got %v", tc.expectError, resultError)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if len(tc.expectError) > 0 && !strings.Contains(resultError, tc.expectError) {
 | 
				
			||||||
 | 
									t.Errorf("expected error containing '%s', got %s", tc.expectError, resultError)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if podLister.called != tc.expectListPods {
 | 
				
			||||||
 | 
									t.Errorf("expected getPods=%v, got %v", tc.expectListPods, podLister.called)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if evaluator.lv != tc.expectEvaluate {
 | 
				
			||||||
 | 
									t.Errorf("expected to evaluate %v, got %v", tc.expectEvaluate, evaluator.lv)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(result.Warnings, tc.expectWarnings) {
 | 
				
			||||||
 | 
									t.Errorf("expected warnings:\n%v\ngot\n%v", tc.expectWarnings, result.Warnings)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								staging/src/k8s.io/pod-security-admission/admission/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								staging/src/k8s.io/pod-security-admission/admission/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2021 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Package admission contains PodSecurity admission logic
 | 
				
			||||||
 | 
					package admission
 | 
				
			||||||
@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2021 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package admission
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/client-go/kubernetes"
 | 
				
			||||||
 | 
						corev1listers "k8s.io/client-go/listers/core/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NamespaceGetterFromClient(client kubernetes.Interface) NamespaceGetter {
 | 
				
			||||||
 | 
						return &namespaceGetter{client: client}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NamespaceGetterFromListerAndClient(lister corev1listers.NamespaceLister, client kubernetes.Interface) NamespaceGetter {
 | 
				
			||||||
 | 
						return &namespaceGetter{lister: lister, client: client}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type namespaceGetter struct {
 | 
				
			||||||
 | 
						lister corev1listers.NamespaceLister
 | 
				
			||||||
 | 
						client kubernetes.Interface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (n *namespaceGetter) GetNamespace(ctx context.Context, name string) (namespace *corev1.Namespace, err error) {
 | 
				
			||||||
 | 
						if n.lister != nil {
 | 
				
			||||||
 | 
							namespace, err := n.lister.Get(name)
 | 
				
			||||||
 | 
							if err == nil || !apierrors.IsNotFound(err) {
 | 
				
			||||||
 | 
								return namespace, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return n.client.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								staging/src/k8s.io/pod-security-admission/admission/pods.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								staging/src/k8s.io/pod-security-admission/admission/pods.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2021 The Kubernetes Authors.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package admission
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						corev1 "k8s.io/api/core/v1"
 | 
				
			||||||
 | 
						metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
 | 
						"k8s.io/client-go/kubernetes"
 | 
				
			||||||
 | 
						corev1listers "k8s.io/client-go/listers/core/v1"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PodListerFromClient returns a PodLister that does live lists using the provided client.
 | 
				
			||||||
 | 
					func PodListerFromClient(client kubernetes.Interface) PodLister {
 | 
				
			||||||
 | 
						return &clientPodLister{client}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type clientPodLister struct {
 | 
				
			||||||
 | 
						client kubernetes.Interface
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *clientPodLister) ListPods(ctx context.Context, namespace string) ([]*corev1.Pod, error) {
 | 
				
			||||||
 | 
						list, err := p.client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						pods := make([]*corev1.Pod, len(list.Items))
 | 
				
			||||||
 | 
						for i := range list.Items {
 | 
				
			||||||
 | 
							pods[i] = &list.Items[i]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return pods, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PodListerFromInformer returns a PodLister that does cached lists using the provided lister.
 | 
				
			||||||
 | 
					func PodListerFromInformer(lister corev1listers.PodLister) PodLister {
 | 
				
			||||||
 | 
						return &informerPodLister{lister}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type informerPodLister struct {
 | 
				
			||||||
 | 
						lister corev1listers.PodLister
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (p *informerPodLister) ListPods(ctx context.Context, namespace string) ([]*corev1.Pod, error) {
 | 
				
			||||||
 | 
						return p.lister.Pods(namespace).List(labels.Everything())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user