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  }