...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/certificates/ctbattest/admission.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/certificates/ctbattest

     1  /*
     2  Copyright 2022 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 ctbattest
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  
    24  	"k8s.io/apiserver/pkg/admission"
    25  	genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
    26  	"k8s.io/apiserver/pkg/authorization/authorizer"
    27  	"k8s.io/component-base/featuregate"
    28  	"k8s.io/klog/v2"
    29  	api "k8s.io/kubernetes/pkg/apis/certificates"
    30  	kapihelper "k8s.io/kubernetes/pkg/apis/core/helper"
    31  	"k8s.io/kubernetes/pkg/features"
    32  	"k8s.io/kubernetes/pkg/registry/rbac"
    33  	"k8s.io/kubernetes/plugin/pkg/admission/certificates"
    34  )
    35  
    36  const PluginName = "ClusterTrustBundleAttest"
    37  
    38  func Register(plugins *admission.Plugins) {
    39  	plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
    40  		return NewPlugin(), nil
    41  	})
    42  }
    43  
    44  // Plugin is the ClusterTrustBundle attest plugin.
    45  //
    46  // In order to create or update a ClusterTrustBundle that sets signerName,
    47  // you must have the following permission: group=certificates.k8s.io
    48  // resource=signers resourceName=<the signer name> verb=attest.
    49  type Plugin struct {
    50  	*admission.Handler
    51  	authz authorizer.Authorizer
    52  
    53  	inspectedFeatureGates bool
    54  	enabled               bool
    55  }
    56  
    57  var _ admission.ValidationInterface = &Plugin{}
    58  var _ admission.InitializationValidator = &Plugin{}
    59  var _ genericadmissioninit.WantsAuthorizer = &Plugin{}
    60  var _ genericadmissioninit.WantsFeatures = &Plugin{}
    61  
    62  func NewPlugin() *Plugin {
    63  	return &Plugin{
    64  		Handler: admission.NewHandler(admission.Create, admission.Update),
    65  	}
    66  }
    67  
    68  // SetAuthorizer sets the plugin's authorizer.
    69  func (p *Plugin) SetAuthorizer(authz authorizer.Authorizer) {
    70  	p.authz = authz
    71  }
    72  
    73  // InspectFeatureGates implements WantsFeatures.
    74  func (p *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
    75  	p.enabled = featureGates.Enabled(features.ClusterTrustBundle)
    76  	p.inspectedFeatureGates = true
    77  }
    78  
    79  // ValidateInitialization checks that the plugin was initialized correctly.
    80  func (p *Plugin) ValidateInitialization() error {
    81  	if p.authz == nil {
    82  		return fmt.Errorf("%s requires an authorizer", PluginName)
    83  	}
    84  	if !p.inspectedFeatureGates {
    85  		return fmt.Errorf("%s did not see feature gates", PluginName)
    86  	}
    87  	return nil
    88  }
    89  
    90  var clusterTrustBundleGroupResource = api.Resource("clustertrustbundles")
    91  
    92  func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, _ admission.ObjectInterfaces) error {
    93  	if !p.enabled {
    94  		return nil
    95  	}
    96  	if a.GetResource().GroupResource() != clusterTrustBundleGroupResource {
    97  		return nil
    98  	}
    99  
   100  	newBundle, ok := a.GetObject().(*api.ClusterTrustBundle)
   101  	if !ok {
   102  		return admission.NewForbidden(a, fmt.Errorf("expected type ClusterTrustBundle, got: %T", a.GetOldObject()))
   103  	}
   104  
   105  	// Unlike CSRs, it's OK to validate against the *new* object, because
   106  	// updates to signer name will be rejected during validation.
   107  
   108  	// If signer name isn't specified, we don't need to perform the
   109  	// attest check.
   110  	if newBundle.Spec.SignerName == "" {
   111  		return nil
   112  	}
   113  
   114  	// Skip the attest check when the semantics of the bundle are unchanged to support storage migration and GC workflows
   115  	if a.GetOperation() == admission.Update && rbac.IsOnlyMutatingGCFields(a.GetObject(), a.GetOldObject(), kapihelper.Semantic) {
   116  		return nil
   117  	}
   118  
   119  	if !certificates.IsAuthorizedForSignerName(ctx, p.authz, a.GetUserInfo(), "attest", newBundle.Spec.SignerName) {
   120  		klog.V(4).Infof("user not permitted to attest ClusterTrustBundle %q with signerName %q", newBundle.Name, newBundle.Spec.SignerName)
   121  		return admission.NewForbidden(a, fmt.Errorf("user not permitted to attest for signerName %q", newBundle.Spec.SignerName))
   122  	}
   123  
   124  	return nil
   125  }
   126  

View as plain text