1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-24 08:36:46 +00:00

feat: add cel user lib (#12414)

* feat: add cel user lib

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* unit test

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
Charles-Edouard Brétéché 2025-03-14 13:51:25 +01:00 committed by GitHub
parent a2ed5014e3
commit af550f54d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 156 additions and 2 deletions

33
pkg/cel/libs/user/impl.go Normal file
View file

@ -0,0 +1,33 @@
package user
import (
"strings"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
"github.com/kyverno/kyverno/pkg/cel/utils"
)
const (
saPrefix = "system:serviceaccount:"
)
type impl struct {
types.Adapter
}
func (c *impl) parse_service_account_string(user ref.Val) ref.Val {
if user, err := utils.ConvertToNative[string](user); err != nil {
return types.WrapErr(err)
} else {
var sa ServiceAccount
if strings.HasPrefix(user, saPrefix) {
user = user[len(saPrefix):]
if sep := strings.Index(user, ":"); sep != -1 {
sa.Namesapce = user[:sep]
sa.Name = user[sep+1:]
}
}
return c.NativeToValue(sa)
}
}

View file

@ -0,0 +1,50 @@
package user
import (
"fmt"
"testing"
"github.com/google/cel-go/cel"
"github.com/kyverno/kyverno/pkg/cel/utils"
"github.com/stretchr/testify/assert"
)
func Test_impl_parse_service_account_string(t *testing.T) {
tests := []struct {
name string
user string
want ServiceAccount
}{{
name: "simple",
user: "system:serviceaccount:foo:bar",
want: ServiceAccount{Namesapce: "foo", Name: "bar"},
}, {
name: "with :",
user: "system:serviceaccount:foo:bar:baz",
want: ServiceAccount{Namesapce: "foo", Name: "bar:baz"},
}, {
name: "not a service account",
user: "something-else:123",
want: ServiceAccount{},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
opts := Lib()
env, err := cel.NewEnv(opts)
assert.NoError(t, err)
assert.NotNil(t, env)
ast, issues := env.Compile(fmt.Sprintf(`user.ParseServiceAccount("%s")`, tt.user))
fmt.Println(issues.String())
assert.Nil(t, issues)
assert.NotNil(t, ast)
prog, err := env.Program(ast)
assert.NoError(t, err)
assert.NotNil(t, prog)
out, _, err := prog.Eval(map[string]any{})
assert.NoError(t, err)
sa, err := utils.ConvertToNative[ServiceAccount](out)
assert.NoError(t, err)
assert.Equal(t, tt.want, sa)
})
}
}

59
pkg/cel/libs/user/lib.go Normal file
View file

@ -0,0 +1,59 @@
package user
import (
"reflect"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/ext"
)
const libraryName = "kyverno.user"
type lib struct{}
func Lib() cel.EnvOption {
// create the cel lib env option
return cel.Lib(&lib{})
}
func (*lib) NativeTypes() []reflect.Type {
return []reflect.Type{
reflect.TypeFor[ServiceAccount](),
}
}
func (*lib) LibraryName() string {
return libraryName
}
func (c *lib) CompileOptions() []cel.EnvOption {
return []cel.EnvOption{
ext.NativeTypes(reflect.TypeFor[ServiceAccount]()),
c.extendEnv,
}
}
func (*lib) ProgramOptions() []cel.ProgramOption {
return []cel.ProgramOption{}
}
func (c *lib) extendEnv(env *cel.Env) (*cel.Env, error) {
// create implementation, recording the envoy types aware adapter
impl := impl{
Adapter: env.CELTypeAdapter(),
}
// build our function overloads
libraryDecls := map[string][]cel.FunctionOpt{
"user.ParseServiceAccount": {
cel.Overload("parse_service_account_string", []*cel.Type{types.StringType}, ServiceAccountType, cel.UnaryBinding(impl.parse_service_account_string)),
},
}
// create env options corresponding to our function overloads
options := []cel.EnvOption{}
for name, overloads := range libraryDecls {
options = append(options, cel.Function(name, overloads...))
}
// extend environment with our function overloads
return env.Extend(options...)
}

View file

@ -0,0 +1,10 @@
package user
import "github.com/google/cel-go/common/types"
var ServiceAccountType = types.NewObjectType("user.ServiceAccount")
type ServiceAccount struct {
Name string `json:"name,omitempty"`
Namesapce string `json:"namespace,omitempty"`
}

View file

@ -10,6 +10,7 @@ import (
vpolautogen "github.com/kyverno/kyverno/pkg/cel/autogen"
"github.com/kyverno/kyverno/pkg/cel/libs/context"
"github.com/kyverno/kyverno/pkg/cel/libs/http"
"github.com/kyverno/kyverno/pkg/cel/libs/user"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
apiservercel "k8s.io/apiserver/pkg/cel"
@ -128,7 +129,7 @@ func (c *compiler) compileForKubernetes(policy *policiesv1alpha1.ValidatingPolic
panic(err)
}
options = append(options, declOptions...)
options = append(options, context.Lib(), http.Lib())
options = append(options, context.Lib(), http.Lib(), user.Lib())
// TODO: params, authorizer, authorizer.requestResource ?
env, err := base.Extend(options...)
if err != nil {

View file

@ -7,6 +7,7 @@ import (
engine "github.com/kyverno/kyverno/pkg/cel"
"github.com/kyverno/kyverno/pkg/cel/libs/context"
"github.com/kyverno/kyverno/pkg/cel/libs/http"
"github.com/kyverno/kyverno/pkg/cel/libs/user"
"github.com/kyverno/kyverno/pkg/cel/policy"
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
"github.com/kyverno/kyverno/pkg/imageverification/imageverifierfunctions"
@ -76,7 +77,7 @@ func (c *compiler) Compile(logger logr.Logger, ivpolicy *policiesv1alpha1.ImageV
for _, declType := range declTypes {
options = append(options, cel.Types(declType.CelType()))
}
options = append(options, imageverifierfunctions.Lib(logger, c.ictx, ivpolicy, c.lister), context.Lib(), http.Lib())
options = append(options, imageverifierfunctions.Lib(logger, c.ictx, ivpolicy, c.lister), context.Lib(), http.Lib(), user.Lib())
env, err := base.Extend(options...)
if err != nil {
return nil, append(allErrs, field.InternalError(nil, err))