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 )