1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-05 07:26:55 +00:00

refactor: image utils (#3630)

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
This commit is contained in:
Charles-Edouard Brétéché 2022-04-20 17:01:02 +02:00 committed by GitHub
parent 12bbca2477
commit 2e1a87d149
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 151 additions and 81 deletions

View file

@ -974,19 +974,13 @@ func (in *Rule) DeepCopyInto(out *Rule) {
in, out := &in.ImageExtractors, &out.ImageExtractors
*out = make(kube.ImageExtractorConfigs, len(*in))
for key, val := range *in {
var outVal []*kube.ImageExtractorConfig
var outVal []kube.ImageExtractorConfig
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]*kube.ImageExtractorConfig, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(kube.ImageExtractorConfig)
**out = **in
}
}
*out = make([]kube.ImageExtractorConfig, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}

View file

@ -8,7 +8,6 @@ import (
jsonpatch "github.com/evanphx/json-patch/v5"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
pkgcommon "github.com/kyverno/kyverno/pkg/common"
imageutils "github.com/kyverno/kyverno/pkg/utils/image"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/pkg/errors"
admissionv1 "k8s.io/api/admission/v1"
@ -60,17 +59,17 @@ type Interface interface {
AddElement(data map[string]interface{}, index int) error
// AddImageInfo adds image info to the context
AddImageInfo(info imageutils.ImageInfo) error
AddImageInfo(info kubeutils.ImageInfo) error
// AddImageInfos adds image infos to the context
AddImageInfos(resource *unstructured.Unstructured) error
// ImageInfo returns image infos present in the context
ImageInfo() map[string]map[string]imageutils.ImageInfo
ImageInfo() map[string]map[string]kubeutils.ImageInfo
// GenerateCustomImageInfo returns image infos as defined by a custom image extraction config
// and updates the context
GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kubeutils.ImageExtractorConfigs) (map[string]map[string]imageutils.ImageInfo, error)
GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kubeutils.ImageExtractorConfigs) (map[string]map[string]kubeutils.ImageInfo, error)
// Checkpoint creates a copy of the current internal state and pushes it into a stack of stored states.
Checkpoint()
@ -92,7 +91,7 @@ type context struct {
mutex sync.RWMutex
jsonRaw []byte
jsonRawCheckpoints [][]byte
images map[string]map[string]imageutils.ImageInfo
images map[string]map[string]kubeutils.ImageInfo
}
// NewContext returns a new context
@ -215,7 +214,7 @@ func (ctx *context) AddElement(data map[string]interface{}, index int) error {
return addToContext(ctx, data)
}
func (ctx *context) AddImageInfo(info imageutils.ImageInfo) error {
func (ctx *context) AddImageInfo(info kubeutils.ImageInfo) error {
data := map[string]interface{}{
"image": info.String(),
"registry": info.Registry,
@ -239,7 +238,7 @@ func (ctx *context) AddImageInfos(resource *unstructured.Unstructured) error {
return addToContext(ctx, images, "images")
}
func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kubeutils.ImageExtractorConfigs) (map[string]map[string]imageutils.ImageInfo, error) {
func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured, imageExtractorConfigs kubeutils.ImageExtractorConfigs) (map[string]map[string]kubeutils.ImageInfo, error) {
images, err := kubeutils.ExtractImagesFromResource(*resource, imageExtractorConfigs)
if err != nil {
return nil, err
@ -250,7 +249,7 @@ func (ctx *context) GenerateCustomImageInfo(resource *unstructured.Unstructured,
return images, addToContext(ctx, images, "images")
}
func (ctx *context) ImageInfo() map[string]map[string]imageutils.ImageInfo {
func (ctx *context) ImageInfo() map[string]map[string]kubeutils.ImageInfo {
return ctx.images
}

View file

@ -15,7 +15,7 @@ import (
engineUtils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/registryclient"
imageUtils "github.com/kyverno/kyverno/pkg/utils/image"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"github.com/pkg/errors"
"sigs.k8s.io/controller-runtime/pkg/log"
)
@ -133,7 +133,7 @@ type imageVerifier struct {
resp *response.EngineResponse
}
func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[string]map[string]imageUtils.ImageInfo) {
func (iv *imageVerifier) verify(imageVerify *v1.ImageVerification, images map[string]map[string]kubeutils.ImageInfo) {
// for backward compatibility
imageVerify = imageVerify.Convert()
@ -180,7 +180,7 @@ func imageMatches(image string, imagePatterns []string) bool {
return false
}
func (iv *imageVerifier) verifySignatures(imageVerify *v1.ImageVerification, imageInfo imageUtils.ImageInfo) (*response.RuleResponse, string) {
func (iv *imageVerifier) verifySignatures(imageVerify *v1.ImageVerification, imageInfo kubeutils.ImageInfo) (*response.RuleResponse, string) {
image := imageInfo.String()
iv.logger.Info("verifying image", "image", image, "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations))
@ -295,7 +295,7 @@ func (iv *imageVerifier) buildOptionsAndPath(attestor *v1.Attestor, imageVerify
return opts, path
}
func (iv *imageVerifier) patchDigest(path string, imageInfo imageUtils.ImageInfo, digest string, ruleResp *response.RuleResponse) {
func (iv *imageVerifier) patchDigest(path string, imageInfo kubeutils.ImageInfo, digest string, ruleResp *response.RuleResponse) {
if imageInfo.Digest == "" {
patch, err := makeAddDigestPatch(path, imageInfo, digest)
if err != nil {
@ -307,7 +307,7 @@ func (iv *imageVerifier) patchDigest(path string, imageInfo imageUtils.ImageInfo
}
}
func makeAddDigestPatch(path string, imageInfo imageUtils.ImageInfo, digest string) ([]byte, error) {
func makeAddDigestPatch(path string, imageInfo kubeutils.ImageInfo, digest string) ([]byte, error) {
var patch = make(map[string]interface{})
patch["op"] = "replace"
patch["path"] = path
@ -315,7 +315,7 @@ func makeAddDigestPatch(path string, imageInfo imageUtils.ImageInfo, digest stri
return json.Marshal(patch)
}
func (iv *imageVerifier) attestImage(imageVerify *v1.ImageVerification, imageInfo imageUtils.ImageInfo) *response.RuleResponse {
func (iv *imageVerifier) attestImage(imageVerify *v1.ImageVerification, imageInfo kubeutils.ImageInfo) *response.RuleResponse {
image := imageInfo.String()
start := time.Now()
@ -367,7 +367,7 @@ func buildStatementMap(statements []map[string]interface{}) map[string][]map[str
return results
}
func (iv *imageVerifier) checkAttestations(a *v1.Attestation, s map[string]interface{}, img imageUtils.ImageInfo) (bool, error) {
func (iv *imageVerifier) checkAttestations(a *v1.Attestation, s map[string]interface{}, img kubeutils.ImageInfo) (bool, error) {
if len(a.Conditions) == 0 {
return true, nil
}

View file

@ -22,9 +22,6 @@ type ImageInfo struct {
// Digest is the image digest portion e.g. `sha256:128c6e3534b842a2eec139999b8ce8aa9a2af9907e2b9269550809d18cd832a3`
Digest string `json:"digest,omitempty"`
// Pointer is the path to the image object in the resource
Pointer string `json:"-"`
}
func (i *ImageInfo) String() string {
@ -36,7 +33,7 @@ func (i *ImageInfo) String() string {
}
}
func GetImageInfo(image string, pointer string) (*ImageInfo, error) {
func GetImageInfo(image string) (*ImageInfo, error) {
image = addDefaultDomain(image)
ref, err := reference.Parse(image)
if err != nil {
@ -64,7 +61,6 @@ func GetImageInfo(image string, pointer string) (*ImageInfo, error) {
Path: path,
Tag: tag,
Digest: digest,
Pointer: pointer,
}, nil
}

View file

@ -62,7 +62,7 @@ func Test_GetImageInfo(t *testing.T) {
}
func validateImageInfo(t *testing.T, raw, name, path, registry, tag, digest, str string) {
i1, err := GetImageInfo(raw, "")
i1, err := GetImageInfo(raw)
assert.Nil(t, err)
assert.Equal(t, name, i1.Name)
assert.Equal(t, path, i1.Path)

View file

@ -9,7 +9,13 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type ImageExtractorConfigs map[string][]*ImageExtractorConfig
type ImageInfo struct {
imageutils.ImageInfo
// Pointer is the path to the image object in the resource
Pointer string `json:"-"`
}
type ImageExtractorConfigs map[string][]ImageExtractorConfig
type ImageExtractorConfig struct {
// Path is the path to the object containing the image field in a custom resource.
@ -52,15 +58,15 @@ type imageExtractor struct {
Name string
}
func (i *imageExtractor) ExtractFromResource(resource interface{}) (map[string]imageutils.ImageInfo, error) {
imageInfo := map[string]imageutils.ImageInfo{}
func (i *imageExtractor) ExtractFromResource(resource interface{}) (map[string]ImageInfo, error) {
imageInfo := map[string]ImageInfo{}
if err := extract(resource, []string{}, i.Key, i.Value, i.Fields, &imageInfo); err != nil {
return nil, err
}
return imageInfo, nil
}
func extract(obj interface{}, path []string, keyPath, valuePath string, fields []string, imageInfos *map[string]imageutils.ImageInfo) error {
func extract(obj interface{}, path []string, keyPath, valuePath string, fields []string, imageInfos *map[string]ImageInfo) error {
if obj == nil {
return nil
}
@ -100,10 +106,10 @@ func extract(obj interface{}, path []string, keyPath, valuePath string, fields [
if !ok {
return fmt.Errorf("invalid value")
}
if imageInfo, err := imageutils.GetImageInfo(value, pointer); err != nil {
if imageInfo, err := imageutils.GetImageInfo(value); err != nil {
return fmt.Errorf("invalid image %s", value)
} else {
(*imageInfos)[key] = *imageInfo
(*imageInfos)[key] = ImageInfo{*imageInfo, pointer}
}
return nil
}
@ -160,8 +166,8 @@ func lookupImageExtractor(kind string, configs ImageExtractorConfigs) []imageExt
return registeredExtractors[kind]
}
func ExtractImagesFromResource(resource unstructured.Unstructured, configs ImageExtractorConfigs) (map[string]map[string]imageutils.ImageInfo, error) {
infos := map[string]map[string]imageutils.ImageInfo{}
func ExtractImagesFromResource(resource unstructured.Unstructured, configs ImageExtractorConfigs) (map[string]map[string]ImageInfo, error) {
infos := map[string]map[string]ImageInfo{}
for _, extractor := range lookupImageExtractor(resource.GetKind(), configs) {
if infoMap, err := extractor.ExtractFromResource(resource.Object); err != nil {
return nil, err

View file

@ -4,7 +4,6 @@ import (
"testing"
"github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/utils/image"
imageutils "github.com/kyverno/kyverno/pkg/utils/image"
"gotest.tools/assert"
)
@ -13,106 +12,182 @@ func Test_extractImageInfo(t *testing.T) {
tests := []struct {
extractionConfig ImageExtractorConfigs
raw []byte
images map[string]map[string]imageutils.ImageInfo
images map[string]map[string]ImageInfo
}{
{
raw: []byte(`{"apiVersion": "v1","kind": "Pod","metadata": {"name": "myapp"},"spec": {"initContainers": [{"name": "init","image": "index.docker.io/busybox:v1.2.3"}],"containers": [{"name": "nginx","image": "nginx:latest"}], "ephemeralContainers": [{"name": "ephemeral", "image":"test/nginx:latest"}]}}`),
images: map[string]map[string]image.ImageInfo{
images: map[string]map[string]ImageInfo{
"initContainers": {
"init": {Registry: "index.docker.io", Name: "busybox", Path: "busybox", Tag: "v1.2.3", Pointer: "/spec/initContainers/0/image"},
"init": {
imageutils.ImageInfo{
Registry: "index.docker.io",
Name: "busybox",
Path: "busybox",
Tag: "v1.2.3",
},
"/spec/initContainers/0/image",
},
},
"containers": {
"nginx": {Registry: "docker.io", Name: "nginx", Path: "nginx", Tag: "latest", Pointer: "/spec/containers/0/image"},
"nginx": {
imageutils.ImageInfo{
Registry: "docker.io",
Name: "nginx",
Path: "nginx",
Tag: "latest",
},
"/spec/containers/0/image",
},
},
"ephemeralContainers": {
"ephemeral": {Registry: "docker.io", Name: "nginx", Path: "test/nginx", Tag: "latest", Pointer: "/spec/ephemeralContainers/0/image"},
"ephemeral": {
imageutils.ImageInfo{
Registry: "docker.io",
Name: "nginx",
Path: "test/nginx",
Tag: "latest",
},
"/spec/ephemeralContainers/0/image",
},
},
},
},
{
raw: []byte(`{"apiVersion": "v1","kind": "Pod","metadata": {"name": "myapp"},"spec": {"containers": [{"name": "nginx","image": "test/nginx:latest"}]}}`),
images: map[string]map[string]imageutils.ImageInfo{
images: map[string]map[string]ImageInfo{
"containers": {
"nginx": {Registry: "docker.io", Name: "nginx", Path: "test/nginx", Tag: "latest", Pointer: "/spec/containers/0/image"},
"nginx": {
imageutils.ImageInfo{
Registry: "docker.io",
Name: "nginx",
Path: "test/nginx",
Tag: "latest",
},
"/spec/containers/0/image",
},
},
},
},
{
raw: []byte(`{"apiVersion": "apps/v1","kind": "Deployment","metadata": {"name": "myapp"},"spec": {"selector": {"matchLabels": {"app": "myapp"}},"template": {"metadata": {"labels": {"app": "myapp"}},"spec": {"initContainers": [{"name": "init","image": "fictional.registry.example:10443/imagename:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"}],"containers": [{"name": "myapp","image": "fictional.registry.example:10443/imagename"}],"ephemeralContainers": [{"name": "ephemeral","image": "fictional.registry.example:10443/imagename:tag@sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"}] }}}}`),
images: map[string]map[string]imageutils.ImageInfo{
images: map[string]map[string]ImageInfo{
"initContainers": {
"init": {Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "tag", Digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", Pointer: "/spec/template/spec/initContainers/0/image"},
"init": {
imageutils.ImageInfo{
Registry: "fictional.registry.example:10443",
Name: "imagename",
Path: "imagename",
Tag: "tag",
Digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
},
"/spec/template/spec/initContainers/0/image",
},
},
"containers": {
"myapp": {Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "latest", Pointer: "/spec/template/spec/containers/0/image"},
"myapp": {
imageutils.ImageInfo{
Registry: "fictional.registry.example:10443",
Name: "imagename",
Path: "imagename",
Tag: "latest",
},
"/spec/template/spec/containers/0/image",
},
},
"ephemeralContainers": {
"ephemeral": {Registry: "fictional.registry.example:10443", Name: "imagename", Path: "imagename", Tag: "tag", Digest: "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", Pointer: "/spec/template/spec/ephemeralContainers/0/image"},
"ephemeral": {
imageutils.ImageInfo{
Registry: "fictional.registry.example:10443",
Name: "imagename",
Path: "imagename",
Tag: "tag",
Digest: "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
},
"/spec/template/spec/ephemeralContainers/0/image",
},
},
},
},
{
raw: []byte(`{"apiVersion": "batch/v1beta1","kind": "CronJob","metadata": {"name": "hello"},"spec": {"schedule": "*/1 * * * *","jobTemplate": {"spec": {"template": {"spec": {"containers": [{"name": "hello","image": "test.example.com/test/my-app:v2"}]}}}}}}`),
images: map[string]map[string]imageutils.ImageInfo{
images: map[string]map[string]ImageInfo{
"containers": {
"hello": {Registry: "test.example.com", Name: "my-app", Path: "test/my-app", Tag: "v2", Pointer: "/spec/jobTemplate/spec/template/spec/containers/0/image"},
"hello": {
imageutils.ImageInfo{
Registry: "test.example.com",
Name: "my-app",
Path: "test/my-app",
Tag: "v2",
},
"/spec/jobTemplate/spec/template/spec/containers/0/image",
},
},
},
},
{
extractionConfig: ImageExtractorConfigs{
"Task": []*ImageExtractorConfig{
"Task": []ImageExtractorConfig{
{Path: "/spec/steps/*/image"},
},
},
raw: []byte(`{"apiVersion":"tekton.dev/v1beta1","kind":"Task","metadata":{"name":"example-task-name"},"spec":{"params":[{"name":"pathToDockerFile","type":"string","description":"The path to the dockerfile to build","default":"/workspace/workspace/Dockerfile"}],"resources":{"inputs":[{"name":"workspace","type":"git"}],"outputs":[{"name":"builtImage","type":"image"}]},"steps":[{"name":"ubuntu-example","image":"ubuntu","args":["ubuntu-build-example","SECRETS-example.md"]},{"image":"gcr.io/example-builders/build-example","command":["echo"],"args":["$(params.pathToDockerFile)"]},{"name":"dockerfile-pushexample","image":"gcr.io/example-builders/push-example","args":["push","$(resources.outputs.builtImage.url)"],"volumeMounts":[{"name":"docker-socket-example","mountPath":"/var/run/docker.sock"}]}],"volumes":[{"name":"example-volume","emptyDir":{}}]}}`),
images: map[string]map[string]imageutils.ImageInfo{
images: map[string]map[string]ImageInfo{
"custom": {
"/spec/steps/0/image": {
Registry: "docker.io",
Name: "ubuntu",
Path: "ubuntu",
Tag: "latest",
Pointer: "/spec/steps/0/image",
imageutils.ImageInfo{
Registry: "docker.io",
Name: "ubuntu",
Path: "ubuntu",
Tag: "latest",
},
"/spec/steps/0/image",
},
"/spec/steps/1/image": {
Registry: "gcr.io",
Name: "build-example",
Path: "example-builders/build-example",
Tag: "latest",
Pointer: "/spec/steps/1/image",
imageutils.ImageInfo{
Registry: "gcr.io",
Name: "build-example",
Path: "example-builders/build-example",
Tag: "latest",
},
"/spec/steps/1/image",
},
"/spec/steps/2/image": {
Registry: "gcr.io",
Name: "push-example",
Path: "example-builders/push-example",
Tag: "latest",
Pointer: "/spec/steps/2/image",
imageutils.ImageInfo{
Registry: "gcr.io",
Name: "push-example",
Path: "example-builders/push-example",
Tag: "latest",
},
"/spec/steps/2/image",
},
},
},
},
{
extractionConfig: ImageExtractorConfigs{
"Task": []*ImageExtractorConfig{
"Task": []ImageExtractorConfig{
{Name: "steps", Path: "/spec/steps/*", Value: "image", Key: "name"},
},
}, raw: []byte(`{"apiVersion":"tekton.dev/v1beta1","kind":"Task","metadata":{"name":"example-task-name"},"spec":{"steps":[{"name":"ubuntu-example","image":"ubuntu","args":["ubuntu-build-example","SECRETS-example.md"]},{"name":"dockerfile-pushexample","image":"gcr.io/example-builders/push-example","args":["push","$(resources.outputs.builtImage.url)"]}]}}`),
images: map[string]map[string]imageutils.ImageInfo{
images: map[string]map[string]ImageInfo{
"steps": {
"dockerfile-pushexample": {
Registry: "gcr.io",
Name: "push-example",
Path: "example-builders/push-example",
Tag: "latest",
Pointer: "/spec/steps/1/image",
imageutils.ImageInfo{
Registry: "gcr.io",
Name: "push-example",
Path: "example-builders/push-example",
Tag: "latest",
},
"/spec/steps/1/image",
},
"ubuntu-example": {
Registry: "docker.io",
Name: "ubuntu",
Path: "ubuntu",
Tag: "latest",
Pointer: "/spec/steps/0/image",
imageutils.ImageInfo{
Registry: "docker.io",
Name: "ubuntu",
Path: "ubuntu",
Tag: "latest",
},
"/spec/steps/0/image",
},
},
},