k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/generic/policy_source.go (about)

     1  /*
     2  Copyright 2024 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 generic
    18  
    19  import (
    20  	"context"
    21  	goerrors "errors"
    22  	"fmt"
    23  	"sync"
    24  	"sync/atomic"
    25  	"time"
    26  
    27  	corev1 "k8s.io/api/core/v1"
    28  	"k8s.io/apimachinery/pkg/api/errors"
    29  	"k8s.io/apimachinery/pkg/api/meta"
    30  	"k8s.io/apimachinery/pkg/labels"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	"k8s.io/apimachinery/pkg/types"
    34  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    35  	"k8s.io/apimachinery/pkg/util/wait"
    36  	"k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic"
    37  	"k8s.io/client-go/dynamic"
    38  	"k8s.io/client-go/dynamic/dynamicinformer"
    39  	"k8s.io/client-go/informers"
    40  	"k8s.io/client-go/tools/cache"
    41  	"k8s.io/klog/v2"
    42  )
    43  
    44  type policySource[P runtime.Object, B runtime.Object, E Evaluator] struct {
    45  	ctx                context.Context
    46  	policyInformer     generic.Informer[P]
    47  	bindingInformer    generic.Informer[B]
    48  	restMapper         meta.RESTMapper
    49  	newPolicyAccessor  func(P) PolicyAccessor
    50  	newBindingAccessor func(B) BindingAccessor
    51  
    52  	informerFactory informers.SharedInformerFactory
    53  	dynamicClient   dynamic.Interface
    54  
    55  	compiler func(P) E
    56  
    57  	// Currently compiled list of valid/active policy-binding pairs
    58  	policies atomic.Pointer[[]PolicyHook[P, B, E]]
    59  	// Whether the cache of policies is dirty and needs to be recompiled
    60  	policiesDirty atomic.Bool
    61  
    62  	lock             sync.Mutex
    63  	compiledPolicies map[types.NamespacedName]compiledPolicyEntry[E]
    64  
    65  	// Temporary until we use the dynamic informer factory
    66  	paramsCRDControllers map[schema.GroupVersionKind]*paramInfo
    67  }
    68  
    69  type paramInfo struct {
    70  	mapping meta.RESTMapping
    71  
    72  	// When the param is changed, or the informer is done being used, the cancel
    73  	// func should be called to stop/cleanup the original informer
    74  	cancelFunc func()
    75  
    76  	// The lister for this param
    77  	informer informers.GenericInformer
    78  }
    79  
    80  type compiledPolicyEntry[E Evaluator] struct {
    81  	policyVersion string
    82  	evaluator     E
    83  }
    84  
    85  type PolicyHook[P runtime.Object, B runtime.Object, E Evaluator] struct {
    86  	Policy   P
    87  	Bindings []B
    88  
    89  	// ParamInformer is the informer for the param CRD for this policy, or nil if
    90  	// there is no param or if there was a configuration error
    91  	ParamInformer informers.GenericInformer
    92  	ParamScope    meta.RESTScope
    93  
    94  	Evaluator          E
    95  	ConfigurationError error
    96  }
    97  
    98  var _ Source[PolicyHook[runtime.Object, runtime.Object, Evaluator]] = &policySource[runtime.Object, runtime.Object, Evaluator]{}
    99  
   100  func NewPolicySource[P runtime.Object, B runtime.Object, E Evaluator](
   101  	policyInformer cache.SharedIndexInformer,
   102  	bindingInformer cache.SharedIndexInformer,
   103  	newPolicyAccessor func(P) PolicyAccessor,
   104  	newBindingAccessor func(B) BindingAccessor,
   105  	compiler func(P) E,
   106  	paramInformerFactory informers.SharedInformerFactory,
   107  	dynamicClient dynamic.Interface,
   108  	restMapper meta.RESTMapper,
   109  ) Source[PolicyHook[P, B, E]] {
   110  	res := &policySource[P, B, E]{
   111  		compiler:             compiler,
   112  		policyInformer:       generic.NewInformer[P](policyInformer),
   113  		bindingInformer:      generic.NewInformer[B](bindingInformer),
   114  		compiledPolicies:     map[types.NamespacedName]compiledPolicyEntry[E]{},
   115  		newPolicyAccessor:    newPolicyAccessor,
   116  		newBindingAccessor:   newBindingAccessor,
   117  		paramsCRDControllers: map[schema.GroupVersionKind]*paramInfo{},
   118  		informerFactory:      paramInformerFactory,
   119  		dynamicClient:        dynamicClient,
   120  		restMapper:           restMapper,
   121  	}
   122  	return res
   123  }
   124  
   125  func (s *policySource[P, B, E]) Run(ctx context.Context) error {
   126  	if s.ctx != nil {
   127  		return fmt.Errorf("policy source already running")
   128  	}
   129  
   130  	// Wait for initial cache sync of policies and informers before reconciling
   131  	// any
   132  	if !cache.WaitForNamedCacheSync(fmt.Sprintf("%T", s), ctx.Done(), s.UpstreamHasSynced) {
   133  		err := ctx.Err()
   134  		if err == nil {
   135  			err = fmt.Errorf("initial cache sync for %T failed", s)
   136  		}
   137  		return err
   138  	}
   139  
   140  	s.ctx = ctx
   141  
   142  	// Perform initial policy compilation after initial list has finished
   143  	s.notify()
   144  	s.refreshPolicies()
   145  
   146  	notifyFuncs := cache.ResourceEventHandlerFuncs{
   147  		AddFunc: func(_ interface{}) {
   148  			s.notify()
   149  		},
   150  		UpdateFunc: func(_, _ interface{}) {
   151  			s.notify()
   152  		},
   153  		DeleteFunc: func(_ interface{}) {
   154  			s.notify()
   155  		},
   156  	}
   157  	handle, err := s.policyInformer.AddEventHandler(notifyFuncs)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	defer func() {
   162  		if err := s.policyInformer.RemoveEventHandler(handle); err != nil {
   163  			utilruntime.HandleError(fmt.Errorf("failed to remove policy event handler: %w", err))
   164  		}
   165  	}()
   166  
   167  	bindingHandle, err := s.bindingInformer.AddEventHandler(notifyFuncs)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	defer func() {
   172  		if err := s.bindingInformer.RemoveEventHandler(bindingHandle); err != nil {
   173  			utilruntime.HandleError(fmt.Errorf("failed to remove binding event handler: %w", err))
   174  		}
   175  	}()
   176  
   177  	// Start a worker that checks every second to see if policy data is dirty
   178  	// and needs to be recompiled
   179  	go func() {
   180  		// Loop every 1 second until context is cancelled, refreshing policies
   181  		wait.Until(s.refreshPolicies, 1*time.Second, ctx.Done())
   182  	}()
   183  
   184  	<-ctx.Done()
   185  	return nil
   186  }
   187  
   188  func (s *policySource[P, B, E]) UpstreamHasSynced() bool {
   189  	return s.policyInformer.HasSynced() && s.bindingInformer.HasSynced()
   190  }
   191  
   192  // HasSynced implements Source.
   193  func (s *policySource[P, B, E]) HasSynced() bool {
   194  	// As an invariant we never store `nil` into the atomic list of processed
   195  	// policy hooks. If it is nil, then we haven't compiled all the policies
   196  	// and stored them yet.
   197  	return s.Hooks() != nil
   198  }
   199  
   200  // Hooks implements Source.
   201  func (s *policySource[P, B, E]) Hooks() []PolicyHook[P, B, E] {
   202  	res := s.policies.Load()
   203  
   204  	// Error case should not happen since evaluation function never
   205  	// returns error
   206  	if res == nil {
   207  		// Not yet synced
   208  		return nil
   209  	}
   210  
   211  	return *res
   212  }
   213  
   214  func (s *policySource[P, B, E]) refreshPolicies() {
   215  	if !s.UpstreamHasSynced() {
   216  		return
   217  	} else if !s.policiesDirty.Swap(false) {
   218  		return
   219  	}
   220  
   221  	// It is ok the cache gets marked dirty again between us clearing the
   222  	// flag and us calculating the policies. The dirty flag would be marked again,
   223  	// and we'd have a no-op after comparing resource versions on the next sync.
   224  	klog.Infof("refreshing policies")
   225  	policies, err := s.calculatePolicyData()
   226  
   227  	// Intentionally store policy list regardless of error. There may be
   228  	// an error returned if there was a configuration error in one of the policies,
   229  	// but we would still want those policies evaluated
   230  	// (for instance to return error on failaction). Or if there was an error
   231  	// listing all policies at all, we would want to wipe the list.
   232  	s.policies.Store(&policies)
   233  
   234  	if err != nil {
   235  		// An error was generated while syncing policies. Mark it as dirty again
   236  		// so we can retry later
   237  		utilruntime.HandleError(fmt.Errorf("encountered error syncing policies: %w. Rescheduling policy sync", err))
   238  		s.notify()
   239  	}
   240  }
   241  
   242  func (s *policySource[P, B, E]) notify() {
   243  	s.policiesDirty.Store(true)
   244  }
   245  
   246  // calculatePolicyData calculates the list of policies and bindings for each
   247  // policy. If there is an error in generation, it will return the error and
   248  // the partial list of policies that were able to be generated. Policies that
   249  // have an error will have a non-nil ConfigurationError field, but still be
   250  // included in the result.
   251  //
   252  // This function caches the result of the intermediate compilations
   253  func (s *policySource[P, B, E]) calculatePolicyData() ([]PolicyHook[P, B, E], error) {
   254  	if !s.UpstreamHasSynced() {
   255  		return nil, fmt.Errorf("cannot calculate policy data until upstream has synced")
   256  	}
   257  
   258  	// Fat-fingered lock that can be made more fine-tuned if required
   259  	s.lock.Lock()
   260  	defer s.lock.Unlock()
   261  
   262  	// Create a local copy of all policies and bindings
   263  	policiesToBindings := map[types.NamespacedName][]B{}
   264  	bindingList, err := s.bindingInformer.List(labels.Everything())
   265  	if err != nil {
   266  		// This should never happen unless types are misconfigured
   267  		// (can't use meta.accessor on them)
   268  		return nil, err
   269  	}
   270  
   271  	// Gather a list of all active policy bindings
   272  	for _, bindingSpec := range bindingList {
   273  		bindingAccessor := s.newBindingAccessor(bindingSpec)
   274  		policyKey := bindingAccessor.GetPolicyName()
   275  
   276  		// Add this binding to the list of bindings for this policy
   277  		policiesToBindings[policyKey] = append(policiesToBindings[policyKey], bindingSpec)
   278  	}
   279  
   280  	result := make([]PolicyHook[P, B, E], 0, len(bindingList))
   281  	usedParams := map[schema.GroupVersionKind]struct{}{}
   282  	var errs []error
   283  	for policyKey, bindingSpecs := range policiesToBindings {
   284  		var inf generic.NamespacedLister[P] = s.policyInformer
   285  		if len(policyKey.Namespace) > 0 {
   286  			inf = s.policyInformer.Namespaced(policyKey.Namespace)
   287  		}
   288  		policySpec, err := inf.Get(policyKey.Name)
   289  		if errors.IsNotFound(err) {
   290  			// Policy for bindings doesn't exist. This can happen if the policy
   291  			// was deleted before the binding, or the binding was created first.
   292  			//
   293  			// Just skip bindings that refer to non-existent policies
   294  			// If the policy is recreated, the cache will be marked dirty and
   295  			// this function will run again.
   296  			continue
   297  		} else if err != nil {
   298  			// This should never happen since fetching from a cache should never
   299  			// fail and this function checks that the cache was synced before
   300  			// even getting to this point.
   301  			errs = append(errs, err)
   302  			continue
   303  		}
   304  
   305  		var parsedParamKind *schema.GroupVersionKind
   306  		policyAccessor := s.newPolicyAccessor(policySpec)
   307  
   308  		if paramKind := policyAccessor.GetParamKind(); paramKind != nil {
   309  			groupVersion, err := schema.ParseGroupVersion(paramKind.APIVersion)
   310  			if err != nil {
   311  				errs = append(errs, fmt.Errorf("failed to parse paramKind APIVersion: %w", err))
   312  				continue
   313  			}
   314  			parsedParamKind = &schema.GroupVersionKind{
   315  				Group:   groupVersion.Group,
   316  				Version: groupVersion.Version,
   317  				Kind:    paramKind.Kind,
   318  			}
   319  
   320  			// TEMPORARY UNTIL WE HAVE SHARED PARAM INFORMERS
   321  			usedParams[*parsedParamKind] = struct{}{}
   322  		}
   323  
   324  		paramInformer, paramScope, configurationError := s.ensureParamsForPolicyLocked(parsedParamKind)
   325  		result = append(result, PolicyHook[P, B, E]{
   326  			Policy:             policySpec,
   327  			Bindings:           bindingSpecs,
   328  			Evaluator:          s.compilePolicyLocked(policySpec),
   329  			ParamInformer:      paramInformer,
   330  			ParamScope:         paramScope,
   331  			ConfigurationError: configurationError,
   332  		})
   333  
   334  		// Should queue a re-sync for policy sync error. If our shared param
   335  		// informer can notify us when CRD discovery changes we can remove this
   336  		// and just rely on the informer to notify us when the CRDs change
   337  		if configurationError != nil {
   338  			errs = append(errs, configurationError)
   339  		}
   340  	}
   341  
   342  	// Clean up orphaned policies by replacing the old cache of compiled policies
   343  	// (the map of used policies is updated by `compilePolicy`)
   344  	for policyKey := range s.compiledPolicies {
   345  		if _, wasSeen := policiesToBindings[policyKey]; !wasSeen {
   346  			delete(s.compiledPolicies, policyKey)
   347  		}
   348  	}
   349  
   350  	// Clean up orphaned param informers
   351  	for paramKind, info := range s.paramsCRDControllers {
   352  		if _, wasSeen := usedParams[paramKind]; !wasSeen {
   353  			info.cancelFunc()
   354  			delete(s.paramsCRDControllers, paramKind)
   355  		}
   356  	}
   357  
   358  	err = nil
   359  	if len(errs) > 0 {
   360  		err = goerrors.Join(errs...)
   361  	}
   362  	return result, err
   363  }
   364  
   365  // ensureParamsForPolicyLocked ensures that the informer for the paramKind is
   366  // started and returns the informer and the scope of the paramKind.
   367  //
   368  // Must be called under write lock
   369  func (s *policySource[P, B, E]) ensureParamsForPolicyLocked(paramSource *schema.GroupVersionKind) (informers.GenericInformer, meta.RESTScope, error) {
   370  	if paramSource == nil {
   371  		return nil, nil, nil
   372  	} else if info, ok := s.paramsCRDControllers[*paramSource]; ok {
   373  		return info.informer, info.mapping.Scope, nil
   374  	}
   375  
   376  	mapping, err := s.restMapper.RESTMapping(schema.GroupKind{
   377  		Group: paramSource.Group,
   378  		Kind:  paramSource.Kind,
   379  	}, paramSource.Version)
   380  
   381  	if err != nil {
   382  		// Failed to resolve. Return error so we retry again (rate limited)
   383  		// Save a record of this definition with an evaluator that unconditionally
   384  		return nil, nil, fmt.Errorf("failed to find resource referenced by paramKind: '%v'", *paramSource)
   385  	}
   386  
   387  	// We are not watching this param. Start an informer for it.
   388  	instanceContext, instanceCancel := context.WithCancel(s.ctx)
   389  
   390  	var informer informers.GenericInformer
   391  
   392  	// Try to see if our provided informer factory has an informer for this type.
   393  	// We assume the informer is already started, and starts all types associated
   394  	// with it.
   395  	if genericInformer, err := s.informerFactory.ForResource(mapping.Resource); err == nil {
   396  		informer = genericInformer
   397  
   398  		// Start the informer
   399  		s.informerFactory.Start(instanceContext.Done())
   400  
   401  	} else {
   402  		// Dynamic JSON informer fallback.
   403  		// Cannot use shared dynamic informer since it would be impossible
   404  		// to clean CRD informers properly with multiple dependents
   405  		// (cannot start ahead of time, and cannot track dependencies via stopCh)
   406  		informer = dynamicinformer.NewFilteredDynamicInformer(
   407  			s.dynamicClient,
   408  			mapping.Resource,
   409  			corev1.NamespaceAll,
   410  			// Use same interval as is used for k8s typed sharedInformerFactory
   411  			// https://github.com/kubernetes/kubernetes/blob/7e0923899fed622efbc8679cca6b000d43633e38/cmd/kube-apiserver/app/server.go#L430
   412  			10*time.Minute,
   413  			cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
   414  			nil,
   415  		)
   416  		go informer.Informer().Run(instanceContext.Done())
   417  	}
   418  
   419  	klog.Infof("informer started for %v", *paramSource)
   420  	ret := &paramInfo{
   421  		mapping:    *mapping,
   422  		cancelFunc: instanceCancel,
   423  		informer:   informer,
   424  	}
   425  	s.paramsCRDControllers[*paramSource] = ret
   426  	return ret.informer, mapping.Scope, nil
   427  }
   428  
   429  // For testing
   430  func (s *policySource[P, B, E]) getParamInformer(param schema.GroupVersionKind) (informers.GenericInformer, meta.RESTScope) {
   431  	s.lock.Lock()
   432  	defer s.lock.Unlock()
   433  
   434  	if info, ok := s.paramsCRDControllers[param]; ok {
   435  		return info.informer, info.mapping.Scope
   436  	}
   437  
   438  	return nil, nil
   439  }
   440  
   441  // compilePolicyLocked compiles the policy and returns the evaluator for it.
   442  // If the policy has not changed since the last compilation, it will return
   443  // the cached evaluator.
   444  //
   445  // Must be called under write lock
   446  func (s *policySource[P, B, E]) compilePolicyLocked(policySpec P) E {
   447  	policyMeta, err := meta.Accessor(policySpec)
   448  	if err != nil {
   449  		// This should not happen if P, and B have ObjectMeta, but
   450  		// unfortunately there is no way to express "able to call
   451  		// meta.Accessor" as a type constraint
   452  		utilruntime.HandleError(err)
   453  		var emptyEvaluator E
   454  		return emptyEvaluator
   455  	}
   456  	key := types.NamespacedName{
   457  		Namespace: policyMeta.GetNamespace(),
   458  		Name:      policyMeta.GetName(),
   459  	}
   460  
   461  	compiledPolicy, wasCompiled := s.compiledPolicies[key]
   462  
   463  	// If the policy or binding has changed since it was last compiled,
   464  	// and if there is no configuration error (like a missing param CRD)
   465  	// then we recompile
   466  	if !wasCompiled ||
   467  		compiledPolicy.policyVersion != policyMeta.GetResourceVersion() {
   468  
   469  		compiledPolicy = compiledPolicyEntry[E]{
   470  			policyVersion: policyMeta.GetResourceVersion(),
   471  			evaluator:     s.compiler(policySpec),
   472  		}
   473  		s.compiledPolicies[key] = compiledPolicy
   474  	}
   475  
   476  	return compiledPolicy.evaluator
   477  }