Skip to content

Commit

Permalink
Fixed issue-3709: Image verify rule gives error for non-existing conf…
Browse files Browse the repository at this point in the history
…igmap (#19)

Signed-off-by: Pratik Shah <[email protected]>
  • Loading branch information
shahpratikr authored Nov 8, 2022
1 parent b71c000 commit b89edaf
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 1 deletion.
54 changes: 53 additions & 1 deletion pkg/engine/imageVerify.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"net"
"reflect"
"regexp"
"strings"
"time"

Expand All @@ -18,13 +19,54 @@ import (
"github.com/kyverno/kyverno/pkg/engine/variables"
"github.com/kyverno/kyverno/pkg/logging"
"github.com/kyverno/kyverno/pkg/registryclient"
"github.com/kyverno/kyverno/pkg/utils/api"
apiutils "github.com/kyverno/kyverno/pkg/utils/api"
"github.com/kyverno/kyverno/pkg/utils/wildcard"
"github.com/pkg/errors"
"go.uber.org/multierr"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func requiredValidation(policyContext *PolicyContext, rule *kyvernov1.Rule) (bool, error) {
imageInfo := &api.ImageInfo{}
var runVerification = true
images := policyContext.JSONContext.ImageInfo()
imageData, err := json.Marshal(images["containers"]["test"])
if err != nil {
return false, err
}
err = json.Unmarshal(imageData, &imageInfo)
if err != nil {
return false, err
}
// if registry is empty, it means policy rule has only "*" in image reference
// hence allow running verification
if imageInfo.Registry == "" {
return runVerification, nil
}
imageName := fmt.Sprintf("%s/%s:%s", imageInfo.Registry, imageInfo.Name, imageInfo.Tag)
for _, verifyImage := range rule.VerifyImages {
runVerification = true
if len(verifyImage.ImageReferences) > 0 {
for _, imageRef := range verifyImage.ImageReferences {
regex := regexp.MustCompile(imageRef)
groups := regex.FindAllStringSubmatch(imageName, -1)
if len(groups) == 0 {
runVerification = false
}
}
}
if verifyImage.Image != "" {
regex := regexp.MustCompile(verifyImage.Image)
groups := regex.FindAllStringSubmatch(imageName, -1)
if len(groups) == 0 {
runVerification = false
}
}
}
return runVerification, nil
}

func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineResponse, *ImageVerificationMetadata) {
resp := &response.EngineResponse{}
images := policyContext.JSONContext.ImageInfo()
Expand Down Expand Up @@ -66,14 +108,24 @@ func VerifyAndPatchImages(policyContext *PolicyContext) (*response.EngineRespons

logger.V(3).Info("processing image verification rule", "ruleSelector", applyRules)

var err error
runVerification, err := requiredValidation(policyContext, rule)
if err != nil {
appendError(resp, rule, fmt.Sprintf("failed to load image info: %s", err.Error()), response.RuleStatusError)
continue
}
if !runVerification {
logger.V(3).Info("skipping image verification rule", "ruleSelector", applyRules)
continue
}

policyContext.JSONContext.Restore()
if err := LoadContext(logger, rule.Context, policyContext, rule.Name); err != nil {
appendError(resp, rule, fmt.Sprintf("failed to load context: %s", err.Error()), response.RuleStatusError)
continue
}

ruleImages := images
var err error
if rule.ImageExtractors != nil {
if ruleImages, err = policyContext.JSONContext.GenerateCustomImageInfo(&policyContext.NewResource, rule.ImageExtractors); err != nil {
appendError(resp, rule, fmt.Sprintf("failed to extract images: %s", err.Error()), response.RuleStatusError)
Expand Down
7 changes: 7 additions & 0 deletions pkg/engine/imageVerifyValidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ func processImageValidationRule(log logr.Logger, ctx *PolicyContext, rule *kyver
}

log = log.WithValues("rule", rule.Name)
runValidation, err := requiredValidation(ctx, rule)
if err != nil {
log.Error(err, fmt.Sprintf("failed to load image info: %s", err.Error()))
}
if !runValidation {
return ruleResponse(*rule, response.Validation, "image verified", response.RuleStatusPass, nil)
}
if err := LoadContext(log, rule.Context, ctx, rule.Name); err != nil {
if _, ok := err.(gojmespath.NotFoundError); ok {
log.V(3).Info("failed to load context", "reason", err.Error())
Expand Down
95 changes: 95 additions & 0 deletions pkg/engine/imageVerify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
client "github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/cosign"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/response"
Expand Down Expand Up @@ -305,6 +306,63 @@ var testSampleMultipleKeyPolicy = `
}
`

var testConfigMapMissing = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"annotations": {
"pod-policies.kyverno.io/autogen-controllers": "none"
},
"name": "image-verify-polset"
},
"spec": {
"background": false,
"failurePolicy": "Fail",
"rules": [
{
"context": [
{
"configMap": {
"name": "myconfigmap",
"namespace": "somens"
},
"name": "myconfigmap"
}
],
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"name": "image-verify-pol1",
"verifyImages": [
{
"imageReferences": [
"ghcr.io/*"
],
"attestors": [
{
"count": 1,
"entries": [
{
"keys": {
"publicKeys": "{{myconfigmap.data.configmapkey}}"
}
}
]
}
]
}
]
}
],
"validationFailureAction": "audit",
"webhookTimeoutSeconds": 30
}
}`

var testSampleResource = `{
"apiVersion": "v1",
"kind": "Pod",
Expand All @@ -319,9 +377,46 @@ var testSampleResource = `{
}
}`

var testConfigMapMissingResource = `{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"labels": {
"run": "test"
},
"name": "test"
},
"spec": {
"containers": [
{
"image": "nginx:latest",
"name": "test",
"resources": {}
}
]
}
}`

var testVerifyImageKey = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==\n-----END PUBLIC KEY-----\n`
var testOtherKey = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpNlOGZ323zMlhs4bcKSpAKQvbcWi5ZLRmijm6SqXDy0Fp0z0Eal+BekFnLzs8rUXUaXlhZ3hNudlgFJH+nFNMw==\n-----END PUBLIC KEY-----\n`

func Test_ConfigMapMissingSuccess(t *testing.T) {
policyContext := buildContext(t, testConfigMapMissing, testConfigMapMissingResource, "")
cosign.ClearMock()
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 0)
}

func Test_ConfigMapMissingFailure(t *testing.T) {
ghcrImage := strings.Replace(testConfigMapMissingResource, "nginx:latest", "ghcr.io/kyverno/test-verify-image:signed", -1)
policyContext := buildContext(t, testConfigMapMissing, ghcrImage, "")
policyContext.Client = client.NewEmptyFakeClient()
cosign.ClearMock()
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusError, err.PolicyResponse.Rules[0].Message)
}

func Test_SignatureGoodSigned(t *testing.T) {
policyContext := buildContext(t, testSampleSingleKeyPolicy, testSampleResource, "")
cosign.ClearMock()
Expand Down

0 comments on commit b89edaf

Please sign in to comment.