k8s.io/apiserver@v0.31.1/pkg/cel/environment/base.go (about)

     1  /*
     2  Copyright 2023 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 environment
    18  
    19  import (
    20  	"fmt"
    21  	"strconv"
    22  	"sync"
    23  	"sync/atomic"
    24  
    25  	"github.com/google/cel-go/cel"
    26  	"github.com/google/cel-go/checker"
    27  	"github.com/google/cel-go/ext"
    28  	"github.com/google/cel-go/interpreter"
    29  	"golang.org/x/sync/singleflight"
    30  
    31  	"k8s.io/apimachinery/pkg/util/version"
    32  	celconfig "k8s.io/apiserver/pkg/apis/cel"
    33  	"k8s.io/apiserver/pkg/cel/library"
    34  	genericfeatures "k8s.io/apiserver/pkg/features"
    35  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    36  	utilversion "k8s.io/apiserver/pkg/util/version"
    37  )
    38  
    39  // DefaultCompatibilityVersion returns a default compatibility version for use with EnvSet
    40  // that guarantees compatibility with CEL features/libraries/parameters understood by
    41  // the api server min compatibility version
    42  //
    43  // This default will be set to no more than the current Kubernetes major.minor version.
    44  //
    45  // Note that a default version number less than n-1 the current Kubernetes major.minor version
    46  // indicates a wider range of version compatibility than strictly required for rollback.
    47  // A wide range of compatibility is desirable because it means that CEL expressions are portable
    48  // across a wider range of Kubernetes versions.
    49  // A default version number equal to the current Kubernetes major.minor version
    50  // indicates fast forward CEL features that can be used when rollback is no longer needed.
    51  func DefaultCompatibilityVersion() *version.Version {
    52  	effectiveVer := utilversion.DefaultComponentGlobalsRegistry.EffectiveVersionFor(utilversion.DefaultKubeComponent)
    53  	if effectiveVer == nil {
    54  		effectiveVer = utilversion.DefaultKubeEffectiveVersion()
    55  	}
    56  	return effectiveVer.MinCompatibilityVersion()
    57  }
    58  
    59  var baseOpts = append(baseOptsWithoutStrictCost, StrictCostOpt)
    60  
    61  var baseOptsWithoutStrictCost = []VersionedOptions{
    62  	{
    63  		// CEL epoch was actually 1.23, but we artificially set it to 1.0 because these
    64  		// options should always be present.
    65  		IntroducedVersion: version.MajorMinor(1, 0),
    66  		EnvOptions: []cel.EnvOption{
    67  			cel.HomogeneousAggregateLiterals(),
    68  			// Validate function declarations once during base env initialization,
    69  			// so they don't need to be evaluated each time a CEL rule is compiled.
    70  			// This is a relatively expensive operation.
    71  			cel.EagerlyValidateDeclarations(true),
    72  			cel.DefaultUTCTimeZone(true),
    73  
    74  			library.URLs(),
    75  			library.Regex(),
    76  			library.Lists(),
    77  
    78  			// cel-go v0.17.7 change the cost of has() from 0 to 1, but also provided the CostEstimatorOptions option to preserve the old behavior, so we enabled it at the same time we bumped our cel version to v0.17.7.
    79  			// Since it is a regression fix, we apply it uniformly to all code use v0.17.7.
    80  			cel.CostEstimatorOptions(checker.PresenceTestHasCost(false)),
    81  		},
    82  		ProgramOptions: []cel.ProgramOption{
    83  			cel.EvalOptions(cel.OptOptimize, cel.OptTrackCost),
    84  			cel.CostLimit(celconfig.PerCallLimit),
    85  
    86  			// cel-go v0.17.7 change the cost of has() from 0 to 1, but also provided the CostEstimatorOptions option to preserve the old behavior, so we enabled it at the same time we bumped our cel version to v0.17.7.
    87  			// Since it is a regression fix, we apply it uniformly to all code use v0.17.7.
    88  			cel.CostTrackerOptions(interpreter.PresenceTestHasCost(false)),
    89  		},
    90  	},
    91  	{
    92  		IntroducedVersion: version.MajorMinor(1, 27),
    93  		EnvOptions: []cel.EnvOption{
    94  			library.Authz(),
    95  		},
    96  	},
    97  	{
    98  		IntroducedVersion: version.MajorMinor(1, 28),
    99  		EnvOptions: []cel.EnvOption{
   100  			cel.CrossTypeNumericComparisons(true),
   101  			cel.OptionalTypes(),
   102  			library.Quantity(),
   103  		},
   104  	},
   105  	// add the new validator in 1.29
   106  	{
   107  		IntroducedVersion: version.MajorMinor(1, 29),
   108  		EnvOptions: []cel.EnvOption{
   109  			cel.ASTValidators(
   110  				cel.ValidateDurationLiterals(),
   111  				cel.ValidateTimestampLiterals(),
   112  				cel.ValidateRegexLiterals(),
   113  				cel.ValidateHomogeneousAggregateLiterals(),
   114  			),
   115  		},
   116  	},
   117  	// String library
   118  	{
   119  		IntroducedVersion: version.MajorMinor(1, 0),
   120  		RemovedVersion:    version.MajorMinor(1, 29),
   121  		EnvOptions: []cel.EnvOption{
   122  			ext.Strings(ext.StringsVersion(0)),
   123  		},
   124  	},
   125  	{
   126  		IntroducedVersion: version.MajorMinor(1, 29),
   127  		EnvOptions: []cel.EnvOption{
   128  			ext.Strings(ext.StringsVersion(2)),
   129  		},
   130  	},
   131  	// Set library
   132  	{
   133  		IntroducedVersion: version.MajorMinor(1, 29),
   134  		EnvOptions: []cel.EnvOption{
   135  			ext.Sets(),
   136  		},
   137  	},
   138  	{
   139  		IntroducedVersion: version.MajorMinor(1, 30),
   140  		EnvOptions: []cel.EnvOption{
   141  			library.IP(),
   142  			library.CIDR(),
   143  		},
   144  	},
   145  	// Format Library
   146  	{
   147  		IntroducedVersion: version.MajorMinor(1, 31),
   148  		EnvOptions: []cel.EnvOption{
   149  			library.Format(),
   150  		},
   151  	},
   152  	// Authz selectors
   153  	{
   154  		IntroducedVersion: version.MajorMinor(1, 31),
   155  		FeatureEnabled: func() bool {
   156  			enabled := utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors)
   157  			authzSelectorsLibraryInit.Do(func() {
   158  				// Record the first time feature enablement was checked for this library.
   159  				// This is checked from integration tests to ensure no cached cel envs
   160  				// are constructed before feature enablement is effectively set.
   161  				authzSelectorsLibraryEnabled.Store(enabled)
   162  				// Uncomment to debug where the first initialization is coming from if needed.
   163  				// debug.PrintStack()
   164  			})
   165  			return enabled
   166  		},
   167  		EnvOptions: []cel.EnvOption{
   168  			library.AuthzSelectors(),
   169  		},
   170  	},
   171  }
   172  
   173  var (
   174  	authzSelectorsLibraryInit    sync.Once
   175  	authzSelectorsLibraryEnabled atomic.Value
   176  )
   177  
   178  // AuthzSelectorsLibraryEnabled returns whether the AuthzSelectors library was enabled when it was constructed.
   179  // If it has not been contructed yet, this returns `false, false`.
   180  // This is solely for the benefit of the integration tests making sure feature gates get correctly parsed before AuthzSelector ever has to check for enablement.
   181  func AuthzSelectorsLibraryEnabled() (enabled, constructed bool) {
   182  	enabled, constructed = authzSelectorsLibraryEnabled.Load().(bool)
   183  	return
   184  }
   185  
   186  var StrictCostOpt = VersionedOptions{
   187  	// This is to configure the cost calculation for extended libraries
   188  	IntroducedVersion: version.MajorMinor(1, 0),
   189  	ProgramOptions: []cel.ProgramOption{
   190  		cel.CostTracking(&library.CostEstimator{}),
   191  	},
   192  }
   193  
   194  // MustBaseEnvSet returns the common CEL base environments for Kubernetes for Version, or panics
   195  // if the version is nil, or does not have major and minor components.
   196  //
   197  // The returned environment contains function libraries, language settings, optimizations and
   198  // runtime cost limits appropriate CEL as it is used in Kubernetes.
   199  //
   200  // The returned environment contains no CEL variable definitions or custom type declarations and
   201  // should be extended to construct environments with the appropriate variable definitions,
   202  // type declarations and any other needed configuration.
   203  // strictCost is used to determine whether to enforce strict cost calculation for CEL expressions.
   204  func MustBaseEnvSet(ver *version.Version, strictCost bool) *EnvSet {
   205  	if ver == nil {
   206  		panic("version must be non-nil")
   207  	}
   208  	if len(ver.Components()) < 2 {
   209  		panic(fmt.Sprintf("version must contain an major and minor component, but got: %s", ver.String()))
   210  	}
   211  	key := strconv.FormatUint(uint64(ver.Major()), 10) + "." + strconv.FormatUint(uint64(ver.Minor()), 10)
   212  	var entry interface{}
   213  	if strictCost {
   214  		if entry, ok := baseEnvs.Load(key); ok {
   215  			return entry.(*EnvSet)
   216  		}
   217  		entry, _, _ = baseEnvsSingleflight.Do(key, func() (interface{}, error) {
   218  			entry := mustNewEnvSet(ver, baseOpts)
   219  			baseEnvs.Store(key, entry)
   220  			return entry, nil
   221  		})
   222  	} else {
   223  		if entry, ok := baseEnvsWithOption.Load(key); ok {
   224  			return entry.(*EnvSet)
   225  		}
   226  		entry, _, _ = baseEnvsWithOptionSingleflight.Do(key, func() (interface{}, error) {
   227  			entry := mustNewEnvSet(ver, baseOptsWithoutStrictCost)
   228  			baseEnvsWithOption.Store(key, entry)
   229  			return entry, nil
   230  		})
   231  	}
   232  
   233  	return entry.(*EnvSet)
   234  }
   235  
   236  var (
   237  	baseEnvs                       = sync.Map{}
   238  	baseEnvsWithOption             = sync.Map{}
   239  	baseEnvsSingleflight           = &singleflight.Group{}
   240  	baseEnvsWithOptionSingleflight = &singleflight.Group{}
   241  )