mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-22 07:41:10 +00:00
fix: image parse func and add chainsaw tests (#12396)
* fix: image parse func and add chainsaw tests Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> * fix: linter Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com> --------- Signed-off-by: Vishal Choudhary <vishal.choudhary@nirmata.com>
This commit is contained in:
parent
6fdbdbce28
commit
d56e6037a4
19 changed files with 598 additions and 116 deletions
|
@ -204,7 +204,7 @@ func (e *ivengine) handleMutation(ctx context.Context, policies []CompiledImageV
|
||||||
}
|
}
|
||||||
|
|
||||||
if p, errList := c.Compile(e.logger, ivpol.Policy); errList != nil {
|
if p, errList := c.Compile(e.logger, ivpol.Policy); errList != nil {
|
||||||
response.Result = *engineapi.RuleError("evaluation", engineapi.ImageVerify, "failed to compile policy", err, nil)
|
response.Result = *engineapi.RuleError("evaluation", engineapi.ImageVerify, "failed to compile policy", errList.ToAggregate(), nil)
|
||||||
} else {
|
} else {
|
||||||
result, err := p.Evaluate(ctx, ictx, attr, request, namespace, true)
|
result, err := p.Evaluate(ctx, ictx, attr, request, namespace, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -91,6 +91,10 @@ uOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Verifications: []admissionregistrationv1.Validation{
|
Verifications: []admissionregistrationv1.Validation{
|
||||||
|
{
|
||||||
|
Expression: "images.containers.map(i, image(i).registry() == \"ghcr.io\" ).all(e, e)",
|
||||||
|
Message: "images are not from ghcr registry",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Expression: "images.containers.map(image, verifyImageSignatures(image, [attestors.notary])).all(e, e > 0)",
|
Expression: "images.containers.map(image, verifyImageSignatures(image, [attestors.notary])).all(e, e > 0)",
|
||||||
Message: "failed to verify image with notary cert",
|
Message: "failed to verify image with notary cert",
|
||||||
|
|
|
@ -3,6 +3,7 @@ package cel
|
||||||
import (
|
import (
|
||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/ext"
|
"github.com/google/cel-go/ext"
|
||||||
|
"github.com/kyverno/kyverno/pkg/cel/libs/image"
|
||||||
"k8s.io/apiserver/pkg/cel/library"
|
"k8s.io/apiserver/pkg/cel/library"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,5 +31,6 @@ func NewEnv() (*cel.Env, error) {
|
||||||
library.Lists(),
|
library.Lists(),
|
||||||
library.Regex(),
|
library.Regex(),
|
||||||
library.URLs(),
|
library.URLs(),
|
||||||
|
image.ImageLib(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,21 +62,6 @@ func (c *impl) get_imagedata_string(ctx ref.Val, image ref.Val) ref.Val {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *impl) parse_imagereference_string(ctx ref.Val, image ref.Val) ref.Val {
|
|
||||||
if self, err := utils.ConvertToNative[Context](ctx); err != nil {
|
|
||||||
return types.WrapErr(err)
|
|
||||||
} else if image, err := utils.ConvertToNative[string](image); err != nil {
|
|
||||||
return types.WrapErr(err)
|
|
||||||
} else {
|
|
||||||
parsedRef, err := self.ParseImageReference(image)
|
|
||||||
if err != nil {
|
|
||||||
// Errors are not expected here since Parse is a more lenient parser than ParseRequestURI.
|
|
||||||
return types.NewErr("failed to parse image data: %v", err)
|
|
||||||
}
|
|
||||||
return c.NativeToValue(parsedRef)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *impl) list_resources_string_string_string(args ...ref.Val) ref.Val {
|
func (c *impl) list_resources_string_string_string(args ...ref.Val) ref.Val {
|
||||||
if self, err := utils.ConvertToNative[Context](args[0]); err != nil {
|
if self, err := utils.ConvertToNative[Context](args[0]); err != nil {
|
||||||
return types.WrapErr(err)
|
return types.WrapErr(err)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -146,7 +147,7 @@ func Test_impl_get_imagedata_string(t *testing.T) {
|
||||||
env, err := base.Extend(options...)
|
env, err := base.Extend(options...)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, env)
|
assert.NotNil(t, env)
|
||||||
ast, issues := env.Compile(`context.GetImageData("ghcr.io/kyverno/kyverno:latest")`)
|
ast, issues := env.Compile(`context.GetImageData("ghcr.io/kyverno/kyverno:latest").resolvedImage`)
|
||||||
assert.Nil(t, issues)
|
assert.Nil(t, issues)
|
||||||
assert.NotNil(t, ast)
|
assert.NotNil(t, ast)
|
||||||
prog, err := env.Program(ast)
|
prog, err := env.Program(ast)
|
||||||
|
@ -154,54 +155,30 @@ func Test_impl_get_imagedata_string(t *testing.T) {
|
||||||
assert.NotNil(t, prog)
|
assert.NotNil(t, prog)
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"context": Context{&MockCtx{
|
"context": Context{&MockCtx{
|
||||||
GetImageDataFunc: func(image string) (*imagedataloader.ImageData, error) {
|
GetImageDataFunc: func(image string) (map[string]interface{}, error) {
|
||||||
idl, err := imagedataloader.New(nil)
|
idl, err := imagedataloader.New(nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return idl.FetchImageData(context.TODO(), image)
|
data, err := idl.FetchImageData(context.TODO(), image)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
raw, err := json.Marshal(data.Data())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
apiData := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal(raw, &apiData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return apiData, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
out, _, err := prog.Eval(data)
|
out, _, err := prog.Eval(data)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
img := out.Value().(*imagedataloader.ImageData)
|
resolvedImg := out.Value().(string)
|
||||||
assert.Equal(t, img.Tag, "latest")
|
assert.True(t, strings.HasPrefix(resolvedImg, "ghcr.io/kyverno/kyverno:latest@sha256:"))
|
||||||
assert.True(t, strings.HasPrefix(img.ResolvedImage, "ghcr.io/kyverno/kyverno:latest@sha256:"))
|
|
||||||
assert.True(t, img.ConfigData != nil)
|
|
||||||
assert.True(t, img.Manifest != nil)
|
|
||||||
assert.True(t, img.ImageIndex != nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_impl_parse_image_ref_string(t *testing.T) {
|
|
||||||
opts := Lib()
|
|
||||||
base, err := cel.NewEnv(opts)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, base)
|
|
||||||
options := []cel.EnvOption{
|
|
||||||
cel.Variable("context", ContextType),
|
|
||||||
}
|
|
||||||
env, err := base.Extend(options...)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, env)
|
|
||||||
ast, issues := env.Compile(`context.ParseImageReference("ghcr.io/kyverno/kyverno:latest")`)
|
|
||||||
assert.Nil(t, issues)
|
|
||||||
assert.NotNil(t, ast)
|
|
||||||
prog, err := env.Program(ast)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotNil(t, prog)
|
|
||||||
data := map[string]any{
|
|
||||||
"context": Context{&MockCtx{
|
|
||||||
ParseImageReferenceFunc: func(image string) (imagedataloader.ImageReference, error) {
|
|
||||||
return imagedataloader.ParseImageReference(image)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
out, _, err := prog.Eval(data)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
img := out.Value().(imagedataloader.ImageReference)
|
|
||||||
assert.Equal(t, img.Tag, "latest")
|
|
||||||
assert.Equal(t, img.Identifier, "latest")
|
|
||||||
assert.Equal(t, img.Image, "ghcr.io/kyverno/kyverno:latest")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_impl_get_resource_string_string_string_string(t *testing.T) {
|
func Test_impl_get_resource_string_string_string_string(t *testing.T) {
|
||||||
|
|
|
@ -67,18 +67,10 @@ func (c *lib) extendEnv(env *cel.Env) (*cel.Env, error) {
|
||||||
cel.MemberOverload(
|
cel.MemberOverload(
|
||||||
"get_imagedata_string",
|
"get_imagedata_string",
|
||||||
[]*cel.Type{ContextType, types.StringType},
|
[]*cel.Type{ContextType, types.StringType},
|
||||||
imageDataType.CelType(),
|
types.DynType,
|
||||||
cel.BinaryBinding(impl.get_imagedata_string),
|
cel.BinaryBinding(impl.get_imagedata_string),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
"ParseImageReference": {
|
|
||||||
cel.MemberOverload(
|
|
||||||
"parse_image_reference_string",
|
|
||||||
[]*cel.Type{ContextType, types.StringType},
|
|
||||||
imageReferenceType.CelType(),
|
|
||||||
cel.BinaryBinding(impl.parse_imagereference_string),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
"ListResources": {
|
"ListResources": {
|
||||||
// TODO: should not use DynType in return
|
// TODO: should not use DynType in return
|
||||||
cel.MemberOverload(
|
cel.MemberOverload(
|
||||||
|
|
|
@ -2,18 +2,16 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kyverno/kyverno/pkg/globalcontext/store"
|
"github.com/kyverno/kyverno/pkg/globalcontext/store"
|
||||||
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MOCK FOR TESTING
|
// MOCK FOR TESTING
|
||||||
type MockCtx struct {
|
type MockCtx struct {
|
||||||
GetConfigMapFunc func(string, string) (*unstructured.Unstructured, error)
|
GetConfigMapFunc func(string, string) (*unstructured.Unstructured, error)
|
||||||
GetGlobalReferenceFunc func(string, string) (any, error)
|
GetGlobalReferenceFunc func(string, string) (any, error)
|
||||||
GetImageDataFunc func(string) (*imagedataloader.ImageData, error)
|
GetImageDataFunc func(string) (map[string]interface{}, error)
|
||||||
ParseImageReferenceFunc func(string) (imagedataloader.ImageReference, error)
|
ListResourcesFunc func(string, string, string) (*unstructured.UnstructuredList, error)
|
||||||
ListResourcesFunc func(string, string, string) (*unstructured.UnstructuredList, error)
|
GetResourceFunc func(string, string, string, string) (*unstructured.Unstructured, error)
|
||||||
GetResourceFunc func(string, string, string, string) (*unstructured.Unstructured, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mock *MockCtx) GetConfigMap(ns string, n string) (*unstructured.Unstructured, error) {
|
func (mock *MockCtx) GetConfigMap(ns string, n string) (*unstructured.Unstructured, error) {
|
||||||
|
@ -24,14 +22,10 @@ func (mock *MockCtx) GetGlobalReference(n, p string) (any, error) {
|
||||||
return mock.GetGlobalReferenceFunc(n, p)
|
return mock.GetGlobalReferenceFunc(n, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mock *MockCtx) GetImageData(n string) (*imagedataloader.ImageData, error) {
|
func (mock *MockCtx) GetImageData(n string) (map[string]interface{}, error) {
|
||||||
return mock.GetImageDataFunc(n)
|
return mock.GetImageDataFunc(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mock *MockCtx) ParseImageReference(n string) (imagedataloader.ImageReference, error) {
|
|
||||||
return mock.ParseImageReferenceFunc(n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mock *MockCtx) ListResources(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error) {
|
func (mock *MockCtx) ListResources(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error) {
|
||||||
return mock.ListResourcesFunc(apiVersion, resource, namespace)
|
return mock.ListResourcesFunc(apiVersion, resource, namespace)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,23 +2,20 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/cel-go/common/types"
|
"github.com/google/cel-go/common/types"
|
||||||
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ContextType = types.NewOpaqueType("context.Context")
|
ContextType = types.NewOpaqueType("context.Context")
|
||||||
configMapType = BuildConfigMapType()
|
configMapType = BuildConfigMapType()
|
||||||
imageDataType = BuildImageDataType()
|
imageDataType = BuildImageDataType()
|
||||||
imageReferenceType = BuildImageReferenceType()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContextInterface interface {
|
type ContextInterface interface {
|
||||||
GetConfigMap(string, string) (*unstructured.Unstructured, error)
|
GetConfigMap(string, string) (*unstructured.Unstructured, error)
|
||||||
GetGlobalReference(string, string) (any, error)
|
GetGlobalReference(string, string) (any, error)
|
||||||
GetImageData(string) (*imagedataloader.ImageData, error)
|
GetImageData(string) (map[string]interface{}, error)
|
||||||
ParseImageReference(string) (imagedataloader.ImageReference, error)
|
|
||||||
ListResources(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error)
|
ListResources(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error)
|
||||||
GetResource(apiVersion, resource, namespace, name string) (*unstructured.Unstructured, error)
|
GetResource(apiVersion, resource, namespace, name string) (*unstructured.Unstructured, error)
|
||||||
}
|
}
|
||||||
|
@ -81,20 +78,6 @@ func BuildImageDataType() *apiservercel.DeclType {
|
||||||
return apiservercel.NewObjectType("imageData", fields(f...))
|
return apiservercel.NewObjectType("imageData", fields(f...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildImageReferenceType() *apiservercel.DeclType {
|
|
||||||
f := make([]*apiservercel.DeclField, 0)
|
|
||||||
f = append(f,
|
|
||||||
field("image", apiservercel.StringType, true),
|
|
||||||
field("resolvedImage", apiservercel.StringType, true),
|
|
||||||
field("registry", apiservercel.StringType, true),
|
|
||||||
field("repository", apiservercel.StringType, true),
|
|
||||||
field("tag", apiservercel.StringType, false),
|
|
||||||
field("digest", apiservercel.StringType, false),
|
|
||||||
field("identifier", apiservercel.StringType, true),
|
|
||||||
)
|
|
||||||
return apiservercel.NewObjectType("imageReference", fields(f...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func field(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
func field(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||||
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
158
pkg/cel/libs/image/lib.go
Normal file
158
pkg/cel/libs/image/lib.go
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/cel-go/cel"
|
||||||
|
"github.com/google/cel-go/common/types"
|
||||||
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ImageLib() cel.EnvOption {
|
||||||
|
return cel.Lib(imageLib)
|
||||||
|
}
|
||||||
|
|
||||||
|
var imageLib = &imageLibType{}
|
||||||
|
|
||||||
|
type imageLibType struct{}
|
||||||
|
|
||||||
|
func (*imageLibType) LibraryName() string {
|
||||||
|
return "kyverno.Image"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*imageLibType) Types() []*cel.Type {
|
||||||
|
return []*cel.Type{ImageType}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*imageLibType) declarations() map[string][]cel.FunctionOpt {
|
||||||
|
return map[string][]cel.FunctionOpt{
|
||||||
|
"image": {
|
||||||
|
cel.Overload("string_to_image", []*cel.Type{cel.StringType}, ImageType, cel.UnaryBinding((stringToImage))),
|
||||||
|
},
|
||||||
|
"isImage": {
|
||||||
|
cel.Overload("is_image_string", []*cel.Type{cel.StringType}, cel.BoolType, cel.UnaryBinding(isImage)),
|
||||||
|
},
|
||||||
|
"containsDigest": {
|
||||||
|
cel.MemberOverload("image_contains_digest", []*cel.Type{ImageType}, cel.BoolType, cel.UnaryBinding(imageContainsDigest)),
|
||||||
|
},
|
||||||
|
"registry": {
|
||||||
|
cel.MemberOverload("image_registry", []*cel.Type{ImageType}, cel.StringType, cel.UnaryBinding(imageRegistry)),
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
cel.MemberOverload("image_repository", []*cel.Type{ImageType}, cel.StringType, cel.UnaryBinding(imageRepository)),
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
cel.MemberOverload("image_identifier", []*cel.Type{ImageType}, cel.StringType, cel.UnaryBinding(imageIdentifier)),
|
||||||
|
},
|
||||||
|
"tag": {
|
||||||
|
cel.MemberOverload("image_tag", []*cel.Type{ImageType}, cel.StringType, cel.UnaryBinding(imageTag)),
|
||||||
|
},
|
||||||
|
"digest": {
|
||||||
|
cel.MemberOverload("image_digest", []*cel.Type{ImageType}, cel.StringType, cel.UnaryBinding(imageDigest)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *imageLibType) CompileOptions() []cel.EnvOption {
|
||||||
|
imageLibraryDecls := i.declarations()
|
||||||
|
options := make([]cel.EnvOption, 0, len(imageLibraryDecls))
|
||||||
|
for name, overloads := range imageLibraryDecls {
|
||||||
|
options = append(options, cel.Function(name, overloads...))
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*imageLibType) ProgramOptions() []cel.ProgramOption {
|
||||||
|
return []cel.ProgramOption{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isImage(arg ref.Val) ref.Val {
|
||||||
|
str, ok := arg.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := name.ParseReference(str)
|
||||||
|
if err != nil {
|
||||||
|
return types.Bool(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.Bool(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringToImage(arg ref.Val) ref.Val {
|
||||||
|
str, ok := arg.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := name.ParseReference(str)
|
||||||
|
if err != nil {
|
||||||
|
return types.WrapErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Image{ImageReference: ConvertToImageRef(v)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageContainsDigest(arg ref.Val) ref.Val {
|
||||||
|
v, ok := arg.Value().(ImageReference)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(arg)
|
||||||
|
}
|
||||||
|
return types.Bool(len(v.Digest) != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageRegistry(arg ref.Val) ref.Val {
|
||||||
|
v, ok := arg.Value().(ImageReference)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(arg)
|
||||||
|
}
|
||||||
|
return types.String(v.Registry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageRepository(arg ref.Val) ref.Val {
|
||||||
|
v, ok := arg.Value().(ImageReference)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(arg)
|
||||||
|
}
|
||||||
|
return types.String(v.Repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageIdentifier(arg ref.Val) ref.Val {
|
||||||
|
v, ok := arg.Value().(ImageReference)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(arg)
|
||||||
|
}
|
||||||
|
return types.String(v.Identifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageTag(arg ref.Val) ref.Val {
|
||||||
|
v, ok := arg.Value().(ImageReference)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(arg)
|
||||||
|
}
|
||||||
|
return types.String(v.Tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func imageDigest(arg ref.Val) ref.Val {
|
||||||
|
v, ok := arg.Value().(ImageReference)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(arg)
|
||||||
|
}
|
||||||
|
return types.String(v.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ConvertToImageRef(ref name.Reference) ImageReference {
|
||||||
|
var img ImageReference
|
||||||
|
img.Image = ref.String()
|
||||||
|
img.Registry = ref.Context().RegistryStr()
|
||||||
|
img.Repository = ref.Context().RepositoryStr()
|
||||||
|
img.Identifier = ref.Identifier()
|
||||||
|
|
||||||
|
if _, ok := ref.(name.Tag); ok {
|
||||||
|
img.Tag = ref.Identifier()
|
||||||
|
} else {
|
||||||
|
img.Digest = ref.Identifier()
|
||||||
|
}
|
||||||
|
|
||||||
|
return img
|
||||||
|
}
|
226
pkg/cel/libs/image/lib_test.go
Normal file
226
pkg/cel/libs/image/lib_test.go
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
package image_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/cel-go/cel"
|
||||||
|
"github.com/google/cel-go/common/types"
|
||||||
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
|
"github.com/kyverno/kyverno/pkg/cel/libs/image"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testImageLib(t *testing.T, expr string, expectResult ref.Val, expectRuntimeErrPattern string, expectCompileErrs []string) {
|
||||||
|
env, err := cel.NewEnv(
|
||||||
|
image.ImageLib(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
compiled, issues := env.Compile(expr)
|
||||||
|
|
||||||
|
if len(expectCompileErrs) > 0 {
|
||||||
|
missingCompileErrs := []string{}
|
||||||
|
matchedCompileErrs := sets.New[int]()
|
||||||
|
for _, expectedCompileErr := range expectCompileErrs {
|
||||||
|
compiledPattern, err := regexp.Compile(expectedCompileErr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to compile expected err regex: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
didMatch := false
|
||||||
|
|
||||||
|
for i, compileError := range issues.Errors() {
|
||||||
|
if compiledPattern.Match([]byte(compileError.Message)) {
|
||||||
|
didMatch = true
|
||||||
|
matchedCompileErrs.Insert(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !didMatch {
|
||||||
|
missingCompileErrs = append(missingCompileErrs, expectedCompileErr)
|
||||||
|
} else if len(matchedCompileErrs) != len(issues.Errors()) {
|
||||||
|
unmatchedErrs := []cel.Error{}
|
||||||
|
for i, issue := range issues.Errors() {
|
||||||
|
if !matchedCompileErrs.Has(i) {
|
||||||
|
unmatchedErrs = append(unmatchedErrs, *issue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Empty(t, unmatchedErrs, "unexpected compilation errors")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Empty(t, missingCompileErrs, "expected compilation errors")
|
||||||
|
return
|
||||||
|
} else if len(issues.Errors()) > 0 {
|
||||||
|
for _, err := range issues.Errors() {
|
||||||
|
t.Errorf("unexpected compile error: %v", err)
|
||||||
|
}
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
prog, err := env.Program(compiled)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%v", err)
|
||||||
|
}
|
||||||
|
res, _, err := prog.Eval(map[string]interface{}{})
|
||||||
|
if len(expectRuntimeErrPattern) > 0 {
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("no runtime error thrown. Expected: %v", expectRuntimeErrPattern)
|
||||||
|
} else if matched, regexErr := regexp.MatchString(expectRuntimeErrPattern, err.Error()); regexErr != nil {
|
||||||
|
t.Fatalf("failed to compile expected err regex: %v", regexErr)
|
||||||
|
} else if !matched {
|
||||||
|
t.Fatalf("unexpected err: %v", err)
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
t.Fatalf("%v", err)
|
||||||
|
} else if expectResult != nil {
|
||||||
|
converted := res.Equal(expectResult).Value().(bool)
|
||||||
|
require.True(t, converted, "expectation not equal to output")
|
||||||
|
} else {
|
||||||
|
t.Fatal("expected result must not be nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImage(t *testing.T) {
|
||||||
|
trueVal := types.Bool(true)
|
||||||
|
falseVal := types.Bool(false)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
expr string
|
||||||
|
expectValue ref.Val
|
||||||
|
expectedCompileErr []string
|
||||||
|
expectedRuntimeErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "parse",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:latest")`,
|
||||||
|
expectValue: image.Image{ImageReference: image.ConvertToImageRef(name.MustParseReference("registry.k8s.io/kube-apiserver-arm64:latest"))},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "parse_invalid_image",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:@")`,
|
||||||
|
expectedRuntimeErr: "could not parse reference: registry.k8s.io/kube-apiserver-arm64:@",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "isImage",
|
||||||
|
expr: `isImage("registry.k8s.io/kube-apiserver-arm64:latest")`,
|
||||||
|
expectValue: trueVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "isImage_false",
|
||||||
|
expr: `isImage("registry.k8s.io/kube-apiserver-arm64:@")`,
|
||||||
|
expectValue: falseVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "isImage_noOverload",
|
||||||
|
expr: `isImage(0)`,
|
||||||
|
expectedCompileErr: []string{"found no matching overload for 'isImage' applied to.*"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contains_digest_no_identifier",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64").containsDigest()`,
|
||||||
|
expectValue: falseVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contains_digest_tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:latest").containsDigest()`,
|
||||||
|
expectValue: falseVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contains_digest_true",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64@sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2").containsDigest()`,
|
||||||
|
expectValue: trueVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contains_digest_with_tag_true",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:latest@sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2").containsDigest()`,
|
||||||
|
expectValue: trueVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "registry",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64").registry() == "registry.k8s.io"`,
|
||||||
|
expectValue: trueVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "registry_matches",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64").registry().matches("(registry.k8s.io|ghcr.io)")`,
|
||||||
|
expectValue: trueVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "repository",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64").repository() == "kube-apiserver-arm64"`,
|
||||||
|
expectValue: trueVal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "identifier_tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:testtag").identifier()`,
|
||||||
|
expectValue: types.String("testtag"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default_identifier",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64").identifier()`,
|
||||||
|
expectValue: types.String("latest"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "identifer_digest",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64@sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2").identifier()`,
|
||||||
|
expectValue: types.String("sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "identifer_digest_and_tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:latest@sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2").identifier()`,
|
||||||
|
expectValue: types.String("sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:testtag").tag()`,
|
||||||
|
expectValue: types.String("testtag"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default_tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64").tag()`,
|
||||||
|
expectValue: types.String("latest"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no_tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64@sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2").tag()`,
|
||||||
|
expectValue: types.String(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "identifier_tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:testtag").identifier()`,
|
||||||
|
expectValue: types.String("testtag"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no_digest",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64").digest()`,
|
||||||
|
expectValue: types.String(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "digest_tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:testtag").digest()`,
|
||||||
|
expectValue: types.String(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "digest",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64@sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2").digest()`,
|
||||||
|
expectValue: types.String("sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "digest_digest_and_tag",
|
||||||
|
expr: `image("registry.k8s.io/kube-apiserver-arm64:latest@sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2").digest() == "sha256:6aefddb645ee6963afd681b1845c661d0ea4c3b20ab9db86d9e753b203d385f2"`,
|
||||||
|
expectValue: trueVal,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
testImageLib(t, c.expr, c.expectValue, c.expectedRuntimeErr, c.expectedCompileErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
60
pkg/cel/libs/image/types.go
Normal file
60
pkg/cel/libs/image/types.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package image
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/google/cel-go/cel"
|
||||||
|
"github.com/google/cel-go/common/types"
|
||||||
|
"github.com/google/cel-go/common/types/ref"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ImageType = cel.ObjectType("kyverno.image")
|
||||||
|
|
||||||
|
type ImageReference struct {
|
||||||
|
Image string `json:"image,omitempty"`
|
||||||
|
Registry string `json:"registry,omitempty"`
|
||||||
|
Repository string `json:"repository,omitempty"`
|
||||||
|
Identifier string `json:"identifier,omitempty"`
|
||||||
|
Tag string `json:"tag,omitempty"`
|
||||||
|
Digest string `json:"digest,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
ImageReference
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Image) ConvertToNative(typeDesc reflect.Type) (interface{}, error) {
|
||||||
|
if reflect.TypeOf(v.ImageReference).AssignableTo(typeDesc) {
|
||||||
|
return v.ImageReference, nil
|
||||||
|
}
|
||||||
|
if reflect.TypeOf("").AssignableTo(typeDesc) {
|
||||||
|
return v.ImageReference.Image, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("type conversion error from 'Image' to '%v'", typeDesc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Image) ConvertToType(typeVal ref.Type) ref.Val {
|
||||||
|
switch typeVal {
|
||||||
|
case ImageType:
|
||||||
|
return v
|
||||||
|
default:
|
||||||
|
return types.NewErr("type conversion error from '%s' to '%s'", ImageType, typeVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Image) Equal(other ref.Val) ref.Val {
|
||||||
|
img, ok := other.(Image)
|
||||||
|
if !ok {
|
||||||
|
return types.MaybeNoSuchOverloadErr(other)
|
||||||
|
}
|
||||||
|
return types.Bool(reflect.DeepEqual(v.ImageReference, img.ImageReference))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Image) Type() ref.Type {
|
||||||
|
return ImageType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Image) Value() interface{} {
|
||||||
|
return v.ImageReference
|
||||||
|
}
|
|
@ -91,9 +91,13 @@ func (cp *contextProvider) GetGlobalReference(name, projection string) (any, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *contextProvider) GetImageData(image string) (*imagedataloader.ImageData, error) {
|
func (cp *contextProvider) GetImageData(image string) (map[string]interface{}, error) {
|
||||||
// TODO: get image credentials from image verification policies?
|
// TODO: get image credentials from image verification policies?
|
||||||
return cp.imagedata.FetchImageData(context.TODO(), image)
|
data, err := cp.imagedata.FetchImageData(context.TODO(), image)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return getValue(data.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLikelyKubernetesObject(data any) bool {
|
func isLikelyKubernetesObject(data any) bool {
|
||||||
|
@ -132,10 +136,6 @@ func (cp *contextProvider) GetResource(apiVersion, resource, namespace, name str
|
||||||
return resourceInteface.Get(context.TODO(), name, metav1.GetOptions{})
|
return resourceInteface.Get(context.TODO(), name, metav1.GetOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *contextProvider) ParseImageReference(image string) (imagedataloader.ImageReference, error) {
|
|
||||||
return imagedataloader.ParseImageReference(image)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *contextProvider) getResourceClient(groupVersion schema.GroupVersion, resource string, namespace string) dynamic.ResourceInterface {
|
func (cp *contextProvider) getResourceClient(groupVersion schema.GroupVersion, resource string, namespace string) dynamic.ResourceInterface {
|
||||||
client := cp.dclient.Resource(groupVersion.WithResource(resource))
|
client := cp.dclient.Resource(groupVersion.WithResource(resource))
|
||||||
if namespace != "" {
|
if namespace != "" {
|
||||||
|
|
|
@ -3,7 +3,6 @@ package policy
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/kyverno/kyverno/pkg/imageverification/imagedataloader"
|
|
||||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
@ -48,14 +47,10 @@ func (cp *FakeContextProvider) GetGlobalReference(string, string) (any, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *FakeContextProvider) GetImageData(string) (*imagedataloader.ImageData, error) {
|
func (cp *FakeContextProvider) GetImageData(string) (map[string]interface{}, error) {
|
||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cp *FakeContextProvider) ParseImageReference(image string) (imagedataloader.ImageReference, error) {
|
|
||||||
return imagedataloader.ParseImageReference(image)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *FakeContextProvider) ListResources(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error) {
|
func (cp *FakeContextProvider) ListResources(apiVersion, resource, namespace string) (*unstructured.UnstructuredList, error) {
|
||||||
gv, err := schema.ParseGroupVersion(apiVersion)
|
gv, err := schema.ParseGroupVersion(apiVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
16
pkg/cel/policy/utils.go
Normal file
16
pkg/cel/policy/utils.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package policy
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
func getValue(data any) (map[string]interface{}, error) {
|
||||||
|
raw, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
apiData := map[string]interface{}{}
|
||||||
|
err = json.Unmarshal(raw, &apiData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return apiData, nil
|
||||||
|
}
|
|
@ -160,10 +160,7 @@ type ImageData struct {
|
||||||
RemoteOpts []remote.Option
|
RemoteOpts []remote.Option
|
||||||
NameOpts []name.Option
|
NameOpts []name.Option
|
||||||
|
|
||||||
ImageReference `json:",inline"`
|
ImageDescriptor `json:",inline"`
|
||||||
ImageIndex interface{} `json:"imageIndex,omitempty"`
|
|
||||||
Manifest *gcrv1.Manifest `json:"manifest,omitempty"`
|
|
||||||
ConfigData *gcrv1.ConfigFile `json:"config,omitempty"`
|
|
||||||
|
|
||||||
NameRef name.Reference
|
NameRef name.Reference
|
||||||
desc *remote.Descriptor
|
desc *remote.Descriptor
|
||||||
|
@ -173,6 +170,13 @@ type ImageData struct {
|
||||||
verifiedIntotoPayloads map[string][]byte
|
verifiedIntotoPayloads map[string][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ImageDescriptor struct {
|
||||||
|
ImageReference `json:",inline"`
|
||||||
|
ImageIndex interface{} `json:"imageIndex,omitempty"`
|
||||||
|
Manifest *gcrv1.Manifest `json:"manifest,omitempty"`
|
||||||
|
ConfigData *gcrv1.ConfigFile `json:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type referrerData struct {
|
type referrerData struct {
|
||||||
layerDescriptor *gcrv1.Descriptor
|
layerDescriptor *gcrv1.Descriptor
|
||||||
data []byte
|
data []byte
|
||||||
|
@ -201,6 +205,10 @@ func (i *ImageData) WithDigest(digest string) string {
|
||||||
return i.NameRef.Context().Digest(digest).String()
|
return i.NameRef.Context().Digest(digest).String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *ImageData) Data() ImageDescriptor {
|
||||||
|
return i.ImageDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
func (i *ImageData) loadReferrers() error {
|
func (i *ImageData) loadReferrers() error {
|
||||||
if i.referrersManifest != nil {
|
if i.referrersManifest != nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deployment
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:latest
|
|
@ -0,0 +1,24 @@
|
||||||
|
# yaml-language-server: $schema=https://raw.githubusercontent.com/kyverno/chainsaw/main/.schemas/json/test-chainsaw-v1alpha1.json
|
||||||
|
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||||
|
kind: Test
|
||||||
|
metadata:
|
||||||
|
name: image-data
|
||||||
|
spec:
|
||||||
|
steps:
|
||||||
|
- name: create policy
|
||||||
|
try:
|
||||||
|
- create:
|
||||||
|
file: policy.yaml
|
||||||
|
- sleep:
|
||||||
|
duration: 10s
|
||||||
|
- name: create deployment
|
||||||
|
try:
|
||||||
|
- create:
|
||||||
|
file: deployment.yaml
|
||||||
|
- name: create bad deployment
|
||||||
|
try:
|
||||||
|
- apply:
|
||||||
|
expect:
|
||||||
|
- check:
|
||||||
|
($error != null): true
|
||||||
|
file: bad-deployment.yaml
|
|
@ -0,0 +1,19 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: deployment
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: ghcr.io/kyverno/kyverno
|
|
@ -0,0 +1,20 @@
|
||||||
|
apiVersion: policies.kyverno.io/v1alpha1
|
||||||
|
kind: ValidatingPolicy
|
||||||
|
metadata:
|
||||||
|
name: check-images
|
||||||
|
spec:
|
||||||
|
matchConstraints:
|
||||||
|
resourceRules:
|
||||||
|
- apiGroups: [apps]
|
||||||
|
apiVersions: [v1]
|
||||||
|
operations: [CREATE, UPDATE]
|
||||||
|
resources: [deployments]
|
||||||
|
variables:
|
||||||
|
- name: images
|
||||||
|
expression: >-
|
||||||
|
object.spec.template.spec.containers.map(e, image(e.image))
|
||||||
|
validations:
|
||||||
|
- expression: >-
|
||||||
|
variables.images.map(i, i.registry() == "ghcr.io" && !i.containsDigest()).all(e, e)
|
||||||
|
message: >-
|
||||||
|
Deployment must be have images from ghcr and images should be tagged
|
Loading…
Add table
Reference in a new issue