feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
committed by
GitHub
parent
b5015ec7b9
commit
447a51f08b
626
vendor/k8s.io/apiserver/pkg/cel/library/authz.go
generated
vendored
Normal file
626
vendor/k8s.io/apiserver/pkg/cel/library/authz.go
generated
vendored
Normal file
@@ -0,0 +1,626 @@
|
||||
/*
|
||||
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 library
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// Authz provides a CEL function library extension for performing authorization checks.
|
||||
// Note that authorization checks are only supported for CEL expression fields in the API
|
||||
// where an 'authorizer' variable is provided to the CEL expression. See the
|
||||
// documentation of API fields where CEL expressions are used to learn if the 'authorizer'
|
||||
// variable is provided.
|
||||
//
|
||||
// path
|
||||
//
|
||||
// Returns a PathCheck configured to check authorization for a non-resource request
|
||||
// path (e.g. /healthz). If path is an empty string, an error is returned.
|
||||
// Note that the leading '/' is not required.
|
||||
//
|
||||
// <Authorizer>.path(<string>) <PathCheck>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.path('/healthz') // returns a PathCheck for the '/healthz' API path
|
||||
// authorizer.path('') // results in "path must not be empty" error
|
||||
// authorizer.path(' ') // results in "path must not be empty" error
|
||||
//
|
||||
// group
|
||||
//
|
||||
// Returns a GroupCheck configured to check authorization for the API resources for
|
||||
// a particular API group.
|
||||
// Note that authorization checks are only supported for CEL expression fields in the API
|
||||
// where an 'authorizer' variable is provided to the CEL expression. Check the
|
||||
// documentation of API fields where CEL expressions are used to learn if the 'authorizer'
|
||||
// variable is provided.
|
||||
//
|
||||
// <Authorizer>.group(<string>) <GroupCheck>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('apps') // returns a GroupCheck for the 'apps' API group
|
||||
// authorizer.group('') // returns a GroupCheck for the core API group
|
||||
// authorizer.group('example.com') // returns a GroupCheck for the custom resources in the 'example.com' API group
|
||||
//
|
||||
// serviceAccount
|
||||
//
|
||||
// Returns an Authorizer configured to check authorization for the provided service account namespace and name.
|
||||
// If the name is not a valid DNS subdomain string (as defined by RFC 1123), an error is returned.
|
||||
// If the namespace is not a valid DNS label (as defined by RFC 1123), an error is returned.
|
||||
//
|
||||
// <Authorizer>.serviceAccount(<string>, <string>) <Authorizer>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.serviceAccount('default', 'myserviceaccount') // returns an Authorizer for the service account with namespace 'default' and name 'myserviceaccount'
|
||||
// authorizer.serviceAccount('not@a#valid!namespace', 'validname') // returns an error
|
||||
// authorizer.serviceAccount('valid.example.com', 'invalid@*name') // returns an error
|
||||
//
|
||||
// resource
|
||||
//
|
||||
// Returns a ResourceCheck configured to check authorization for a particular API resource.
|
||||
// Note that the provided resource string should be a lower case plural name of a Kubernetes API resource.
|
||||
//
|
||||
// <GroupCheck>.resource(<string>) <ResourceCheck>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('apps').resource('deployments') // returns a ResourceCheck for the 'deployments' resources in the 'apps' group.
|
||||
// authorizer.group('').resource('pods') // returns a ResourceCheck for the 'pods' resources in the core group.
|
||||
// authorizer.group('apps').resource('') // results in "resource must not be empty" error
|
||||
// authorizer.group('apps').resource(' ') // results in "resource must not be empty" error
|
||||
//
|
||||
// subresource
|
||||
//
|
||||
// Returns a ResourceCheck configured to check authorization for a particular subresource of an API resource.
|
||||
// If subresource is set to "", the subresource field of this ResourceCheck is considered unset.
|
||||
//
|
||||
// <ResourceCheck>.subresource(<string>) <ResourceCheck>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('').resource('pods').subresource('status') // returns a ResourceCheck the 'status' subresource of 'pods'
|
||||
// authorizer.group('apps').resource('deployments').subresource('scale') // returns a ResourceCheck the 'scale' subresource of 'deployments'
|
||||
// authorizer.group('example.com').resource('widgets').subresource('scale') // returns a ResourceCheck for the 'scale' subresource of the 'widgets' custom resource
|
||||
// authorizer.group('example.com').resource('widgets').subresource('') // returns a ResourceCheck for the 'widgets' resource.
|
||||
//
|
||||
// namespace
|
||||
//
|
||||
// Returns a ResourceCheck configured to check authorization for a particular namespace.
|
||||
// For cluster scoped resources, namespace() does not need to be called; namespace defaults
|
||||
// to "", which is the correct namespace value to use to check cluster scoped resources.
|
||||
// If namespace is set to "", the ResourceCheck will check authorization for the cluster scope.
|
||||
//
|
||||
// <ResourceCheck>.namespace(<string>) <ResourceCheck>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('apps').resource('deployments').namespace('test') // returns a ResourceCheck for 'deployments' in the 'test' namespace
|
||||
// authorizer.group('').resource('pods').namespace('default') // returns a ResourceCheck for 'pods' in the 'default' namespace
|
||||
// authorizer.group('').resource('widgets').namespace('') // returns a ResourceCheck for 'widgets' in the cluster scope
|
||||
//
|
||||
// name
|
||||
//
|
||||
// Returns a ResourceCheck configured to check authorization for a particular resource name.
|
||||
// If name is set to "", the name field of this ResourceCheck is considered unset.
|
||||
//
|
||||
// <ResourceCheck>.name(<name>) <ResourceCheck>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('apps').resource('deployments').namespace('test').name('backend') // returns a ResourceCheck for the 'backend' 'deployments' resource in the 'test' namespace
|
||||
// authorizer.group('apps').resource('deployments').namespace('test').name('') // returns a ResourceCheck for the 'deployments' resource in the 'test' namespace
|
||||
//
|
||||
// check
|
||||
//
|
||||
// For PathCheck, checks if the principal (user or service account) that sent the request is authorized for the HTTP request verb of the path.
|
||||
// For ResourceCheck, checks if the principal (user or service account) that sent the request is authorized for the API verb and the configured authorization checks of the ResourceCheck.
|
||||
// The check operation can be expensive, particularly in clusters using the webhook authorization mode.
|
||||
//
|
||||
// <PathCheck>.check(<check>) <Decision>
|
||||
// <ResourceCheck>.check(<check>) <Decision>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('').resource('pods').namespace('default').check('create') // Checks if the principal (user or service account) is authorized create pods in the 'default' namespace.
|
||||
// authorizer.path('/healthz').check('get') // Checks if the principal (user or service account) is authorized to make HTTP GET requests to the /healthz API path.
|
||||
//
|
||||
// allowed
|
||||
//
|
||||
// Returns true if the authorizer's decision for the check is "allow". Note that if the authorizer's decision is
|
||||
// "no opinion", that the 'allowed' function will return false.
|
||||
//
|
||||
// <Decision>.allowed() <bool>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('').resource('pods').namespace('default').check('create').allowed() // Returns true if the principal (user or service account) is allowed create pods in the 'default' namespace.
|
||||
// authorizer.path('/healthz').check('get').allowed() // Returns true if the principal (user or service account) is allowed to make HTTP GET requests to the /healthz API path.
|
||||
//
|
||||
// reason
|
||||
//
|
||||
// Returns a string reason for the authorization decision
|
||||
//
|
||||
// <Decision>.reason() <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.path('/healthz').check('GET').reason()
|
||||
//
|
||||
// errored
|
||||
//
|
||||
// Returns true if the authorization check resulted in an error.
|
||||
//
|
||||
// <Decision>.errored() <bool>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('').resource('pods').namespace('default').check('create').errored() // Returns true if the authorization check resulted in an error
|
||||
//
|
||||
// error
|
||||
//
|
||||
// If the authorization check resulted in an error, returns the error. Otherwise, returns the empty string.
|
||||
//
|
||||
// <Decision>.error() <string>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// authorizer.group('').resource('pods').namespace('default').check('create').error()
|
||||
func Authz() cel.EnvOption {
|
||||
return cel.Lib(authzLib)
|
||||
}
|
||||
|
||||
var authzLib = &authz{}
|
||||
|
||||
type authz struct{}
|
||||
|
||||
func (*authz) LibraryName() string {
|
||||
return "k8s.authz"
|
||||
}
|
||||
|
||||
var authzLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"path": {
|
||||
cel.MemberOverload("authorizer_path", []*cel.Type{AuthorizerType, cel.StringType}, PathCheckType,
|
||||
cel.BinaryBinding(authorizerPath))},
|
||||
"group": {
|
||||
cel.MemberOverload("authorizer_group", []*cel.Type{AuthorizerType, cel.StringType}, GroupCheckType,
|
||||
cel.BinaryBinding(authorizerGroup))},
|
||||
"serviceAccount": {
|
||||
cel.MemberOverload("authorizer_serviceaccount", []*cel.Type{AuthorizerType, cel.StringType, cel.StringType}, AuthorizerType,
|
||||
cel.FunctionBinding(authorizerServiceAccount))},
|
||||
"resource": {
|
||||
cel.MemberOverload("groupcheck_resource", []*cel.Type{GroupCheckType, cel.StringType}, ResourceCheckType,
|
||||
cel.BinaryBinding(groupCheckResource))},
|
||||
"subresource": {
|
||||
cel.MemberOverload("resourcecheck_subresource", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
|
||||
cel.BinaryBinding(resourceCheckSubresource))},
|
||||
"namespace": {
|
||||
cel.MemberOverload("resourcecheck_namespace", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
|
||||
cel.BinaryBinding(resourceCheckNamespace))},
|
||||
"name": {
|
||||
cel.MemberOverload("resourcecheck_name", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
|
||||
cel.BinaryBinding(resourceCheckName))},
|
||||
"check": {
|
||||
cel.MemberOverload("pathcheck_check", []*cel.Type{PathCheckType, cel.StringType}, DecisionType,
|
||||
cel.BinaryBinding(pathCheckCheck)),
|
||||
cel.MemberOverload("resourcecheck_check", []*cel.Type{ResourceCheckType, cel.StringType}, DecisionType,
|
||||
cel.BinaryBinding(resourceCheckCheck))},
|
||||
"errored": {
|
||||
cel.MemberOverload("decision_errored", []*cel.Type{DecisionType}, cel.BoolType,
|
||||
cel.UnaryBinding(decisionErrored))},
|
||||
"error": {
|
||||
cel.MemberOverload("decision_error", []*cel.Type{DecisionType}, cel.StringType,
|
||||
cel.UnaryBinding(decisionError))},
|
||||
"allowed": {
|
||||
cel.MemberOverload("decision_allowed", []*cel.Type{DecisionType}, cel.BoolType,
|
||||
cel.UnaryBinding(decisionAllowed))},
|
||||
"reason": {
|
||||
cel.MemberOverload("decision_reason", []*cel.Type{DecisionType}, cel.StringType,
|
||||
cel.UnaryBinding(decisionReason))},
|
||||
}
|
||||
|
||||
func (*authz) CompileOptions() []cel.EnvOption {
|
||||
options := make([]cel.EnvOption, 0, len(authzLibraryDecls))
|
||||
for name, overloads := range authzLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*authz) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func authorizerPath(arg1, arg2 ref.Val) ref.Val {
|
||||
authz, ok := arg1.(authorizerVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
path, ok := arg2.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(path)) == 0 {
|
||||
return types.NewErr("path must not be empty")
|
||||
}
|
||||
|
||||
return authz.pathCheck(path)
|
||||
}
|
||||
|
||||
func authorizerGroup(arg1, arg2 ref.Val) ref.Val {
|
||||
authz, ok := arg1.(authorizerVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
group, ok := arg2.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
return authz.groupCheck(group)
|
||||
}
|
||||
|
||||
func authorizerServiceAccount(args ...ref.Val) ref.Val {
|
||||
argn := len(args)
|
||||
if argn != 3 {
|
||||
return types.NoSuchOverloadErr()
|
||||
}
|
||||
|
||||
authz, ok := args[0].(authorizerVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[0])
|
||||
}
|
||||
|
||||
namespace, ok := args[1].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[1])
|
||||
}
|
||||
|
||||
name, ok := args[2].Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(args[2])
|
||||
}
|
||||
|
||||
if errors := apimachineryvalidation.ValidateServiceAccountName(name, false); len(errors) > 0 {
|
||||
return types.NewErr("Invalid service account name")
|
||||
}
|
||||
if errors := apimachineryvalidation.ValidateNamespaceName(namespace, false); len(errors) > 0 {
|
||||
return types.NewErr("Invalid service account namespace")
|
||||
}
|
||||
return authz.serviceAccount(namespace, name)
|
||||
}
|
||||
|
||||
func groupCheckResource(arg1, arg2 ref.Val) ref.Val {
|
||||
groupCheck, ok := arg1.(groupCheckVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
resource, ok := arg2.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
if len(strings.TrimSpace(resource)) == 0 {
|
||||
return types.NewErr("resource must not be empty")
|
||||
}
|
||||
return groupCheck.resourceCheck(resource)
|
||||
}
|
||||
|
||||
func resourceCheckSubresource(arg1, arg2 ref.Val) ref.Val {
|
||||
resourceCheck, ok := arg1.(resourceCheckVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
subresource, ok := arg2.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
result := resourceCheck
|
||||
result.subresource = subresource
|
||||
return result
|
||||
}
|
||||
|
||||
func resourceCheckNamespace(arg1, arg2 ref.Val) ref.Val {
|
||||
resourceCheck, ok := arg1.(resourceCheckVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
namespace, ok := arg2.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
result := resourceCheck
|
||||
result.namespace = namespace
|
||||
return result
|
||||
}
|
||||
|
||||
func resourceCheckName(arg1, arg2 ref.Val) ref.Val {
|
||||
resourceCheck, ok := arg1.(resourceCheckVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
name, ok := arg2.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
result := resourceCheck
|
||||
result.name = name
|
||||
return result
|
||||
}
|
||||
|
||||
func pathCheckCheck(arg1, arg2 ref.Val) ref.Val {
|
||||
pathCheck, ok := arg1.(pathCheckVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
httpRequestVerb, ok := arg2.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
return pathCheck.Authorize(context.TODO(), httpRequestVerb)
|
||||
}
|
||||
|
||||
func resourceCheckCheck(arg1, arg2 ref.Val) ref.Val {
|
||||
resourceCheck, ok := arg1.(resourceCheckVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
apiVerb, ok := arg2.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg1)
|
||||
}
|
||||
|
||||
return resourceCheck.Authorize(context.TODO(), apiVerb)
|
||||
}
|
||||
|
||||
func decisionErrored(arg ref.Val) ref.Val {
|
||||
decision, ok := arg.(decisionVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
return types.Bool(decision.err != nil)
|
||||
}
|
||||
|
||||
func decisionError(arg ref.Val) ref.Val {
|
||||
decision, ok := arg.(decisionVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
if decision.err == nil {
|
||||
return types.String("")
|
||||
}
|
||||
return types.String(decision.err.Error())
|
||||
}
|
||||
|
||||
func decisionAllowed(arg ref.Val) ref.Val {
|
||||
decision, ok := arg.(decisionVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
return types.Bool(decision.authDecision == authorizer.DecisionAllow)
|
||||
}
|
||||
|
||||
func decisionReason(arg ref.Val) ref.Val {
|
||||
decision, ok := arg.(decisionVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
return types.String(decision.reason)
|
||||
}
|
||||
|
||||
var (
|
||||
AuthorizerType = cel.ObjectType("kubernetes.authorization.Authorizer")
|
||||
PathCheckType = cel.ObjectType("kubernetes.authorization.PathCheck")
|
||||
GroupCheckType = cel.ObjectType("kubernetes.authorization.GroupCheck")
|
||||
ResourceCheckType = cel.ObjectType("kubernetes.authorization.ResourceCheck")
|
||||
DecisionType = cel.ObjectType("kubernetes.authorization.Decision")
|
||||
)
|
||||
|
||||
// Resource represents an API resource
|
||||
type Resource interface {
|
||||
// GetName returns the name of the object as presented in the request. On a CREATE operation, the client
|
||||
// may omit name and rely on the server to generate the name. If that is the case, this method will return
|
||||
// the empty string
|
||||
GetName() string
|
||||
// GetNamespace is the namespace associated with the request (if any)
|
||||
GetNamespace() string
|
||||
// GetResource is the name of the resource being requested. This is not the kind. For example: pods
|
||||
GetResource() schema.GroupVersionResource
|
||||
// GetSubresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
|
||||
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
|
||||
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
|
||||
GetSubresource() string
|
||||
}
|
||||
|
||||
func NewAuthorizerVal(userInfo user.Info, authorizer authorizer.Authorizer) ref.Val {
|
||||
return authorizerVal{receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType), userInfo: userInfo, authAuthorizer: authorizer}
|
||||
}
|
||||
|
||||
func NewResourceAuthorizerVal(userInfo user.Info, authorizer authorizer.Authorizer, requestResource Resource) ref.Val {
|
||||
a := authorizerVal{receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType), userInfo: userInfo, authAuthorizer: authorizer}
|
||||
resource := requestResource.GetResource()
|
||||
g := a.groupCheck(resource.Group)
|
||||
r := g.resourceCheck(resource.Resource)
|
||||
r.subresource = requestResource.GetSubresource()
|
||||
r.namespace = requestResource.GetNamespace()
|
||||
r.name = requestResource.GetName()
|
||||
return r
|
||||
}
|
||||
|
||||
type authorizerVal struct {
|
||||
receiverOnlyObjectVal
|
||||
userInfo user.Info
|
||||
authAuthorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
func (a authorizerVal) pathCheck(path string) pathCheckVal {
|
||||
return pathCheckVal{receiverOnlyObjectVal: receiverOnlyVal(PathCheckType), authorizer: a, path: path}
|
||||
}
|
||||
|
||||
func (a authorizerVal) groupCheck(group string) groupCheckVal {
|
||||
return groupCheckVal{receiverOnlyObjectVal: receiverOnlyVal(GroupCheckType), authorizer: a, group: group}
|
||||
}
|
||||
|
||||
func (a authorizerVal) serviceAccount(namespace, name string) authorizerVal {
|
||||
sa := &serviceaccount.ServiceAccountInfo{Name: name, Namespace: namespace}
|
||||
return authorizerVal{
|
||||
receiverOnlyObjectVal: receiverOnlyVal(AuthorizerType),
|
||||
userInfo: sa.UserInfo(),
|
||||
authAuthorizer: a.authAuthorizer,
|
||||
}
|
||||
}
|
||||
|
||||
type pathCheckVal struct {
|
||||
receiverOnlyObjectVal
|
||||
authorizer authorizerVal
|
||||
path string
|
||||
}
|
||||
|
||||
func (a pathCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
|
||||
attr := &authorizer.AttributesRecord{
|
||||
Path: a.path,
|
||||
Verb: verb,
|
||||
User: a.authorizer.userInfo,
|
||||
}
|
||||
|
||||
decision, reason, err := a.authorizer.authAuthorizer.Authorize(ctx, attr)
|
||||
return newDecision(decision, err, reason)
|
||||
}
|
||||
|
||||
type groupCheckVal struct {
|
||||
receiverOnlyObjectVal
|
||||
authorizer authorizerVal
|
||||
group string
|
||||
}
|
||||
|
||||
func (g groupCheckVal) resourceCheck(resource string) resourceCheckVal {
|
||||
return resourceCheckVal{receiverOnlyObjectVal: receiverOnlyVal(ResourceCheckType), groupCheck: g, resource: resource}
|
||||
}
|
||||
|
||||
type resourceCheckVal struct {
|
||||
receiverOnlyObjectVal
|
||||
groupCheck groupCheckVal
|
||||
resource string
|
||||
subresource string
|
||||
namespace string
|
||||
name string
|
||||
}
|
||||
|
||||
func (a resourceCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
|
||||
attr := &authorizer.AttributesRecord{
|
||||
ResourceRequest: true,
|
||||
APIGroup: a.groupCheck.group,
|
||||
APIVersion: "*",
|
||||
Resource: a.resource,
|
||||
Subresource: a.subresource,
|
||||
Namespace: a.namespace,
|
||||
Name: a.name,
|
||||
Verb: verb,
|
||||
User: a.groupCheck.authorizer.userInfo,
|
||||
}
|
||||
decision, reason, err := a.groupCheck.authorizer.authAuthorizer.Authorize(ctx, attr)
|
||||
return newDecision(decision, err, reason)
|
||||
}
|
||||
|
||||
func newDecision(authDecision authorizer.Decision, err error, reason string) decisionVal {
|
||||
return decisionVal{receiverOnlyObjectVal: receiverOnlyVal(DecisionType), authDecision: authDecision, err: err, reason: reason}
|
||||
}
|
||||
|
||||
type decisionVal struct {
|
||||
receiverOnlyObjectVal
|
||||
err error
|
||||
authDecision authorizer.Decision
|
||||
reason string
|
||||
}
|
||||
|
||||
// receiverOnlyObjectVal provides an implementation of ref.Val for
|
||||
// any object type that has receiver functions but does not expose any fields to
|
||||
// CEL.
|
||||
type receiverOnlyObjectVal struct {
|
||||
typeValue *types.Type
|
||||
}
|
||||
|
||||
// receiverOnlyVal returns a receiverOnlyObjectVal for the given type.
|
||||
func receiverOnlyVal(objectType *cel.Type) receiverOnlyObjectVal {
|
||||
return receiverOnlyObjectVal{typeValue: types.NewTypeValue(objectType.String())}
|
||||
}
|
||||
|
||||
// ConvertToNative implements ref.Val.ConvertToNative.
|
||||
func (a receiverOnlyObjectVal) ConvertToNative(typeDesc reflect.Type) (any, error) {
|
||||
return nil, fmt.Errorf("type conversion error from '%s' to '%v'", a.typeValue.String(), typeDesc)
|
||||
}
|
||||
|
||||
// ConvertToType implements ref.Val.ConvertToType.
|
||||
func (a receiverOnlyObjectVal) ConvertToType(typeVal ref.Type) ref.Val {
|
||||
switch typeVal {
|
||||
case a.typeValue:
|
||||
return a
|
||||
case types.TypeType:
|
||||
return a.typeValue
|
||||
}
|
||||
return types.NewErr("type conversion error from '%s' to '%s'", a.typeValue, typeVal)
|
||||
}
|
||||
|
||||
// Equal implements ref.Val.Equal.
|
||||
func (a receiverOnlyObjectVal) Equal(other ref.Val) ref.Val {
|
||||
o, ok := other.(receiverOnlyObjectVal)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(other)
|
||||
}
|
||||
return types.Bool(a == o)
|
||||
}
|
||||
|
||||
// Type implements ref.Val.Type.
|
||||
func (a receiverOnlyObjectVal) Type() ref.Type {
|
||||
return a.typeValue
|
||||
}
|
||||
|
||||
// Value implements ref.Val.Value.
|
||||
func (a receiverOnlyObjectVal) Value() any {
|
||||
return types.NoSuchOverloadErr()
|
||||
}
|
||||
80
vendor/k8s.io/apiserver/pkg/cel/library/cost.go
generated
vendored
80
vendor/k8s.io/apiserver/pkg/cel/library/cost.go
generated
vendored
@@ -36,6 +36,15 @@ type CostEstimator struct {
|
||||
|
||||
func (l *CostEstimator) CallCost(function, overloadId string, args []ref.Val, result ref.Val) *uint64 {
|
||||
switch function {
|
||||
case "check":
|
||||
// An authorization check has a fixed cost
|
||||
// This cost is set to allow for only two authorization checks per expression
|
||||
cost := uint64(350000)
|
||||
return &cost
|
||||
case "serviceAccount", "path", "group", "resource", "subresource", "namespace", "name", "allowed", "reason", "error", "errored":
|
||||
// All authorization builder and accessor functions have a nominal cost
|
||||
cost := uint64(1)
|
||||
return &cost
|
||||
case "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
|
||||
var cost uint64
|
||||
if len(args) > 0 {
|
||||
@@ -78,6 +87,13 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
// WARNING: Any changes to this code impact API compatibility! The estimated cost is used to determine which CEL rules may be written to a
|
||||
// CRD and any change (cost increases and cost decreases) are breaking.
|
||||
switch function {
|
||||
case "check":
|
||||
// An authorization check has a fixed cost
|
||||
// This cost is set to allow for only two authorization checks per expression
|
||||
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 350000, Max: 350000}}
|
||||
case "serviceAccount", "path", "group", "resource", "subresource", "namespace", "name", "allowed", "reason", "error", "errored":
|
||||
// All authorization builder and accessor functions have a nominal cost
|
||||
return &checker.CallEstimate{CostEstimate: checker.CostEstimate{Min: 1, Max: 1}}
|
||||
case "isSorted", "sum", "max", "min", "indexOf", "lastIndexOf":
|
||||
if target != nil {
|
||||
// Charge 1 cost for comparing each element in the list
|
||||
@@ -85,8 +101,8 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
// If the list contains strings or bytes, add the cost of traversing all the strings/bytes as a way
|
||||
// of estimating the additional comparison cost.
|
||||
if elNode := l.listElementNode(*target); elNode != nil {
|
||||
t := elNode.Type().GetPrimitive()
|
||||
if t == exprpb.Type_STRING || t == exprpb.Type_BYTES {
|
||||
k := elNode.Type().Kind()
|
||||
if k == types.StringKind || k == types.BytesKind {
|
||||
sz := l.sizeEstimate(elNode)
|
||||
elCost = elCost.Add(sz.MultiplyByCostFactor(common.StringTraversalCostFactor))
|
||||
}
|
||||
@@ -94,7 +110,6 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
} else { // the target is a string, which is supported by indexOf and lastIndexOf
|
||||
return &checker.CallEstimate{CostEstimate: l.sizeEstimate(*target).MultiplyByCostFactor(common.StringTraversalCostFactor)}
|
||||
}
|
||||
|
||||
}
|
||||
case "url":
|
||||
if len(args) == 1 {
|
||||
@@ -111,13 +126,51 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
||||
sz := l.sizeEstimate(*target)
|
||||
toReplaceSz := l.sizeEstimate(args[0])
|
||||
replaceWithSz := l.sizeEstimate(args[1])
|
||||
// smallest possible result: smallest input size composed of the largest possible substrings being replaced by smallest possible replacement
|
||||
minSz := uint64(math.Ceil(float64(sz.Min)/float64(toReplaceSz.Max))) * replaceWithSz.Min
|
||||
// largest possible result: largest input size composed of the smallest possible substrings being replaced by largest possible replacement
|
||||
maxSz := uint64(math.Ceil(float64(sz.Max)/float64(toReplaceSz.Min))) * replaceWithSz.Max
|
||||
|
||||
var replaceCount, retainedSz checker.SizeEstimate
|
||||
// find the longest replacement:
|
||||
if toReplaceSz.Min == 0 {
|
||||
// if the string being replaced is empty, replace surrounds all characters in the input string with the replacement.
|
||||
if sz.Max < math.MaxUint64 {
|
||||
replaceCount.Max = sz.Max + 1
|
||||
} else {
|
||||
replaceCount.Max = sz.Max
|
||||
}
|
||||
// Include the length of the longest possible original string length.
|
||||
retainedSz.Max = sz.Max
|
||||
} else if replaceWithSz.Max <= toReplaceSz.Min {
|
||||
// If the replacement does not make the result longer, use the original string length.
|
||||
replaceCount.Max = 0
|
||||
retainedSz.Max = sz.Max
|
||||
} else {
|
||||
// Replace the smallest possible substrings with the largest possible replacement
|
||||
// as many times as possible.
|
||||
replaceCount.Max = uint64(math.Ceil(float64(sz.Max) / float64(toReplaceSz.Min)))
|
||||
}
|
||||
|
||||
// find the shortest replacement:
|
||||
if toReplaceSz.Max == 0 {
|
||||
// if the string being replaced is empty, replace surrounds all characters in the input string with the replacement.
|
||||
if sz.Min < math.MaxUint64 {
|
||||
replaceCount.Min = sz.Min + 1
|
||||
} else {
|
||||
replaceCount.Min = sz.Min
|
||||
}
|
||||
// Include the length of the shortest possible original string length.
|
||||
retainedSz.Min = sz.Min
|
||||
} else if toReplaceSz.Max <= replaceWithSz.Min {
|
||||
// If the replacement does not make the result shorter, use the original string length.
|
||||
replaceCount.Min = 0
|
||||
retainedSz.Min = sz.Min
|
||||
} else {
|
||||
// Replace the largest possible substrings being with the smallest possible replacement
|
||||
// as many times as possible.
|
||||
replaceCount.Min = uint64(math.Ceil(float64(sz.Min) / float64(toReplaceSz.Max)))
|
||||
}
|
||||
size := replaceCount.Multiply(replaceWithSz).Add(retainedSz)
|
||||
|
||||
// cost is the traversal plus the construction of the result
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &checker.SizeEstimate{Min: minSz, Max: maxSz}}
|
||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &size}
|
||||
}
|
||||
case "split":
|
||||
if target != nil {
|
||||
@@ -194,7 +247,8 @@ func (l *CostEstimator) sizeEstimate(t checker.AstNode) checker.SizeEstimate {
|
||||
}
|
||||
|
||||
func (l *CostEstimator) listElementNode(list checker.AstNode) checker.AstNode {
|
||||
if lt := list.Type().GetListType(); lt != nil {
|
||||
if params := list.Type().Parameters(); len(params) > 0 {
|
||||
lt := params[0]
|
||||
nodePath := list.Path()
|
||||
if nodePath != nil {
|
||||
// Provide path if we have it so that a OpenAPIv3 maxLength validation can be looked up, if it exists
|
||||
@@ -202,10 +256,10 @@ func (l *CostEstimator) listElementNode(list checker.AstNode) checker.AstNode {
|
||||
path := make([]string, len(nodePath)+1)
|
||||
copy(path, nodePath)
|
||||
path[len(nodePath)] = "@items"
|
||||
return &itemsNode{path: path, t: lt.GetElemType(), expr: nil}
|
||||
return &itemsNode{path: path, t: lt, expr: nil}
|
||||
} else {
|
||||
// Provide just the type if no path is available so that worst case size can be looked up based on type.
|
||||
return &itemsNode{t: lt.GetElemType(), expr: nil}
|
||||
return &itemsNode{t: lt, expr: nil}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -220,7 +274,7 @@ func (l *CostEstimator) EstimateSize(element checker.AstNode) *checker.SizeEstim
|
||||
|
||||
type itemsNode struct {
|
||||
path []string
|
||||
t *exprpb.Type
|
||||
t *types.Type
|
||||
expr *exprpb.Expr
|
||||
}
|
||||
|
||||
@@ -228,7 +282,7 @@ func (i *itemsNode) Path() []string {
|
||||
return i.path
|
||||
}
|
||||
|
||||
func (i *itemsNode) Type() *exprpb.Type {
|
||||
func (i *itemsNode) Type() *types.Type {
|
||||
return i.t
|
||||
}
|
||||
|
||||
|
||||
34
vendor/k8s.io/apiserver/pkg/cel/library/libraries.go
generated
vendored
34
vendor/k8s.io/apiserver/pkg/cel/library/libraries.go
generated
vendored
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package library
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/ext"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
)
|
||||
|
||||
// ExtensionLibs declares the set of CEL extension libraries available everywhere CEL is used in Kubernetes.
|
||||
var ExtensionLibs = append(k8sExtensionLibs, ext.Strings())
|
||||
|
||||
var k8sExtensionLibs = []cel.EnvOption{
|
||||
URLs(),
|
||||
Regex(),
|
||||
Lists(),
|
||||
}
|
||||
|
||||
var ExtensionLibRegexOptimizations = []*interpreter.RegexOptimization{FindRegexOptimization, FindAllRegexOptimization}
|
||||
4
vendor/k8s.io/apiserver/pkg/cel/library/lists.go
generated
vendored
4
vendor/k8s.io/apiserver/pkg/cel/library/lists.go
generated
vendored
@@ -95,6 +95,10 @@ var listsLib = &lists{}
|
||||
|
||||
type lists struct{}
|
||||
|
||||
func (*lists) LibraryName() string {
|
||||
return "k8s.lists"
|
||||
}
|
||||
|
||||
var paramA = cel.TypeParamType("A")
|
||||
|
||||
// CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain.
|
||||
|
||||
380
vendor/k8s.io/apiserver/pkg/cel/library/quantity.go
generated
vendored
Normal file
380
vendor/k8s.io/apiserver/pkg/cel/library/quantity.go
generated
vendored
Normal file
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
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 library
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
)
|
||||
|
||||
// Quantity provides a CEL function library extension of Kubernetes
|
||||
// resource.Quantity parsing functions. See `resource.Quantity`
|
||||
// documentation for more detailed information about the format itself:
|
||||
// https://pkg.go.dev/k8s.io/apimachinery/pkg/api/resource#Quantity
|
||||
//
|
||||
// quantity
|
||||
//
|
||||
// Converts a string to a Quantity or results in an error if the string is not a valid Quantity. Refer
|
||||
// to resource.Quantity documentation for information on accepted patterns.
|
||||
//
|
||||
// quantity(<string>) <Quantity>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// quantity('1.5G') // returns a Quantity
|
||||
// quantity('200k') // returns a Quantity
|
||||
// quantity('200K') // error
|
||||
// quantity('Three') // error
|
||||
// quantity('Mi') // error
|
||||
//
|
||||
// isQuantity
|
||||
//
|
||||
// Returns true if a string is a valid Quantity. isQuantity returns true if and
|
||||
// only if quantity does not result in error.
|
||||
//
|
||||
// isQuantity( <string>) <bool>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// isQuantity('1.3G') // returns true
|
||||
// isQuantity('1.3Gi') // returns true
|
||||
// isQuantity('1,3G') // returns false
|
||||
// isQuantity('10000k') // returns true
|
||||
// isQuantity('200K') // returns false
|
||||
// isQuantity('Three') // returns false
|
||||
// isQuantity('Mi') // returns false
|
||||
//
|
||||
// Conversion to Scalars:
|
||||
//
|
||||
// - isInteger: returns true if and only if asInteger is safe to call without an error
|
||||
//
|
||||
// - asInteger: returns a representation of the current value as an int64 if
|
||||
// possible or results in an error if conversion would result in overflow
|
||||
// or loss of precision.
|
||||
//
|
||||
// - asApproximateFloat: returns a float64 representation of the quantity which may
|
||||
// lose precision. If the value of the quantity is outside the range of a float64
|
||||
// +Inf/-Inf will be returned.
|
||||
//
|
||||
// <Quantity>.isInteger() <bool>
|
||||
// <Quantity>.asInteger() <int>
|
||||
// <Quantity>.asApproximateFloat() <float>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// quantity("50000000G").isInteger() // returns true
|
||||
// quantity("50k").isInteger() // returns true
|
||||
// quantity("9999999999999999999999999999999999999G").asInteger() // error: cannot convert value to integer
|
||||
// quantity("9999999999999999999999999999999999999G").isInteger() // returns false
|
||||
// quantity("50k").asInteger() == 50000 // returns true
|
||||
// quantity("50k").sub(20000).asApproximateFloat() == 30000 // returns true
|
||||
//
|
||||
// Arithmetic
|
||||
//
|
||||
// - sign: Returns `1` if the quantity is positive, `-1` if it is negative. `0` if it is zero
|
||||
//
|
||||
// - add: Returns sum of two quantities or a quantity and an integer
|
||||
//
|
||||
// - sub: Returns difference between two quantities or a quantity and an integer
|
||||
//
|
||||
// <Quantity>.sign() <int>
|
||||
// <Quantity>.add(<quantity>) <quantity>
|
||||
// <Quantity>.add(<integer>) <quantity>
|
||||
// <Quantity>.sub(<quantity>) <quantity>
|
||||
// <Quantity>.sub(<integer>) <quantity>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// quantity("50k").add("20k") == quantity("70k") // returns true
|
||||
// quantity("50k").add(20) == quantity("50020") // returns true
|
||||
// quantity("50k").sub("20k") == quantity("30k") // returns true
|
||||
// quantity("50k").sub(20000) == quantity("30k") // returns true
|
||||
// quantity("50k").add(20).sub(quantity("100k")).sub(-50000) == quantity("20") // returns true
|
||||
//
|
||||
// Comparisons
|
||||
//
|
||||
// - isGreaterThan: Returns true if and only if the receiver is greater than the operand
|
||||
//
|
||||
// - isLessThan: Returns true if and only if the receiver is less than the operand
|
||||
//
|
||||
// - compareTo: Compares receiver to operand and returns 0 if they are equal, 1 if the receiver is greater, or -1 if the receiver is less than the operand
|
||||
//
|
||||
//
|
||||
// <Quantity>.isLessThan(<quantity>) <bool>
|
||||
// <Quantity>.isGreaterThan(<quantity>) <bool>
|
||||
// <Quantity>.compareTo(<quantity>) <int>
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// quantity("200M").compareTo(quantity("0.2G")) // returns 0
|
||||
// quantity("50M").compareTo(quantity("50Mi")) // returns -1
|
||||
// quantity("50Mi").compareTo(quantity("50M")) // returns 1
|
||||
// quantity("150Mi").isGreaterThan(quantity("100Mi")) // returns true
|
||||
// quantity("50Mi").isGreaterThan(quantity("100Mi")) // returns false
|
||||
// quantity("50M").isLessThan(quantity("100M")) // returns true
|
||||
// quantity("100M").isLessThan(quantity("50M")) // returns false
|
||||
|
||||
func Quantity() cel.EnvOption {
|
||||
return cel.Lib(quantityLib)
|
||||
}
|
||||
|
||||
var quantityLib = &quantity{}
|
||||
|
||||
type quantity struct{}
|
||||
|
||||
func (*quantity) LibraryName() string {
|
||||
return "k8s.quantity"
|
||||
}
|
||||
|
||||
var quantityLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"quantity": {
|
||||
cel.Overload("string_to_quantity", []*cel.Type{cel.StringType}, apiservercel.QuantityType, cel.UnaryBinding((stringToQuantity))),
|
||||
},
|
||||
"isQuantity": {
|
||||
cel.Overload("is_quantity_string", []*cel.Type{cel.StringType}, cel.BoolType, cel.UnaryBinding(isQuantity)),
|
||||
},
|
||||
"sign": {
|
||||
cel.Overload("quantity_sign", []*cel.Type{apiservercel.QuantityType}, cel.IntType, cel.UnaryBinding(quantityGetSign)),
|
||||
},
|
||||
"isGreaterThan": {
|
||||
cel.MemberOverload("quantity_is_greater_than", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, cel.BoolType, cel.BinaryBinding(quantityIsGreaterThan)),
|
||||
},
|
||||
"isLessThan": {
|
||||
cel.MemberOverload("quantity_is_less_than", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, cel.BoolType, cel.BinaryBinding(quantityIsLessThan)),
|
||||
},
|
||||
"compareTo": {
|
||||
cel.MemberOverload("quantity_compare_to", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, cel.IntType, cel.BinaryBinding(quantityCompareTo)),
|
||||
},
|
||||
"asApproximateFloat": {
|
||||
cel.MemberOverload("quantity_get_float", []*cel.Type{apiservercel.QuantityType}, cel.DoubleType, cel.UnaryBinding(quantityGetApproximateFloat)),
|
||||
},
|
||||
"asInteger": {
|
||||
cel.MemberOverload("quantity_get_int", []*cel.Type{apiservercel.QuantityType}, cel.IntType, cel.UnaryBinding(quantityGetValue)),
|
||||
},
|
||||
"isInteger": {
|
||||
cel.MemberOverload("quantity_is_integer", []*cel.Type{apiservercel.QuantityType}, cel.BoolType, cel.UnaryBinding(quantityCanValue)),
|
||||
},
|
||||
"add": {
|
||||
cel.MemberOverload("quantity_add", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, apiservercel.QuantityType, cel.BinaryBinding(quantityAdd)),
|
||||
cel.MemberOverload("quantity_add_int", []*cel.Type{apiservercel.QuantityType, cel.IntType}, apiservercel.QuantityType, cel.BinaryBinding(quantityAddInt)),
|
||||
},
|
||||
"sub": {
|
||||
cel.MemberOverload("quantity_sub", []*cel.Type{apiservercel.QuantityType, apiservercel.QuantityType}, apiservercel.QuantityType, cel.BinaryBinding(quantitySub)),
|
||||
cel.MemberOverload("quantity_sub_int", []*cel.Type{apiservercel.QuantityType, cel.IntType}, apiservercel.QuantityType, cel.BinaryBinding(quantitySubInt)),
|
||||
},
|
||||
}
|
||||
|
||||
func (*quantity) CompileOptions() []cel.EnvOption {
|
||||
options := make([]cel.EnvOption, 0, len(quantityLibraryDecls))
|
||||
for name, overloads := range quantityLibraryDecls {
|
||||
options = append(options, cel.Function(name, overloads...))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*quantity) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
|
||||
func isQuantity(arg ref.Val) ref.Val {
|
||||
str, ok := arg.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
_, err := resource.ParseQuantity(str)
|
||||
if err != nil {
|
||||
return types.Bool(false)
|
||||
}
|
||||
|
||||
return types.Bool(true)
|
||||
}
|
||||
|
||||
func stringToQuantity(arg ref.Val) ref.Val {
|
||||
str, ok := arg.Value().(string)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q, err := resource.ParseQuantity(str)
|
||||
if err != nil {
|
||||
return types.WrapErr(err)
|
||||
}
|
||||
|
||||
return apiservercel.Quantity{Quantity: &q}
|
||||
}
|
||||
|
||||
func quantityGetApproximateFloat(arg ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.Double(q.AsApproximateFloat64())
|
||||
}
|
||||
|
||||
func quantityCanValue(arg ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
_, success := q.AsInt64()
|
||||
return types.Bool(success)
|
||||
}
|
||||
|
||||
func quantityGetValue(arg ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
v, success := q.AsInt64()
|
||||
if !success {
|
||||
return types.WrapErr(errors.New("cannot convert value to integer"))
|
||||
}
|
||||
return types.Int(v)
|
||||
}
|
||||
|
||||
func quantityGetSign(arg ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
return types.Int(q.Sign())
|
||||
}
|
||||
|
||||
func quantityIsGreaterThan(arg ref.Val, other ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2, ok := other.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
return types.Bool(q.Cmp(*q2) == 1)
|
||||
}
|
||||
|
||||
func quantityIsLessThan(arg ref.Val, other ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2, ok := other.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
return types.Bool(q.Cmp(*q2) == -1)
|
||||
}
|
||||
|
||||
func quantityCompareTo(arg ref.Val, other ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2, ok := other.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
return types.Int(q.Cmp(*q2))
|
||||
}
|
||||
|
||||
func quantityAdd(arg ref.Val, other ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2, ok := other.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
copy := *q
|
||||
copy.Add(*q2)
|
||||
return &apiservercel.Quantity{
|
||||
Quantity: ©,
|
||||
}
|
||||
}
|
||||
|
||||
func quantityAddInt(arg ref.Val, other ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2, ok := other.Value().(int64)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2Converted := *resource.NewQuantity(q2, resource.DecimalExponent)
|
||||
|
||||
copy := *q
|
||||
copy.Add(q2Converted)
|
||||
return &apiservercel.Quantity{
|
||||
Quantity: ©,
|
||||
}
|
||||
}
|
||||
|
||||
func quantitySub(arg ref.Val, other ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2, ok := other.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
copy := *q
|
||||
copy.Sub(*q2)
|
||||
return &apiservercel.Quantity{
|
||||
Quantity: ©,
|
||||
}
|
||||
}
|
||||
|
||||
func quantitySubInt(arg ref.Val, other ref.Val) ref.Val {
|
||||
q, ok := arg.Value().(*resource.Quantity)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2, ok := other.Value().(int64)
|
||||
if !ok {
|
||||
return types.MaybeNoSuchOverloadErr(arg)
|
||||
}
|
||||
|
||||
q2Converted := *resource.NewQuantity(q2, resource.DecimalExponent)
|
||||
|
||||
copy := *q
|
||||
copy.Sub(q2Converted)
|
||||
return &apiservercel.Quantity{
|
||||
Quantity: ©,
|
||||
}
|
||||
}
|
||||
8
vendor/k8s.io/apiserver/pkg/cel/library/regex.go
generated
vendored
8
vendor/k8s.io/apiserver/pkg/cel/library/regex.go
generated
vendored
@@ -51,6 +51,10 @@ var regexLib = ®ex{}
|
||||
|
||||
type regex struct{}
|
||||
|
||||
func (*regex) LibraryName() string {
|
||||
return "k8s.regex"
|
||||
}
|
||||
|
||||
var regexLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"find": {
|
||||
cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType,
|
||||
@@ -77,7 +81,9 @@ func (*regex) CompileOptions() []cel.EnvOption {
|
||||
}
|
||||
|
||||
func (*regex) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
return []cel.ProgramOption{
|
||||
cel.OptimizeRegex(FindRegexOptimization, FindAllRegexOptimization),
|
||||
}
|
||||
}
|
||||
|
||||
func find(strVal ref.Val, regexVal ref.Val) ref.Val {
|
||||
|
||||
83
vendor/k8s.io/apiserver/pkg/cel/library/test.go
generated
vendored
Normal file
83
vendor/k8s.io/apiserver/pkg/cel/library/test.go
generated
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
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 library
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
|
||||
// Test provides a test() function that returns true.
|
||||
func Test(options ...TestOption) cel.EnvOption {
|
||||
t := &testLib{version: math.MaxUint32}
|
||||
for _, o := range options {
|
||||
t = o(t)
|
||||
}
|
||||
return cel.Lib(t)
|
||||
}
|
||||
|
||||
type testLib struct {
|
||||
version uint32
|
||||
}
|
||||
|
||||
func (*testLib) LibraryName() string {
|
||||
return "k8s.test"
|
||||
}
|
||||
|
||||
type TestOption func(*testLib) *testLib
|
||||
|
||||
func TestVersion(version uint32) func(lib *testLib) *testLib {
|
||||
return func(sl *testLib) *testLib {
|
||||
sl.version = version
|
||||
return sl
|
||||
}
|
||||
}
|
||||
|
||||
func (t *testLib) CompileOptions() []cel.EnvOption {
|
||||
var options []cel.EnvOption
|
||||
|
||||
if t.version == 0 {
|
||||
options = append(options, cel.Function("test",
|
||||
cel.Overload("test", []*cel.Type{}, cel.BoolType,
|
||||
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
|
||||
return types.True
|
||||
}))))
|
||||
}
|
||||
|
||||
if t.version >= 1 {
|
||||
options = append(options, cel.Function("test",
|
||||
cel.Overload("test", []*cel.Type{}, cel.BoolType,
|
||||
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
|
||||
// Return false here so tests can observe which version of the function is registered
|
||||
// Actual function libraries must not break backward compatibility
|
||||
return types.False
|
||||
}))))
|
||||
options = append(options, cel.Function("testV1",
|
||||
cel.Overload("testV1", []*cel.Type{}, cel.BoolType,
|
||||
cel.FunctionBinding(func(args ...ref.Val) ref.Val {
|
||||
return types.True
|
||||
}))))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func (*testLib) ProgramOptions() []cel.ProgramOption {
|
||||
return []cel.ProgramOption{}
|
||||
}
|
||||
8
vendor/k8s.io/apiserver/pkg/cel/library/urls.go
generated
vendored
8
vendor/k8s.io/apiserver/pkg/cel/library/urls.go
generated
vendored
@@ -61,9 +61,9 @@ import (
|
||||
//
|
||||
// - getScheme: If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getHostname: IPv6 addresses are returned with braces, e.g. "[::1]". If absent in the URL, returns an empty string.
|
||||
// - getHostname: IPv6 addresses are returned without braces, e.g. "::1". If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getHost: IPv6 addresses are returned without braces, e.g. "::1". If absent in the URL, returns an empty string.
|
||||
// - getHost: IPv6 addresses are returned with braces, e.g. "[::1]". If absent in the URL, returns an empty string.
|
||||
//
|
||||
// - getEscapedPath: The string returned by getEscapedPath is URL escaped, e.g. "with space" becomes "with%20space".
|
||||
// If absent in the URL, returns an empty string.
|
||||
@@ -112,6 +112,10 @@ var urlsLib = &urls{}
|
||||
|
||||
type urls struct{}
|
||||
|
||||
func (*urls) LibraryName() string {
|
||||
return "k8s.urls"
|
||||
}
|
||||
|
||||
var urlLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"url": {
|
||||
cel.Overload("string_to_url", []*cel.Type{cel.StringType}, apiservercel.URLType,
|
||||
|
||||
Reference in New Issue
Block a user