k8s.io/apiserver@v0.31.1/pkg/cel/environment/environment.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 "math" 22 23 "github.com/google/cel-go/cel" 24 25 "k8s.io/apimachinery/pkg/util/version" 26 apiservercel "k8s.io/apiserver/pkg/cel" 27 ) 28 29 // Type defines the different types of CEL environments used in Kubernetes. 30 // CEL environments are used to compile and evaluate CEL expressions. 31 // Environments include: 32 // - Function libraries 33 // - Variables 34 // - Types (both core CEL types and Kubernetes types) 35 // - Other CEL environment and program options 36 type Type string 37 38 const ( 39 // NewExpressions is used to validate new or modified expressions in 40 // requests that write expressions to API resources. 41 // 42 // This environment type is compatible with a specific Kubernetes 43 // major/minor version. To ensure safe rollback, this environment type 44 // may not include all the function libraries, variables, type declarations, and CEL 45 // language settings available in the StoredExpressions environment type. 46 // 47 // NewExpressions must be used to validate (parse, compile, type check) 48 // all new or modified CEL expressions before they are written to storage. 49 NewExpressions Type = "NewExpressions" 50 51 // StoredExpressions is used to compile and run CEL expressions that have been 52 // persisted to storage. 53 // 54 // This environment type is compatible with CEL expressions that have been 55 // persisted to storage by all known versions of Kubernetes. This is the most 56 // permissive environment available. 57 // 58 // StoredExpressions is appropriate for use with CEL expressions in 59 // configuration files. 60 StoredExpressions Type = "StoredExpressions" 61 ) 62 63 // EnvSet manages the creation and extension of CEL environments. Each EnvSet contains 64 // both an NewExpressions and StoredExpressions environment. EnvSets are created 65 // and extended using VersionedOptions so that the EnvSet can prepare environments according 66 // to what options were introduced at which versions. 67 // 68 // Each EnvSet is given a compatibility version when it is created, and prepares the 69 // NewExpressions environment to be compatible with that version. The EnvSet also 70 // prepares StoredExpressions to be compatible with all known versions of Kubernetes. 71 type EnvSet struct { 72 // compatibilityVersion is the version that all configuration in 73 // the NewExpressions environment is compatible with. 74 compatibilityVersion *version.Version 75 76 // newExpressions is an environment containing only configuration 77 // in this EnvSet that is enabled at this compatibilityVersion. 78 newExpressions *cel.Env 79 80 // storedExpressions is an environment containing the latest configuration 81 // in this EnvSet. 82 storedExpressions *cel.Env 83 } 84 85 func newEnvSet(compatibilityVersion *version.Version, opts []VersionedOptions) (*EnvSet, error) { 86 base, err := cel.NewEnv() 87 if err != nil { 88 return nil, err 89 } 90 baseSet := EnvSet{compatibilityVersion: compatibilityVersion, newExpressions: base, storedExpressions: base} 91 return baseSet.Extend(opts...) 92 } 93 94 func mustNewEnvSet(ver *version.Version, opts []VersionedOptions) *EnvSet { 95 envSet, err := newEnvSet(ver, opts) 96 if err != nil { 97 panic(fmt.Sprintf("Default environment misconfigured: %v", err)) 98 } 99 return envSet 100 } 101 102 // NewExpressionsEnv returns the NewExpressions environment Type for this EnvSet. 103 // See NewExpressions for details. 104 func (e *EnvSet) NewExpressionsEnv() *cel.Env { 105 return e.newExpressions 106 } 107 108 // StoredExpressionsEnv returns the StoredExpressions environment Type for this EnvSet. 109 // See StoredExpressions for details. 110 func (e *EnvSet) StoredExpressionsEnv() *cel.Env { 111 return e.storedExpressions 112 } 113 114 // Env returns the CEL environment for the given Type. 115 func (e *EnvSet) Env(envType Type) (*cel.Env, error) { 116 switch envType { 117 case NewExpressions: 118 return e.newExpressions, nil 119 case StoredExpressions: 120 return e.storedExpressions, nil 121 default: 122 return nil, fmt.Errorf("unsupported environment type: %v", envType) 123 } 124 } 125 126 // VersionedOptions provides a set of CEL configuration options as well as the version the 127 // options were introduced and, optionally, the version the options were removed. 128 type VersionedOptions struct { 129 // IntroducedVersion is the version at which these options were introduced. 130 // The NewExpressions environment will only include options introduced at or before the 131 // compatibility version of the EnvSet. 132 // 133 // For example, to configure a CEL environment with an "object" variable bound to a 134 // resource kind, first create a DeclType from the groupVersionKind of the resource and then 135 // populate a VersionedOptions with the variable and the type: 136 // 137 // schema := schemaResolver.ResolveSchema(groupVersionKind) 138 // objectType := apiservercel.SchemaDeclType(schema, true) 139 // ... 140 // VersionOptions{ 141 // IntroducedVersion: version.MajorMinor(1, 26), 142 // DeclTypes: []*apiservercel.DeclType{ objectType }, 143 // EnvOptions: []cel.EnvOption{ cel.Variable("object", objectType.CelType()) }, 144 // }, 145 // 146 // To create an DeclType from a CRD, use a structural schema. For example: 147 // 148 // schema := structuralschema.NewStructural(crdJSONProps) 149 // objectType := apiservercel.SchemaDeclType(schema, true) 150 // 151 // Required. 152 IntroducedVersion *version.Version 153 // RemovedVersion is the version at which these options were removed. 154 // The NewExpressions environment will not include options removed at or before the 155 // compatibility version of the EnvSet. 156 // 157 // All option removals must be backward compatible; the removal must either be paired 158 // with a compatible replacement introduced at the same version, or the removal must be non-breaking. 159 // The StoredExpressions environment will not include removed options. 160 // 161 // A function library may be upgraded by setting the RemovedVersion of the old library 162 // to the same value as the IntroducedVersion of the new library. The new library must 163 // be backward compatible with the old library. 164 // 165 // For example: 166 // 167 // VersionOptions{ 168 // IntroducedVersion: version.MajorMinor(1, 26), RemovedVersion: version.MajorMinor(1, 27), 169 // EnvOptions: []cel.EnvOption{ libraries.Example(libraries.ExampleVersion(1)) }, 170 // }, 171 // VersionOptions{ 172 // IntroducedVersion: version.MajorMinor(1, 27), 173 // EnvOptions: []EnvOptions{ libraries.Example(libraries.ExampleVersion(2)) }, 174 // }, 175 // 176 // Optional. 177 RemovedVersion *version.Version 178 // FeatureEnabled returns true if these options are enabled by feature gates, 179 // and returns false if these options are not enabled due to feature gates. 180 // 181 // This takes priority over IntroducedVersion / RemovedVersion for the NewExpressions environment. 182 // 183 // The StoredExpressions environment ignores this function. 184 // 185 // Optional. 186 FeatureEnabled func() bool 187 // EnvOptions provides CEL EnvOptions. This may be used to add a cel.Variable, a 188 // cel.Library, or to enable other CEL EnvOptions such as language settings. 189 // 190 // If an added cel.Variable has an OpenAPI type, the type must be included in DeclTypes. 191 EnvOptions []cel.EnvOption 192 // ProgramOptions provides CEL ProgramOptions. This may be used to set a cel.CostLimit, 193 // enable optimizations, and set other program level options that should be enabled 194 // for all programs using this environment. 195 ProgramOptions []cel.ProgramOption 196 // DeclTypes provides OpenAPI type declarations to register with the environment. 197 // 198 // If cel.Variables added to EnvOptions refer to a OpenAPI type, the type must be included in 199 // DeclTypes. 200 DeclTypes []*apiservercel.DeclType 201 } 202 203 // Extend returns an EnvSet based on this EnvSet but extended with given VersionedOptions. 204 // This EnvSet is not mutated. 205 // The returned EnvSet has the same compatibility version as the EnvSet that was extended. 206 // 207 // Extend is an expensive operation and each call to Extend that adds DeclTypes increases 208 // the depth of a chain of resolvers. For these reasons, calls to Extend should be kept 209 // to a minimum. 210 // 211 // Some best practices: 212 // 213 // - Minimize calls Extend when handling API requests. Where possible, call Extend 214 // when initializing components. 215 // - If an EnvSets returned by Extend can be used to compile multiple CEL programs, 216 // call Extend once and reuse the returned EnvSets. 217 // - Prefer a single call to Extend with a full list of VersionedOptions over 218 // making multiple calls to Extend. 219 func (e *EnvSet) Extend(options ...VersionedOptions) (*EnvSet, error) { 220 if len(options) > 0 { 221 newExprOpts, err := e.filterAndBuildOpts(e.newExpressions, e.compatibilityVersion, true, options) 222 if err != nil { 223 return nil, err 224 } 225 p, err := e.newExpressions.Extend(newExprOpts) 226 if err != nil { 227 return nil, err 228 } 229 storedExprOpt, err := e.filterAndBuildOpts(e.storedExpressions, version.MajorMinor(math.MaxUint, math.MaxUint), false, options) 230 if err != nil { 231 return nil, err 232 } 233 s, err := e.storedExpressions.Extend(storedExprOpt) 234 if err != nil { 235 return nil, err 236 } 237 return &EnvSet{compatibilityVersion: e.compatibilityVersion, newExpressions: p, storedExpressions: s}, nil 238 } 239 return e, nil 240 } 241 242 func (e *EnvSet) filterAndBuildOpts(base *cel.Env, compatVer *version.Version, honorFeatureGateEnablement bool, opts []VersionedOptions) (cel.EnvOption, error) { 243 var envOpts []cel.EnvOption 244 var progOpts []cel.ProgramOption 245 var declTypes []*apiservercel.DeclType 246 247 for _, opt := range opts { 248 var allowedByFeatureGate, allowedByVersion bool 249 if opt.FeatureEnabled != nil && honorFeatureGateEnablement { 250 // Feature-gate-enabled libraries must follow compatible default feature enablement. 251 // Enabling alpha features in their first release enables libraries the previous API server is unaware of. 252 allowedByFeatureGate = opt.FeatureEnabled() 253 if !allowedByFeatureGate { 254 continue 255 } 256 } 257 if compatVer.AtLeast(opt.IntroducedVersion) && (opt.RemovedVersion == nil || compatVer.LessThan(opt.RemovedVersion)) { 258 allowedByVersion = true 259 } 260 261 if allowedByFeatureGate || allowedByVersion { 262 envOpts = append(envOpts, opt.EnvOptions...) 263 progOpts = append(progOpts, opt.ProgramOptions...) 264 declTypes = append(declTypes, opt.DeclTypes...) 265 } 266 } 267 268 if len(declTypes) > 0 { 269 provider := apiservercel.NewDeclTypeProvider(declTypes...) 270 if compatVer.AtLeast(version.MajorMinor(1, 31)) { 271 provider.SetRecognizeKeywordAsFieldName(true) 272 } 273 providerOpts, err := provider.EnvOptions(base.CELTypeProvider()) 274 if err != nil { 275 return nil, err 276 } 277 envOpts = append(envOpts, providerOpts...) 278 } 279 280 combined := cel.Lib(&envLoader{ 281 envOpts: envOpts, 282 progOpts: progOpts, 283 }) 284 return combined, nil 285 } 286 287 type envLoader struct { 288 envOpts []cel.EnvOption 289 progOpts []cel.ProgramOption 290 } 291 292 func (e *envLoader) CompileOptions() []cel.EnvOption { 293 return e.envOpts 294 } 295 296 func (e *envLoader) ProgramOptions() []cel.ProgramOption { 297 return e.progOpts 298 }