2023-05-29 21:03:29 +00:00
/ *
Copyright 2022 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 .
* /
2023-06-01 16:58:10 +00:00
package cel
2023-05-29 21:03:29 +00:00
import (
2023-06-01 16:58:10 +00:00
"fmt"
2023-05-29 21:03:29 +00:00
"github.com/google/cel-go/cel"
2023-08-17 05:15:28 +00:00
"k8s.io/apimachinery/pkg/util/version"
celconfig "k8s.io/apiserver/pkg/apis/cel"
2023-05-29 21:03:29 +00:00
apiservercel "k8s.io/apiserver/pkg/cel"
2023-08-17 05:15:28 +00:00
"k8s.io/apiserver/pkg/cel/environment"
2023-05-29 21:03:29 +00:00
"k8s.io/apiserver/pkg/cel/library"
)
const (
2023-06-01 16:58:10 +00:00
ObjectVarName = "object"
OldObjectVarName = "oldObject"
ParamsVarName = "params"
RequestVarName = "request"
2023-08-17 05:15:28 +00:00
NamespaceVarName = "namespaceObject"
2023-06-01 16:58:10 +00:00
AuthorizerVarName = "authorizer"
RequestResourceAuthorizerVarName = "authorizer.requestResource"
2023-08-17 05:15:28 +00:00
VariableVarName = "variables"
2023-05-29 21:03:29 +00:00
)
2023-06-01 16:58:10 +00:00
// BuildRequestType generates a DeclType for AdmissionRequest. This may be replaced with a utility that
2023-05-29 21:03:29 +00:00
// converts the native type definition to apiservercel.DeclType once such a utility becomes available.
// The 'uid' field is omitted since it is not needed for in-process admission review.
// The 'object' and 'oldObject' fields are omitted since they are exposed as root level CEL variables.
2023-06-01 16:58:10 +00:00
func BuildRequestType ( ) * apiservercel . DeclType {
2023-05-29 21:03:29 +00:00
field := func ( name string , declType * apiservercel . DeclType , required bool ) * apiservercel . DeclField {
return apiservercel . NewDeclField ( name , declType , required , nil , nil )
}
fields := func ( fields ... * apiservercel . DeclField ) map [ string ] * apiservercel . DeclField {
result := make ( map [ string ] * apiservercel . DeclField , len ( fields ) )
for _ , f := range fields {
result [ f . Name ] = f
}
return result
}
gvkType := apiservercel . NewObjectType ( "kubernetes.GroupVersionKind" , fields (
field ( "group" , apiservercel . StringType , true ) ,
field ( "version" , apiservercel . StringType , true ) ,
field ( "kind" , apiservercel . StringType , true ) ,
) )
gvrType := apiservercel . NewObjectType ( "kubernetes.GroupVersionResource" , fields (
field ( "group" , apiservercel . StringType , true ) ,
field ( "version" , apiservercel . StringType , true ) ,
field ( "resource" , apiservercel . StringType , true ) ,
) )
userInfoType := apiservercel . NewObjectType ( "kubernetes.UserInfo" , fields (
field ( "username" , apiservercel . StringType , false ) ,
field ( "uid" , apiservercel . StringType , false ) ,
field ( "groups" , apiservercel . NewListType ( apiservercel . StringType , - 1 ) , false ) ,
field ( "extra" , apiservercel . NewMapType ( apiservercel . StringType , apiservercel . NewListType ( apiservercel . StringType , - 1 ) , - 1 ) , false ) ,
) )
return apiservercel . NewObjectType ( "kubernetes.AdmissionRequest" , fields (
field ( "kind" , gvkType , true ) ,
field ( "resource" , gvrType , true ) ,
field ( "subResource" , apiservercel . StringType , false ) ,
field ( "requestKind" , gvkType , true ) ,
field ( "requestResource" , gvrType , true ) ,
field ( "requestSubResource" , apiservercel . StringType , false ) ,
field ( "name" , apiservercel . StringType , true ) ,
field ( "namespace" , apiservercel . StringType , false ) ,
field ( "operation" , apiservercel . StringType , true ) ,
field ( "userInfo" , userInfoType , true ) ,
field ( "dryRun" , apiservercel . BoolType , false ) ,
field ( "options" , apiservercel . DynType , false ) ,
) )
}
2023-08-17 05:15:28 +00:00
// BuildNamespaceType generates a DeclType for Namespace.
// Certain nested fields in Namespace (e.g. managedFields, ownerReferences etc.) are omitted in the generated DeclType
// by design.
func BuildNamespaceType ( ) * apiservercel . DeclType {
field := func ( name string , declType * apiservercel . DeclType , required bool ) * apiservercel . DeclField {
return apiservercel . NewDeclField ( name , declType , required , nil , nil )
}
fields := func ( fields ... * apiservercel . DeclField ) map [ string ] * apiservercel . DeclField {
result := make ( map [ string ] * apiservercel . DeclField , len ( fields ) )
for _ , f := range fields {
result [ f . Name ] = f
}
return result
}
specType := apiservercel . NewObjectType ( "kubernetes.NamespaceSpec" , fields (
field ( "finalizers" , apiservercel . NewListType ( apiservercel . StringType , - 1 ) , true ) ,
) )
conditionType := apiservercel . NewObjectType ( "kubernetes.NamespaceCondition" , fields (
field ( "status" , apiservercel . StringType , true ) ,
field ( "type" , apiservercel . StringType , true ) ,
field ( "lastTransitionTime" , apiservercel . TimestampType , true ) ,
field ( "message" , apiservercel . StringType , true ) ,
field ( "reason" , apiservercel . StringType , true ) ,
) )
statusType := apiservercel . NewObjectType ( "kubernetes.NamespaceStatus" , fields (
field ( "conditions" , apiservercel . NewListType ( conditionType , - 1 ) , true ) ,
field ( "phase" , apiservercel . StringType , true ) ,
) )
metadataType := apiservercel . NewObjectType ( "kubernetes.NamespaceMetadata" , fields (
field ( "name" , apiservercel . StringType , true ) ,
field ( "generateName" , apiservercel . StringType , true ) ,
field ( "namespace" , apiservercel . StringType , true ) ,
field ( "labels" , apiservercel . NewMapType ( apiservercel . StringType , apiservercel . StringType , - 1 ) , true ) ,
field ( "annotations" , apiservercel . NewMapType ( apiservercel . StringType , apiservercel . StringType , - 1 ) , true ) ,
field ( "UID" , apiservercel . StringType , true ) ,
field ( "creationTimestamp" , apiservercel . TimestampType , true ) ,
field ( "deletionGracePeriodSeconds" , apiservercel . IntType , true ) ,
field ( "deletionTimestamp" , apiservercel . TimestampType , true ) ,
field ( "generation" , apiservercel . IntType , true ) ,
field ( "resourceVersion" , apiservercel . StringType , true ) ,
field ( "finalizers" , apiservercel . NewListType ( apiservercel . StringType , - 1 ) , true ) ,
) )
return apiservercel . NewObjectType ( "kubernetes.Namespace" , fields (
field ( "metadata" , metadataType , true ) ,
field ( "spec" , specType , true ) ,
field ( "status" , statusType , true ) ,
) )
}
2023-06-01 16:58:10 +00:00
// CompilationResult represents a compiled validations expression.
2023-05-29 21:03:29 +00:00
type CompilationResult struct {
2023-06-01 16:58:10 +00:00
Program cel . Program
Error * apiservercel . Error
ExpressionAccessor ExpressionAccessor
2023-12-20 12:23:59 +00:00
OutputType * cel . Type
2023-05-29 21:03:29 +00:00
}
2023-08-17 05:15:28 +00:00
// Compiler provides a CEL expression compiler configured with the desired admission related CEL variables and
// environment mode.
type Compiler interface {
CompileCELExpression ( expressionAccessor ExpressionAccessor , options OptionalVariableDeclarations , mode environment . Type ) CompilationResult
}
type compiler struct {
varEnvs variableDeclEnvs
}
func NewCompiler ( env * environment . EnvSet ) Compiler {
return & compiler { varEnvs : mustBuildEnvs ( env ) }
}
type variableDeclEnvs map [ OptionalVariableDeclarations ] * environment . EnvSet
2023-06-01 16:58:10 +00:00
// CompileCELExpression returns a compiled CEL expression.
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
2023-08-17 05:15:28 +00:00
func ( c compiler ) CompileCELExpression ( expressionAccessor ExpressionAccessor , options OptionalVariableDeclarations , envType environment . Type ) CompilationResult {
2024-08-19 08:01:33 +00:00
resultError := func ( errorString string , errType apiservercel . ErrorType , cause error ) CompilationResult {
2023-05-29 21:03:29 +00:00
return CompilationResult {
Error : & apiservercel . Error {
2023-08-17 05:15:28 +00:00
Type : errType ,
Detail : errorString ,
2024-08-19 08:01:33 +00:00
Cause : cause ,
2023-05-29 21:03:29 +00:00
} ,
2023-06-01 16:58:10 +00:00
ExpressionAccessor : expressionAccessor ,
2023-05-29 21:03:29 +00:00
}
}
2023-08-17 05:15:28 +00:00
env , err := c . varEnvs [ options ] . Env ( envType )
if err != nil {
2024-08-19 08:01:33 +00:00
return resultError ( fmt . Sprintf ( "unexpected error loading CEL environment: %v" , err ) , apiservercel . ErrorTypeInternal , nil )
2023-05-29 21:03:29 +00:00
}
2023-06-01 16:58:10 +00:00
ast , issues := env . Compile ( expressionAccessor . GetExpression ( ) )
2023-05-29 21:03:29 +00:00
if issues != nil {
2024-08-19 08:01:33 +00:00
return resultError ( "compilation failed: " + issues . String ( ) , apiservercel . ErrorTypeInvalid , apiservercel . NewCompilationError ( issues ) )
2023-05-29 21:03:29 +00:00
}
2023-06-01 16:58:10 +00:00
found := false
returnTypes := expressionAccessor . ReturnTypes ( )
for _ , returnType := range returnTypes {
2023-08-17 05:15:28 +00:00
if ast . OutputType ( ) == returnType || cel . AnyType == returnType {
2023-06-01 16:58:10 +00:00
found = true
break
}
}
if ! found {
var reason string
if len ( returnTypes ) == 1 {
reason = fmt . Sprintf ( "must evaluate to %v" , returnTypes [ 0 ] . String ( ) )
} else {
reason = fmt . Sprintf ( "must evaluate to one of %v" , returnTypes )
}
2024-08-19 08:01:33 +00:00
return resultError ( reason , apiservercel . ErrorTypeInvalid , nil )
2023-05-29 21:03:29 +00:00
}
_ , err = cel . AstToCheckedExpr ( ast )
if err != nil {
// should be impossible since env.Compile returned no issues
2024-08-19 08:01:33 +00:00
return resultError ( "unexpected compilation error: " + err . Error ( ) , apiservercel . ErrorTypeInternal , nil )
2023-05-29 21:03:29 +00:00
}
prog , err := env . Program ( ast ,
2023-06-01 16:58:10 +00:00
cel . InterruptCheckFrequency ( celconfig . CheckFrequency ) ,
2023-05-29 21:03:29 +00:00
)
if err != nil {
2024-08-19 08:01:33 +00:00
return resultError ( "program instantiation failed: " + err . Error ( ) , apiservercel . ErrorTypeInternal , nil )
2023-05-29 21:03:29 +00:00
}
return CompilationResult {
2023-06-01 16:58:10 +00:00
Program : prog ,
ExpressionAccessor : expressionAccessor ,
2023-12-20 12:23:59 +00:00
OutputType : ast . OutputType ( ) ,
2023-05-29 21:03:29 +00:00
}
}
2023-08-17 05:15:28 +00:00
func mustBuildEnvs ( baseEnv * environment . EnvSet ) variableDeclEnvs {
requestType := BuildRequestType ( )
namespaceType := BuildNamespaceType ( )
2024-06-18 05:38:14 +00:00
envs := make ( variableDeclEnvs , 8 ) // since the number of variable combinations is small, pre-build a environment for each
2023-08-17 05:15:28 +00:00
for _ , hasParams := range [ ] bool { false , true } {
for _ , hasAuthorizer := range [ ] bool { false , true } {
2024-06-18 05:38:14 +00:00
for _ , strictCost := range [ ] bool { false , true } {
var envOpts [ ] cel . EnvOption
if hasParams {
envOpts = append ( envOpts , cel . Variable ( ParamsVarName , cel . DynType ) )
}
if hasAuthorizer {
envOpts = append ( envOpts ,
cel . Variable ( AuthorizerVarName , library . AuthorizerType ) ,
cel . Variable ( RequestResourceAuthorizerVarName , library . ResourceCheckType ) )
}
2023-08-17 05:15:28 +00:00
envOpts = append ( envOpts ,
2024-06-18 05:38:14 +00:00
cel . Variable ( ObjectVarName , cel . DynType ) ,
cel . Variable ( OldObjectVarName , cel . DynType ) ,
cel . Variable ( NamespaceVarName , namespaceType . CelType ( ) ) ,
cel . Variable ( RequestVarName , requestType . CelType ( ) ) )
2023-08-17 05:15:28 +00:00
2024-06-18 05:38:14 +00:00
extended , err := baseEnv . Extend (
environment . VersionedOptions {
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
// options should always be present.
IntroducedVersion : version . MajorMinor ( 1 , 0 ) ,
EnvOptions : envOpts ,
DeclTypes : [ ] * apiservercel . DeclType {
namespaceType ,
requestType ,
} ,
2023-08-17 05:15:28 +00:00
} ,
2024-06-18 05:38:14 +00:00
)
if err != nil {
panic ( fmt . Sprintf ( "environment misconfigured: %v" , err ) )
}
if strictCost {
extended , err = extended . Extend ( environment . StrictCostOpt )
if err != nil {
panic ( fmt . Sprintf ( "environment misconfigured: %v" , err ) )
}
}
envs [ OptionalVariableDeclarations { HasParams : hasParams , HasAuthorizer : hasAuthorizer , StrictCost : strictCost } ] = extended
2023-08-17 05:15:28 +00:00
}
}
}
return envs
}