2023-08-17 05:15:28 +00:00
/ *
Copyright 2023 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 environment
import (
"fmt"
"strconv"
"sync"
2024-08-19 08:01:33 +00:00
"sync/atomic"
2023-08-17 05:15:28 +00:00
"github.com/google/cel-go/cel"
2023-12-20 12:23:59 +00:00
"github.com/google/cel-go/checker"
2023-08-17 05:15:28 +00:00
"github.com/google/cel-go/ext"
2023-12-20 12:23:59 +00:00
"github.com/google/cel-go/interpreter"
2023-08-17 05:15:28 +00:00
"golang.org/x/sync/singleflight"
"k8s.io/apimachinery/pkg/util/version"
celconfig "k8s.io/apiserver/pkg/apis/cel"
"k8s.io/apiserver/pkg/cel/library"
2024-08-19 08:01:33 +00:00
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilversion "k8s.io/apiserver/pkg/util/version"
2023-08-17 05:15:28 +00:00
)
// DefaultCompatibilityVersion returns a default compatibility version for use with EnvSet
// that guarantees compatibility with CEL features/libraries/parameters understood by
2024-08-19 08:01:33 +00:00
// the api server min compatibility version
2023-08-17 05:15:28 +00:00
//
2024-08-19 08:01:33 +00:00
// This default will be set to no more than the current Kubernetes major.minor version.
2023-08-17 05:15:28 +00:00
//
2024-08-19 08:01:33 +00:00
// Note that a default version number less than n-1 the current Kubernetes major.minor version
// indicates a wider range of version compatibility than strictly required for rollback.
// A wide range of compatibility is desirable because it means that CEL expressions are portable
// across a wider range of Kubernetes versions.
// A default version number equal to the current Kubernetes major.minor version
// indicates fast forward CEL features that can be used when rollback is no longer needed.
2023-08-17 05:15:28 +00:00
func DefaultCompatibilityVersion ( ) * version . Version {
2024-08-19 08:01:33 +00:00
effectiveVer := utilversion . DefaultComponentGlobalsRegistry . EffectiveVersionFor ( utilversion . DefaultKubeComponent )
if effectiveVer == nil {
effectiveVer = utilversion . DefaultKubeEffectiveVersion ( )
}
return effectiveVer . MinCompatibilityVersion ( )
2023-08-17 05:15:28 +00:00
}
2024-06-18 05:38:14 +00:00
var baseOpts = append ( baseOptsWithoutStrictCost , StrictCostOpt )
var baseOptsWithoutStrictCost = [ ] VersionedOptions {
2023-08-17 05:15:28 +00:00
{
// CEL epoch was actually 1.23, but we artificially set it to 1.0 because these
// options should always be present.
IntroducedVersion : version . MajorMinor ( 1 , 0 ) ,
EnvOptions : [ ] cel . EnvOption {
cel . HomogeneousAggregateLiterals ( ) ,
// Validate function declarations once during base env initialization,
// so they don't need to be evaluated each time a CEL rule is compiled.
// This is a relatively expensive operation.
cel . EagerlyValidateDeclarations ( true ) ,
cel . DefaultUTCTimeZone ( true ) ,
library . URLs ( ) ,
library . Regex ( ) ,
library . Lists ( ) ,
2024-01-31 17:30:03 +00:00
// 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.
// Since it is a regression fix, we apply it uniformly to all code use v0.17.7.
cel . CostEstimatorOptions ( checker . PresenceTestHasCost ( false ) ) ,
2023-08-17 05:15:28 +00:00
} ,
ProgramOptions : [ ] cel . ProgramOption {
cel . EvalOptions ( cel . OptOptimize , cel . OptTrackCost ) ,
cel . CostLimit ( celconfig . PerCallLimit ) ,
2024-01-31 17:30:03 +00:00
// 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.
// Since it is a regression fix, we apply it uniformly to all code use v0.17.7.
cel . CostTrackerOptions ( interpreter . PresenceTestHasCost ( false ) ) ,
2023-08-17 05:15:28 +00:00
} ,
} ,
{
IntroducedVersion : version . MajorMinor ( 1 , 27 ) ,
EnvOptions : [ ] cel . EnvOption {
library . Authz ( ) ,
} ,
} ,
{
IntroducedVersion : version . MajorMinor ( 1 , 28 ) ,
EnvOptions : [ ] cel . EnvOption {
cel . CrossTypeNumericComparisons ( true ) ,
cel . OptionalTypes ( ) ,
library . Quantity ( ) ,
} ,
} ,
2023-12-20 12:23:59 +00:00
// add the new validator in 1.29
{
IntroducedVersion : version . MajorMinor ( 1 , 29 ) ,
EnvOptions : [ ] cel . EnvOption {
cel . ASTValidators (
cel . ValidateDurationLiterals ( ) ,
cel . ValidateTimestampLiterals ( ) ,
cel . ValidateRegexLiterals ( ) ,
cel . ValidateHomogeneousAggregateLiterals ( ) ,
) ,
} ,
} ,
// String library
{
IntroducedVersion : version . MajorMinor ( 1 , 0 ) ,
RemovedVersion : version . MajorMinor ( 1 , 29 ) ,
EnvOptions : [ ] cel . EnvOption {
ext . Strings ( ext . StringsVersion ( 0 ) ) ,
} ,
} ,
{
IntroducedVersion : version . MajorMinor ( 1 , 29 ) ,
EnvOptions : [ ] cel . EnvOption {
ext . Strings ( ext . StringsVersion ( 2 ) ) ,
} ,
} ,
// Set library
{
IntroducedVersion : version . MajorMinor ( 1 , 29 ) ,
EnvOptions : [ ] cel . EnvOption {
ext . Sets ( ) ,
} ,
} ,
2024-05-15 06:54:18 +00:00
{
IntroducedVersion : version . MajorMinor ( 1 , 30 ) ,
EnvOptions : [ ] cel . EnvOption {
library . IP ( ) ,
library . CIDR ( ) ,
} ,
} ,
2024-08-19 08:01:33 +00:00
// Format Library
{
IntroducedVersion : version . MajorMinor ( 1 , 31 ) ,
EnvOptions : [ ] cel . EnvOption {
library . Format ( ) ,
} ,
} ,
// Authz selectors
{
IntroducedVersion : version . MajorMinor ( 1 , 31 ) ,
FeatureEnabled : func ( ) bool {
enabled := utilfeature . DefaultFeatureGate . Enabled ( genericfeatures . AuthorizeWithSelectors )
authzSelectorsLibraryInit . Do ( func ( ) {
// Record the first time feature enablement was checked for this library.
// This is checked from integration tests to ensure no cached cel envs
// are constructed before feature enablement is effectively set.
authzSelectorsLibraryEnabled . Store ( enabled )
// Uncomment to debug where the first initialization is coming from if needed.
// debug.PrintStack()
} )
return enabled
} ,
EnvOptions : [ ] cel . EnvOption {
library . AuthzSelectors ( ) ,
} ,
} ,
}
var (
authzSelectorsLibraryInit sync . Once
authzSelectorsLibraryEnabled atomic . Value
)
// AuthzSelectorsLibraryEnabled returns whether the AuthzSelectors library was enabled when it was constructed.
// If it has not been contructed yet, this returns `false, false`.
// 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.
func AuthzSelectorsLibraryEnabled ( ) ( enabled , constructed bool ) {
enabled , constructed = authzSelectorsLibraryEnabled . Load ( ) . ( bool )
return
2023-08-17 05:15:28 +00:00
}
2024-06-18 05:38:14 +00:00
var StrictCostOpt = VersionedOptions {
// This is to configure the cost calculation for extended libraries
IntroducedVersion : version . MajorMinor ( 1 , 0 ) ,
ProgramOptions : [ ] cel . ProgramOption {
cel . CostTracking ( & library . CostEstimator { } ) ,
} ,
}
2023-08-17 05:15:28 +00:00
// MustBaseEnvSet returns the common CEL base environments for Kubernetes for Version, or panics
// if the version is nil, or does not have major and minor components.
//
// The returned environment contains function libraries, language settings, optimizations and
// runtime cost limits appropriate CEL as it is used in Kubernetes.
//
// The returned environment contains no CEL variable definitions or custom type declarations and
// should be extended to construct environments with the appropriate variable definitions,
// type declarations and any other needed configuration.
2024-06-18 05:38:14 +00:00
// strictCost is used to determine whether to enforce strict cost calculation for CEL expressions.
func MustBaseEnvSet ( ver * version . Version , strictCost bool ) * EnvSet {
2023-08-17 05:15:28 +00:00
if ver == nil {
panic ( "version must be non-nil" )
}
if len ( ver . Components ( ) ) < 2 {
panic ( fmt . Sprintf ( "version must contain an major and minor component, but got: %s" , ver . String ( ) ) )
}
key := strconv . FormatUint ( uint64 ( ver . Major ( ) ) , 10 ) + "." + strconv . FormatUint ( uint64 ( ver . Minor ( ) ) , 10 )
2024-06-18 05:38:14 +00:00
var entry interface { }
if strictCost {
if entry , ok := baseEnvs . Load ( key ) ; ok {
return entry . ( * EnvSet )
}
entry , _ , _ = baseEnvsSingleflight . Do ( key , func ( ) ( interface { } , error ) {
entry := mustNewEnvSet ( ver , baseOpts )
baseEnvs . Store ( key , entry )
return entry , nil
} )
} else {
if entry , ok := baseEnvsWithOption . Load ( key ) ; ok {
return entry . ( * EnvSet )
}
entry , _ , _ = baseEnvsWithOptionSingleflight . Do ( key , func ( ) ( interface { } , error ) {
entry := mustNewEnvSet ( ver , baseOptsWithoutStrictCost )
baseEnvsWithOption . Store ( key , entry )
return entry , nil
} )
2023-08-17 05:15:28 +00:00
}
return entry . ( * EnvSet )
}
var (
2024-06-18 05:38:14 +00:00
baseEnvs = sync . Map { }
baseEnvsWithOption = sync . Map { }
baseEnvsSingleflight = & singleflight . Group { }
baseEnvsWithOptionSingleflight = & singleflight . Group { }
2023-08-17 05:15:28 +00:00
)