...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity/admission.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/security/podsecurity

     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  }
   286  

View as plain text