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:
parent
a2ed5014e3
commit
af550f54d5
6 changed files with 156 additions and 2 deletions
33
pkg/cel/libs/user/impl.go
Normal file
33
pkg/cel/libs/user/impl.go
Normal 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)
|
||||
}
|
||||
}
|
50
pkg/cel/libs/user/impl_test.go
Normal file
50
pkg/cel/libs/user/impl_test.go
Normal 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
59
pkg/cel/libs/user/lib.go
Normal 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...)
|
||||
}
|
10
pkg/cel/libs/user/types.go
Normal file
10
pkg/cel/libs/user/types.go
Normal 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"`
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Add table
Reference in a new issue