diff --git a/pkg/cel/libs/user/impl.go b/pkg/cel/libs/user/impl.go new file mode 100644 index 0000000000..b851c74ca8 --- /dev/null +++ b/pkg/cel/libs/user/impl.go @@ -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) + } +} diff --git a/pkg/cel/libs/user/impl_test.go b/pkg/cel/libs/user/impl_test.go new file mode 100644 index 0000000000..0acf3cfaa8 --- /dev/null +++ b/pkg/cel/libs/user/impl_test.go @@ -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) + }) + } +} diff --git a/pkg/cel/libs/user/lib.go b/pkg/cel/libs/user/lib.go new file mode 100644 index 0000000000..1be0d1077d --- /dev/null +++ b/pkg/cel/libs/user/lib.go @@ -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...) +} diff --git a/pkg/cel/libs/user/types.go b/pkg/cel/libs/user/types.go new file mode 100644 index 0000000000..4f7fdb3888 --- /dev/null +++ b/pkg/cel/libs/user/types.go @@ -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"` +} diff --git a/pkg/cel/policy/compiler.go b/pkg/cel/policy/compiler.go index 73fff63b66..6d5f80f371 100644 --- a/pkg/cel/policy/compiler.go +++ b/pkg/cel/policy/compiler.go @@ -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 { diff --git a/pkg/imageverification/evaluator/compiler.go b/pkg/imageverification/evaluator/compiler.go index a98440e8f6..a25a6c41f1 100644 --- a/pkg/imageverification/evaluator/compiler.go +++ b/pkg/imageverification/evaluator/compiler.go @@ -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))