1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00
kyverno/pkg/engine/image_verify_test.go
D N Siva Sathyaseelan 0ee73430de
feat:Add support for condition validation across multiple image verification attestations or context entry (#9960)
* added Validate in ImageVerification

Signed-off-by: sivasathyaseeelan <dnsiva.sathyseelan.chy21@iitbhu.ac.in>

* added Validate in ImageVerification

Signed-off-by: sivasathyaseeelan <dnsiva.sathyseelan.chy21@iitbhu.ac.in>

* validate block added

Signed-off-by: sivasathyaseeelan <dnsiva.sathyseelan.chy21@iitbhu.ac.in>

* Name feild is added in Attestation struct

Signed-off-by: sivasathyaseeelan <dnsiva.sathyseelan.chy21@iitbhu.ac.in>

* added imageInfo in policy context

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added response from FetchAttestations to contex entry

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added validate logic

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* condition for name collision in Attestation array is added

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added multiple image verification test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added multiple image verification test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* unit test added

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* ValidateVariable is applied in validate

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* change in regex, logic, conformance test, unit test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some change in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* context entries are added as variables

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical implementations

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Validate and multiple Image verification is working

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* moved validate to verify

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in imageverifier

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in imageverifier

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* unit test added

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* lint fixes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* lint fixes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* lint fixes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* lint fixes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added validate in verifyimage in v2 policies

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* validate is moved to verifyAttestation

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in unit tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* removed loadcontext

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* enhanced tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* enhanced tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Added getRawResp to fing report in statemants

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* aome changes in unit tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* aome changes in unit tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in ivm

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* changes in verify in imageverifer

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test for trivy and vex

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added Validate in ImageVerification

Signed-off-by: sivasathyaseeelan <dnsiva.sathyseelan.chy21@iitbhu.ac.in>

* added Validate in ImageVerification

Signed-off-by: sivasathyaseeelan <dnsiva.sathyseelan.chy21@iitbhu.ac.in>

* added imageInfo in policy context

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added response from FetchAttestations to contex entry

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added validate logic

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* condition for name collision in Attestation array is added

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added multiple image verification test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added multiple image verification test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* unit test added

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* ValidateVariable is applied in validate

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* change in regex, logic, conformance test, unit test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some change in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* context entries are added as variables

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some logical implementations

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Validate and multiple Image verification is working

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* moved validate to verify

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in conformance test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in imageverifier

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Some changes in imageverifier

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* unit test added

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* lint fixes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* lint fixes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* lint fixes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* lint fixes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* added validate in verifyimage in v2 policies

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* validate is moved to verifyAttestation

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in unit tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* removed loadcontext

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* enhanced tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* enhanced tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* Added getRawResp to fing report in statemants

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* aome changes in unit tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* aome changes in unit tests

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* helm test fix

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* some changes in ivm

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* changes in verify in imageverifer

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* test

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* codegen applied

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

* codegen resolved

Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>

---------

Signed-off-by: sivasathyaseeelan <dnsiva.sathyseelan.chy21@iitbhu.ac.in>
Signed-off-by: sivasathyaseeelan <dnsiva.sathyaseelan.chy21@iitbhu.ac.in>
Signed-off-by: D N Siva Sathyaseelan <95441117+sivasathyaseeelan@users.noreply.github.com>
Co-authored-by: sivasathyaseeelan <dnsiva.sathyseelan.chy21@iitbhu.ac.in>
2024-09-05 10:33:37 +00:00

1736 lines
70 KiB
Go

package engine
import (
"context"
"encoding/json"
"fmt"
"strings"
"testing"
"time"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/api/kyverno"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/cosign"
"github.com/kyverno/kyverno/pkg/engine/adapters"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
enginecontext "github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/context/resolvers"
"github.com/kyverno/kyverno/pkg/engine/factories"
"github.com/kyverno/kyverno/pkg/engine/internal"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/engine/mutate/patch"
"github.com/kyverno/kyverno/pkg/engine/policycontext"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/imageverifycache"
"github.com/kyverno/kyverno/pkg/registryclient"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
"gomodules.xyz/jsonpatch/v2"
"gotest.tools/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
kubefake "k8s.io/client-go/kubernetes/fake"
)
var testPolicyGood = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "attest"
},
"spec": {
"rules": [
{
"name": "attest",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"verifyImages": [
{
"image": "*",
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155JI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==\n-----END PUBLIC KEY-----",
"attestations": [
{
"predicateType": "https://example.com/CodeReview/v1",
"attestors": [
{
"entries": [
{
"keys": {
"publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155JI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==\n-----END PUBLIC KEY-----",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
}
]
}
],
"conditions": [
{
"all": [
{
"key": "{{ repo.uri }}",
"operator": "Equals",
"value": "https://github.com/example/my-project"
},
{
"key": "{{ repo.branch }}",
"operator": "Equals",
"value": "main"
}
]
}
]
}
]
}
]
}
]
}
}`
var testPolicyBad = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "attest"
},
"spec": {
"rules": [
{
"name": "attest",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"verifyImages": [
{
"image": "*",
"key": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155JI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==\n-----END PUBLIC KEY-----",
"attestations": [
{
"predicateType": "https://example.com/CodeReview/v1",
"conditions": [
{
"all": [
{
"key": "{{ repo.uri }}",
"operator": "Equals",
"value": "https://github.com/example/my-project"
},
{
"key": "{{ repo.branch }}",
"operator": "Equals",
"value": "prod"
}
]
}
]
}
]
}
]
}
]
}
}
`
var testResource = `{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test",
"annotations": {}
},
"spec": {
"containers": [
{
"name": "pause2",
"image": "ghcr.io/jimbugwadia/pause2"
}
]
}
}`
var cosignTestResource = `{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test",
"annotations": {}
},
"spec": {
"containers": [
{
"name": "pause2",
"image": "ghcr.io/kyverno/test-verify-image:signed"
}
]
}
}`
var cosignTestPolicy = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image",
"annotations": {
"pod-policies.kyverno.io/autogen-controllers": "none"
}
},
"spec": {
"validationFailureAction": "enforce",
"background": false,
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "check-signature",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"verifyImages": [
{
"imageReferences": [
"ghcr.io/kyverno/test-verify-image:*"
],
"useCache": true,
"attestors": [
{
"entries": [
{
"keys": {
"publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM\n5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==\n-----END PUBLIC KEY-----",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
}
]
}
]
}
]
}
]
}
}`
var cosignTestPolicyUpdated = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image",
"annotations": {
"pod-policies.kyverno.io/autogen-controllers": "none"
}
},
"spec": {
"validationFailureAction": "enforce",
"background": false,
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "check-signature-updated",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"verifyImages": [
{
"imageReferences": [
"ghcr.io/kyverno/test-verify-image:*"
],
"useCache": true,
"attestors": [
{
"entries": [
{
"keys": {
"publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM\n5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==\n-----END PUBLIC KEY-----",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
}
]
}
]
}
]
}
]
}
}`
var attestationPayloads = [][]byte{
[]byte(`{"payloadType":"https://example.com/CodeReview/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL0NvZGVSZXZpZXcvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9qaW1idWd3YWRpYS9wYXVzZTIiLCJkaWdlc3QiOnsic2hhMjU2IjoiYjMxYmZiNGQwMjEzZjI1NGQzNjFlMDA3OWRlYWFlYmVmYTRmODJiYTdhYTc2ZWY4MmU5MGI0OTM1YWQ1YjEwNSJ9fV0sInByZWRpY2F0ZSI6eyJhdXRob3IiOiJtYWlsdG86YWxpY2VAZXhhbXBsZS5jb20iLCJyZXBvIjp7ImJyYW5jaCI6Im1haW4iLCJ0eXBlIjoiZ2l0IiwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2V4YW1wbGUvbXktcHJvamVjdCJ9LCJyZXZpZXdlcnMiOlsibWFpbHRvOmJvYkBleGFtcGxlLmNvbSJdfX0=","signatures":[{"keyid":"","sig":"MEYCIQCrEr+vgPDmNCrqGDE/4z9iMLmCXMXcDlGKtSoiuMTSFgIhAN2riBaGk4accWzVl7ypi1XTRxyrPYHst8DesugPXgOf"}]}`),
[]byte(`{"payloadType":"cosign.sigstore.dev/attestation/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjb3NpZ24uc2lnc3RvcmUuZGV2L2F0dGVzdGF0aW9uL3YxIiwic3ViamVjdCI6W3sibmFtZSI6ImdoY3IuaW8vamltYnVnd2FkaWEvcGF1c2UyIiwiZGlnZXN0Ijp7InNoYTI1NiI6ImIzMWJmYjRkMDIxM2YyNTRkMzYxZTAwNzlkZWFhZWJlZmE0ZjgyYmE3YWE3NmVmODJlOTBiNDkzNWFkNWIxMDUifX1dLCJwcmVkaWNhdGUiOnsiRGF0YSI6ImhlbGxvIVxuIiwiVGltZXN0YW1wIjoiMjAyMS0xMC0wNVQwNToxODoxMVoifX0=","signatures":[{"keyid":"","sig":"MEQCIF5r9lf55rnYNPByZ9v6bortww694UEPvmyBIelIDYbIAiBNTGX4V64Oj6jZVRpkJQRxdzKUPYqC5GZTb4oS6eQ6aQ=="}]}`),
[]byte(`{"payloadType":"https://example.com/CodeReview/v1","payload":"eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJodHRwczovL2V4YW1wbGUuY29tL0NvZGVSZXZpZXcvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiZ2hjci5pby9qaW1idWd3YWRpYS9wYXVzZTIiLCJkaWdlc3QiOnsic2hhMjU2IjoiYjMxYmZiNGQwMjEzZjI1NGQzNjFlMDA3OWRlYWFlYmVmYTRmODJiYTdhYTc2ZWY4MmU5MGI0OTM1YWQ1YjEwNSJ9fV0sInByZWRpY2F0ZSI6eyJhdXRob3IiOiJtYWlsdG86YWxpY2VAZXhhbXBsZS5jb20iLCJyZXBvIjp7ImJyYW5jaCI6Im1haW4iLCJ0eXBlIjoiZ2l0IiwidXJpIjoiaHR0cHM6Ly9naXRodWIuY29tL2V4YW1wbGUvbXktcHJvamVjdCJ9LCJyZXZpZXdlcnMiOlsibWFpbHRvOmJvYkBleGFtcGxlLmNvbSJdfX0=","signatures":[{"keyid":"","sig":"MEUCIEeZbdBEFQzWqiMhB+SJgM6yFppUuQSKrpOIX1mxLDmRAiEA8pXqFq0GVc9LKhPzrnJRZhSruDNiKbiLHG5x7ETFyY8="}]}`),
}
var signaturePayloads = [][]byte{
[]byte(`{"critical":{"identity":{"docker-reference":"ghcr.io/kyverno/test-verify-image"},"image":{"docker-manifest-digest":"sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105"},"type":"cosign container image signature"},"optional":null}`),
}
var (
cfg = config.NewDefaultConfiguration(false)
metricsCfg = config.NewDefaultMetricsConfiguration()
jp = jmespath.New(cfg)
)
func testVerifyAndPatchImages(
ctx context.Context,
rclient registryclient.Client,
cmResolver engineapi.ConfigmapResolver,
pContext engineapi.PolicyContext,
cfg config.Configuration,
) (engineapi.EngineResponse, engineapi.ImageVerificationMetadata) {
e := NewEngine(
cfg,
metricsCfg,
jp,
nil,
factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil),
imageverifycache.DisabledImageVerifyCache(),
factories.DefaultContextLoaderFactory(cmResolver),
nil,
)
return e.VerifyAndPatchImages(
ctx,
pContext,
)
}
func Test_CosignMockAttest(t *testing.T) {
policyContext := buildContext(t, testPolicyGood, testResource, "")
err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", attestationPayloads)
defer cosign.ClearMock()
assert.NilError(t, err)
er, ivm := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass,
fmt.Sprintf("expected: %v, got: %v, failure: %v",
engineapi.RuleStatusPass, er.PolicyResponse.Rules[0].Status(), er.PolicyResponse.Rules[0].Message()))
assert.Equal(t, ivm.IsEmpty(), false)
assert.Equal(t, ivm.IsVerified("ghcr.io/jimbugwadia/pause2:latest"), true)
}
func Test_CosignMockAttest_fail(t *testing.T) {
policyContext := buildContext(t, testPolicyBad, testResource, "")
err := cosign.SetMock("ghcr.io/jimbugwadia/pause2:latest", attestationPayloads)
defer cosign.ClearMock()
assert.NilError(t, err)
er, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail)
}
func buildContext(t *testing.T, policy, resource string, oldResource string) *PolicyContext {
var cpol kyvernov1.ClusterPolicy
err := json.Unmarshal([]byte(policy), &cpol)
assert.NilError(t, err)
resourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(resource))
assert.NilError(t, err)
policyContext, err := policycontext.NewPolicyContext(
jp,
*resourceUnstructured,
kyvernov1.Create,
nil,
cfg,
)
assert.NilError(t, err)
policyContext = policyContext.
WithPolicy(&cpol).
WithNewResource(*resourceUnstructured)
if oldResource != "" {
oldResourceUnstructured, err := kubeutils.BytesToUnstructured([]byte(oldResource))
assert.NilError(t, err)
err = enginecontext.AddOldResource(policyContext.JSONContext(), []byte(oldResource))
assert.NilError(t, err)
policyContext = policyContext.WithOldResource(*oldResourceUnstructured)
}
return policyContext
}
var testSampleSingleKeyPolicy = `
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image",
"annotations": {
"pod-policies.kyverno.io/autogen-controllers": "none"
}
},
"spec": {
"validationFailureAction": "enforce",
"background": false,
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "check-signature",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"verifyImages": [
{
"imageReferences": [
"ghcr.io/kyverno/test-verify-image:*"
],
"attestors": [
{
"entries": [
{
"keys": {
"publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8nXRh950IZbRj8Ra/N9sbqOPZrfM\n5/KAQN0/KjHcorm/J5yctVd7iEcnessRQjU917hmKO6JWVGHpDguIyakZA==\n-----END PUBLIC KEY-----",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
}
]
}
]
}
]
}
]
}
}
`
var testSampleMultipleKeyPolicy = `
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image",
"annotations": {
"pod-policies.kyverno.io/autogen-controllers": "none"
}
},
"spec": {
"validationFailureAction": "enforce",
"background": false,
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "check-signature",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"verifyImages": [
{
"imageReferences": [
"ghcr.io/kyverno/test-verify-image:*"
],
"attestors": [
{
"count": COUNT,
"entries": [
{
"keys": {
"publicKeys": "KEY1",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
},
{
"keys": {
"publicKeys": "KEY2",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
}
]
}
]
}
]
}
]
}
}
`
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": "mynamespace"
},
"name": "myconfigmap"
}
],
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"name": "image-verify-pol1",
"verifyImages": [
{
"imageReferences": [
"ghcr.io/*"
],
"mutateDigest": false,
"verifyDigest": false,
"attestors": [
{
"entries": [
{
"keys": {
"publicKeys": "{{myconfigmap.data.configmapkey}}",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
}
]
}
]
}
]
}
],
"validationFailureAction": "Audit",
"webhookTimeoutSeconds": 30
}
}`
var testSampleResource = `{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {"name": "test"},
"spec": {
"containers": [
{
"name": "pause2",
"image": "ghcr.io/kyverno/test-verify-image:signed"
}
]
}
}`
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`
testOtherKey = `-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpNlOGZ323zMlhs4bcKSpAKQvbcWi5ZLRmijm6SqXDy0Fp0z0Eal+BekFnLzs8rUXUaXlhZ3hNudlgFJH+nFNMw==\n-----END PUBLIC KEY-----\n`
)
func Test_NoMatch(t *testing.T) {
policyContext := buildContext(t, testConfigMapMissing, testConfigMapMissingResource, "")
err, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
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, "")
resolver, err := resolvers.NewClientBasedResolver(kubefake.NewSimpleClientset())
assert.NilError(t, err)
resp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), resolver, policyContext, cfg)
assert.Equal(t, len(resp.PolicyResponse.Rules), 1)
assert.Equal(t, resp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusError, resp.PolicyResponse.Rules[0].Message())
}
func Test_SignatureGoodSigned(t *testing.T) {
policyContext := buildContext(t, testSampleSingleKeyPolicy, testSampleResource, "")
policyContext.Policy().GetSpec().Rules[0].VerifyImages[0].MutateDigest = true
engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message())
constainers, found, err := unstructured.NestedSlice(engineResp.PatchedResource.UnstructuredContent(), "spec", "containers")
assert.NilError(t, err)
assert.Equal(t, true, found)
image, found, err := unstructured.NestedString(constainers[0].(map[string]interface{}), "image")
assert.NilError(t, err)
assert.Equal(t, true, found)
assert.Equal(t, "ghcr.io/kyverno/test-verify-image:signed@sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105", image)
}
func Test_SignatureUnsigned(t *testing.T) {
unsigned := strings.Replace(testSampleResource, ":signed", ":unsigned", -1)
policyContext := buildContext(t, testSampleSingleKeyPolicy, unsigned, "")
engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message())
}
func Test_SignatureWrongKey(t *testing.T) {
otherKey := strings.Replace(testSampleResource, ":signed", ":signed-by-someone-else", -1)
policyContext := buildContext(t, testSampleSingleKeyPolicy, otherKey, "")
engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message())
}
func Test_SignaturesMultiKey(t *testing.T) {
policy := strings.Replace(testSampleMultipleKeyPolicy, "KEY1", testVerifyImageKey, -1)
policy = strings.Replace(policy, "KEY2", testVerifyImageKey, -1)
policy = strings.Replace(policy, "COUNT", "0", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message())
}
func Test_SignaturesMultiKeyFail(t *testing.T) {
policy := strings.Replace(testSampleMultipleKeyPolicy, "KEY1", testVerifyImageKey, -1)
policy = strings.Replace(policy, "COUNT", "0", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message())
}
func Test_SignaturesMultiKeyOneGoodKey(t *testing.T) {
policy := strings.Replace(testSampleMultipleKeyPolicy, "KEY1", testVerifyImageKey, -1)
policy = strings.Replace(policy, "KEY2", testOtherKey, -1)
policy = strings.Replace(policy, "COUNT", "1", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
engineResp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message())
}
func Test_SignaturesMultiKeyZeroGoodKey(t *testing.T) {
policy := strings.Replace(testSampleMultipleKeyPolicy, "KEY1", testOtherKey, -1)
policy = strings.Replace(policy, "KEY2", testOtherKey, -1)
policy = strings.Replace(policy, "COUNT", "1", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
resp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(resp.PolicyResponse.Rules), 1)
assert.Equal(t, resp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail, resp.PolicyResponse.Rules[0].Message())
}
func Test_RuleSelectorImageVerify(t *testing.T) {
policyContext := buildContext(t, testSampleSingleKeyPolicy, testSampleResource, "")
rule := newStaticKeyRule("match-all", testOtherKey)
spec := policyContext.Policy().GetSpec()
spec.Rules = append(spec.Rules, *rule)
applyAll := kyvernov1.ApplyAll
spec.ApplyRules = &applyAll
resp, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(resp.PolicyResponse.Rules), 2)
assert.Equal(t, resp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, resp.PolicyResponse.Rules[0].Message())
assert.Equal(t, resp.PolicyResponse.Rules[1].Status(), engineapi.RuleStatusFail, resp.PolicyResponse.Rules[1].Message())
applyOne := kyvernov1.ApplyOne
spec.ApplyRules = &applyOne
resp, _ = testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(resp.PolicyResponse.Rules), 1)
assert.Equal(t, resp.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, resp.PolicyResponse.Rules[0].Message())
}
func newStaticKeyRule(name, key string) *kyvernov1.Rule {
return &kyvernov1.Rule{
Name: name,
MatchResources: kyvernov1.MatchResources{
All: kyvernov1.ResourceFilters{
{
ResourceDescription: kyvernov1.ResourceDescription{
Kinds: []string{"Pod"},
},
},
},
},
VerifyImages: []kyvernov1.ImageVerification{
{
ImageReferences: []string{"*"},
Attestors: []kyvernov1.AttestorSet{
{
Entries: []kyvernov1.Attestor{
{
Keys: &kyvernov1.StaticKeyAttestor{
PublicKeys: key,
},
},
},
},
},
},
},
}
}
var testNestedAttestorPolicy = `
{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image-keyless",
"annotations": {
"pod-policies.kyverno.io/autogen-controllers": "none"
}
},
"spec": {
"validationFailureAction": "enforce",
"background": false,
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "check-image-keyless",
"match": {
"resources": {
"kinds": [
"Pod"
]
}
},
"verifyImages": [
{
"imageReferences": [
"ghcr.io/kyverno/test-verify-image:*"
],
"attestors": [
{
"count": COUNT,
"entries": [
{
"keys": {
"publicKeys": "KEY1",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
},
{
"attestor": {
"entries": [
{
"keys": {
"publicKeys": "KEY2",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
}
]
}
}
]
}
]
}
]
}
]
}
}
`
func Test_NestedAttestors(t *testing.T) {
policy := strings.Replace(testNestedAttestorPolicy, "KEY1", testVerifyImageKey, -1)
policy = strings.Replace(policy, "KEY2", testVerifyImageKey, -1)
policy = strings.Replace(policy, "COUNT", "0", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
err, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
policy = strings.Replace(testNestedAttestorPolicy, "KEY1", testVerifyImageKey, -1)
policy = strings.Replace(policy, "KEY2", testOtherKey, -1)
policy = strings.Replace(policy, "COUNT", "0", -1)
policyContext = buildContext(t, policy, testSampleResource, "")
err, _ = testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusFail)
policy = strings.Replace(testNestedAttestorPolicy, "KEY1", testVerifyImageKey, -1)
policy = strings.Replace(policy, "KEY2", testOtherKey, -1)
policy = strings.Replace(policy, "COUNT", "1", -1)
policyContext = buildContext(t, policy, testSampleResource, "")
err, _ = testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
}
func Test_ExpandKeys(t *testing.T) {
as := internal.ExpandStaticKeys(createStaticKeyAttestorSet("", true, false, false))
assert.Equal(t, 1, len(as.Entries))
as = internal.ExpandStaticKeys(createStaticKeyAttestorSet(testOtherKey, true, false, false))
assert.Equal(t, 1, len(as.Entries))
as = internal.ExpandStaticKeys(createStaticKeyAttestorSet(testOtherKey+testOtherKey+testOtherKey, true, false, false))
assert.Equal(t, 3, len(as.Entries))
as = internal.ExpandStaticKeys(createStaticKeyAttestorSet("", false, true, false))
assert.Equal(t, 1, len(as.Entries))
assert.DeepEqual(t, &kyvernov1.SecretReference{Name: "testsecret", Namespace: "default"},
as.Entries[0].Keys.Secret)
as = internal.ExpandStaticKeys(createStaticKeyAttestorSet("", false, false, true))
assert.Equal(t, 1, len(as.Entries))
assert.DeepEqual(t, "gcpkms://projects/test_project_id/locations/asia-south1/keyRings/test_key_ring_name/cryptoKeys/test_key_name/versions/1", as.Entries[0].Keys.KMS)
as = internal.ExpandStaticKeys((createStaticKeyAttestorSet(testOtherKey, true, true, false)))
assert.Equal(t, 2, len(as.Entries))
assert.DeepEqual(t, testOtherKey, as.Entries[0].Keys.PublicKeys)
assert.DeepEqual(t, &kyvernov1.SecretReference{Name: "testsecret", Namespace: "default"}, as.Entries[1].Keys.Secret)
}
func createStaticKeyAttestorSet(s string, withPublicKey, withSecret, withKMS bool) kyvernov1.AttestorSet {
var entries []kyvernov1.Attestor
if withPublicKey {
attestor := kyvernov1.Attestor{
Keys: &kyvernov1.StaticKeyAttestor{
PublicKeys: s,
},
}
entries = append(entries, attestor)
}
if withSecret {
attestor := kyvernov1.Attestor{
Keys: &kyvernov1.StaticKeyAttestor{
Secret: &kyvernov1.SecretReference{
Name: "testsecret",
Namespace: "default",
},
},
}
entries = append(entries, attestor)
}
if withKMS {
kmsKey := "gcpkms://projects/test_project_id/locations/asia-south1/keyRings/test_key_ring_name/cryptoKeys/test_key_name/versions/1"
attestor := kyvernov1.Attestor{
Keys: &kyvernov1.StaticKeyAttestor{
KMS: kmsKey,
},
}
entries = append(entries, attestor)
}
return kyvernov1.AttestorSet{Entries: entries}
}
func Test_ChangedAnnotation(t *testing.T) {
annotationKey := kyverno.AnnotationImageVerify
annotationNew := fmt.Sprintf("\"annotations\": {\"%s\": \"%s\"}", annotationKey, "true")
newResource := strings.ReplaceAll(testResource, "\"annotations\": {}", annotationNew)
policyContext := buildContext(t, testPolicyGood, testResource, testResource)
hasChanged := internal.HasImageVerifiedAnnotationChanged(policyContext, logr.Discard())
assert.Equal(t, hasChanged, false)
policyContext = buildContext(t, testPolicyGood, newResource, testResource)
hasChanged = internal.HasImageVerifiedAnnotationChanged(policyContext, logr.Discard())
assert.Equal(t, hasChanged, true)
annotationOld := fmt.Sprintf("\"annotations\": {\"%s\": \"%s\"}", annotationKey, "false")
oldResource := strings.ReplaceAll(testResource, "\"annotations\": {}", annotationOld)
policyContext = buildContext(t, testPolicyGood, newResource, oldResource)
hasChanged = internal.HasImageVerifiedAnnotationChanged(policyContext, logr.Discard())
assert.Equal(t, hasChanged, true)
}
func Test_MarkImageVerified(t *testing.T) {
image := "ghcr.io/jimbugwadia/pause2:latest"
policyContext := buildContext(t, testPolicyGood, testResource, "")
err := cosign.SetMock(image, attestationPayloads)
defer cosign.ClearMock()
assert.NilError(t, err)
engineResponse, verifiedImages := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(engineResponse.PolicyResponse.Rules), 1)
assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
assert.Assert(t, verifiedImages.Data != nil)
assert.Equal(t, len(verifiedImages.Data), 1)
assert.Equal(t, verifiedImages.IsVerified(image), true)
patches, err := verifiedImages.Patches(false, logr.Discard())
assert.NilError(t, err)
assert.Equal(t, len(patches), 2)
resource := testApplyPatches(t, patches)
patchedAnnotations := resource.GetAnnotations()
assert.Equal(t, len(patchedAnnotations), 1)
json := patchedAnnotations[kyverno.AnnotationImageVerify]
assert.Assert(t, json != "")
verified, err := engineutils.IsImageVerified(resource, image, logr.Discard())
assert.NilError(t, err)
assert.Equal(t, verified, engineapi.ImageVerificationPass)
}
func testApplyPatches(t *testing.T, patches []jsonpatch.JsonPatchOperation) unstructured.Unstructured {
patchedResource, err := engineutils.ApplyPatches([]byte(testResource), patch.ConvertPatches(patches...))
assert.NilError(t, err)
assert.Assert(t, patchedResource != nil)
u := unstructured.Unstructured{}
err = u.UnmarshalJSON(patchedResource)
assert.NilError(t, err)
return u
}
func Test_ParsePEMDelimited(t *testing.T) {
testPEMPolicy := `{
"apiVersion": "kyverno.io/v1",
"kind": "Policy",
"metadata": {
"name": "check-image"
},
"spec": {
"validationFailureAction": "enforce",
"background": false,
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "check-image",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"verifyImages": [
{
"imageReferences": [
"*"
],
"attestors": [
{
"count": 1,
"entries": [
{
"keys": {
"publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfVMHGmFK4OgVqhy36KZ7a3r4R4/o\nCwaCVvXZV4ZULFbkFZ0IodGqKqcVmgycnoj7d8TpKpAUVNF8kKh90ewH3A==\n-----END PUBLIC KEY-----\n-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE0f1W0XigyPFbX8Xq3QmkbL9gDFTf\nRfc8jF7UadBcwKxiyvPSOKZn+igQfXzpNjrwPSZ58JGvF4Fs8BB3fSRP2g==\n-----END PUBLIC KEY-----",
"rekor": {
"url": "https://rekor.sigstore.dev",
"ignoreTlog": true
},
"ctlog": {
"ignoreSCT": true
}
}
}
]
}
]
}
]
}
]
}
}`
image := "ghcr.io/jimbugwadia/pause2:latest"
policyContext := buildContext(t, testPEMPolicy, testResource, "")
err := cosign.SetMock(image, signaturePayloads)
defer cosign.ClearMock()
assert.NilError(t, err)
engineResponse, verifiedImages := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
assert.Equal(t, len(engineResponse.PolicyResponse.Rules), 1)
assert.Equal(t, engineResponse.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass)
assert.Assert(t, verifiedImages.Data != nil)
assert.Equal(t, len(verifiedImages.Data), 1)
assert.Equal(t, verifiedImages.IsVerified(image), true)
}
func testImageVerifyCache(
ivCache imageverifycache.Client,
ctx context.Context,
rclient registryclient.Client,
cmResolver engineapi.ConfigmapResolver,
pContext engineapi.PolicyContext,
cfg config.Configuration,
) (engineapi.EngineResponse, engineapi.ImageVerificationMetadata) {
e := NewEngine(
cfg,
metricsCfg,
jp,
nil,
factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil),
ivCache,
factories.DefaultContextLoaderFactory(cmResolver),
nil,
)
return e.VerifyAndPatchImages(
ctx,
pContext,
)
}
func errorAssertionUtil(t *testing.T, image string, ivm engineapi.ImageVerificationMetadata, er engineapi.EngineResponse) {
assert.Equal(t, len(er.PolicyResponse.Rules), 1)
assert.Equal(t, er.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass, er.PolicyResponse.Rules[0].Message())
assert.Equal(t, ivm.IsEmpty(), false)
assert.Equal(t, ivm.IsVerified(image), true)
}
func Test_ImageVerifyCacheCosign(t *testing.T) {
opts := []imageverifycache.Option{
imageverifycache.WithCacheEnableFlag(true),
imageverifycache.WithMaxSize(1000),
imageverifycache.WithTTLDuration(24 * time.Hour),
}
imageVerifyCache, err := imageverifycache.New(opts...)
assert.NilError(t, err)
image := "ghcr.io/kyverno/test-verify-image:signed"
policyContext := buildContext(t, cosignTestPolicy, cosignTestResource, "")
start := time.Now()
er, ivm := testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
firstOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
start = time.Now()
er, ivm = testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
secondOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
assert.Check(t, secondOperationTime < firstOperationTime/10, "cache entry is valid, so image verification should be from cache.", firstOperationTime, secondOperationTime)
}
func Test_ImageVerifyCacheDisabled(t *testing.T) {
opts := []imageverifycache.Option{
imageverifycache.WithCacheEnableFlag(false),
imageverifycache.WithMaxSize(1000),
imageverifycache.WithTTLDuration(24 * time.Hour),
}
imageVerifyCache, err := imageverifycache.New(opts...)
assert.NilError(t, err)
image := "ghcr.io/kyverno/test-verify-image:signed"
policyContext := buildContext(t, cosignTestPolicy, cosignTestResource, "")
start := time.Now()
er, ivm := testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
firstOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
start = time.Now()
er, ivm = testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
secondOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
assert.Check(t, secondOperationTime > firstOperationTime/10 && secondOperationTime < firstOperationTime*10, "cache is disabled, so image verification should not be from cache.", firstOperationTime, secondOperationTime)
}
func Test_ImageVerifyCacheExpiredCosign(t *testing.T) {
opts := []imageverifycache.Option{
imageverifycache.WithCacheEnableFlag(true),
imageverifycache.WithMaxSize(1000),
imageverifycache.WithTTLDuration(2 * time.Second),
}
imageVerifyCache, err := imageverifycache.New(opts...)
assert.NilError(t, err)
image := "ghcr.io/kyverno/test-verify-image:signed"
policyContext := buildContext(t, cosignTestPolicy, cosignTestResource, "")
start := time.Now()
er, ivm := testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
firstOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
time.Sleep(5 * time.Second)
start = time.Now()
er, ivm = testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
secondOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
assert.Check(t, secondOperationTime > firstOperationTime/10 && secondOperationTime < firstOperationTime*10, "cache entry is expired, so image verification should not be from cache.", firstOperationTime, secondOperationTime)
}
func Test_changePolicyCacheVerificationCosign(t *testing.T) {
opts := []imageverifycache.Option{
imageverifycache.WithCacheEnableFlag(true),
imageverifycache.WithMaxSize(1000),
imageverifycache.WithTTLDuration(60 * time.Minute),
}
imageVerifyCache, err := imageverifycache.New(opts...)
assert.NilError(t, err)
image := "ghcr.io/kyverno/test-verify-image:signed"
policyContext := buildContext(t, cosignTestPolicy, cosignTestResource, "")
start := time.Now()
er, ivm := testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
firstOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
policyContext = buildContext(t, cosignTestPolicyUpdated, cosignTestResource, "")
start = time.Now()
er, ivm = testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
secondOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
assert.Check(t, secondOperationTime > firstOperationTime/10 && secondOperationTime < firstOperationTime*10, "cache entry not found, so image verification should not be from cache.", firstOperationTime, secondOperationTime)
}
var verifyImageNotaryPolicy = `{
"apiVersion": "kyverno.io/v2beta1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image-notary"
},
"spec": {
"validationFailureAction": "Enforce",
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "verify-signature-notary",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"verifyImages": [
{
"type": "Notary",
"imageReferences": [
"ghcr.io/kyverno/test-verify-image*"
],
"useCache": true,
"attestors": [
{
"count": 1,
"entries": [
{
"certificates": {
"cert": "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG\nTm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx\nMTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0\ndGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+\nb+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL\nhVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m\nIia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0\nVp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f\nETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG\nA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G\nCSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9\nkYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8\nZq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF\nByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ\n5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0\nuOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz\n-----END CERTIFICATE-----"
}
}
]
}
]
}
]
}
]
}
}`
var verifyImageNotaryUpdatedPolicy = `{
"apiVersion": "kyverno.io/v2beta1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image-notary"
},
"spec": {
"validationFailureAction": "Enforce",
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "verify-signature-notary-1",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"verifyImages": [
{
"type": "Notary",
"imageReferences": [
"ghcr.io/kyverno/test-verify-image*"
],
"useCache": true,
"attestors": [
{
"count": 1,
"entries": [
{
"certificates": {
"cert": "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG\nTm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx\nMTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0\ndGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+\nb+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL\nhVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m\nIia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0\nVp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f\nETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG\nA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G\nCSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9\nkYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8\nZq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF\nByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ\n5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0\nuOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz\n-----END CERTIFICATE-----"
}
}
]
}
]
}
]
}
]
}
}`
var verifyImageNotaryResource = `{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"creationTimestamp": null,
"labels": {
"run": "test"
},
"name": "test",
"namespace": "default"
},
"spec": {
"containers": [
{
"image": "ghcr.io/kyverno/test-verify-image:signed",
"name": "test",
"resources": {}
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always"
},
"status": {}
}`
func Test_ImageVerifyCacheNotary(t *testing.T) {
opts := []imageverifycache.Option{
imageverifycache.WithCacheEnableFlag(true),
imageverifycache.WithMaxSize(1000),
imageverifycache.WithTTLDuration(24 * time.Hour),
}
imageVerifyCache, err := imageverifycache.New(opts...)
assert.NilError(t, err)
image := "ghcr.io/kyverno/test-verify-image:signed"
policyContext := buildContext(t, verifyImageNotaryPolicy, verifyImageNotaryResource, "")
start := time.Now()
er, ivm := testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
firstOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
start = time.Now()
er, ivm = testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
secondOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
assert.Check(t, secondOperationTime < firstOperationTime/10, "cache entry is valid, so image verification should be from cache.", firstOperationTime, secondOperationTime)
}
func Test_ImageVerifyCacheExpiredNotary(t *testing.T) {
opts := []imageverifycache.Option{
imageverifycache.WithCacheEnableFlag(true),
imageverifycache.WithMaxSize(1000),
imageverifycache.WithTTLDuration(2 * time.Second),
}
imageVerifyCache, err := imageverifycache.New(opts...)
assert.NilError(t, err)
image := "ghcr.io/kyverno/test-verify-image:signed"
policyContext := buildContext(t, verifyImageNotaryPolicy, verifyImageNotaryResource, "")
start := time.Now()
er, ivm := testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
firstOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
time.Sleep(5 * time.Second)
start = time.Now()
er, ivm = testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
secondOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
assert.Check(t, secondOperationTime > firstOperationTime/10 && secondOperationTime < firstOperationTime*10, "cache entry is expired, so image verification should not be from cache.", firstOperationTime, secondOperationTime)
}
func Test_changePolicyCacheVerificationNotary(t *testing.T) {
opts := []imageverifycache.Option{
imageverifycache.WithCacheEnableFlag(true),
imageverifycache.WithMaxSize(1000),
imageverifycache.WithTTLDuration(60 * time.Minute),
}
imageVerifyCache, err := imageverifycache.New(opts...)
assert.NilError(t, err)
image := "ghcr.io/kyverno/test-verify-image:signed"
policyContext := buildContext(t, verifyImageNotaryPolicy, verifyImageNotaryResource, "")
start := time.Now()
er, ivm := testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
firstOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
policyContext = buildContext(t, verifyImageNotaryUpdatedPolicy, verifyImageNotaryResource, "")
start = time.Now()
er, ivm = testImageVerifyCache(imageVerifyCache, context.TODO(), registryclient.NewOrDie(), nil, policyContext, cfg)
secondOperationTime := time.Since(start)
errorAssertionUtil(t, image, ivm, er)
assert.Check(t, secondOperationTime > firstOperationTime/10 && secondOperationTime < firstOperationTime*10, "cache entry not found, so image verification should not be from cache.", firstOperationTime, secondOperationTime)
}
var excludeVerifyImageNotaryPolicy = `{
"apiVersion": "kyverno.io/v2beta1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image-notary"
},
"spec": {
"validationFailureAction": "Enforce",
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "verify-signature-notary",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"verifyImages": [
{
"type": "Notary",
"imageReferences": [
"ghcr.io/*"
],
"skipImageReferences" : [
"ghcr.io/invalid-user*"
],
"attestors": [
{
"count": 1,
"entries": [
{
"certificates": {
"cert": "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG\nTm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx\nMTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0\ndGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+\nb+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL\nhVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m\nIia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0\nVp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f\nETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG\nA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G\nCSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9\nkYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8\nZq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF\nByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ\n5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0\nuOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz\n-----END CERTIFICATE-----"
}
}
]
}
]
}
]
}
]
}
}`
var excludeVerifyImageNotaryResourcePass = `{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"creationTimestamp": null,
"labels": {
"run": "test"
},
"name": "test",
"namespace": "default"
},
"spec": {
"containers": [
{
"image": "ghcr.io/kyverno/test-verify-image:signed",
"name": "test",
"resources": {}
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always"
},
"status": {}
}`
var excludeVerifyImageNotaryResourceSkip = `{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"creationTimestamp": null,
"labels": {
"run": "test"
},
"name": "testskip",
"namespace": "default"
},
"spec": {
"containers": [
{
"image": "ghcr.io/invalid-user/invalid-image:v1",
"name": "test",
"resources": {}
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always"
},
"status": {}
}`
func Test_SkipImageReferences(t *testing.T) {
policyContextPass := buildContext(t, excludeVerifyImageNotaryPolicy, excludeVerifyImageNotaryResourcePass, "")
// Passes as image is included and not excluded
erPass, ivm := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContextPass, cfg)
assert.Equal(t, len(erPass.PolicyResponse.Rules), 1)
assert.Equal(t, erPass.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass,
fmt.Sprintf("expected: %v, got: %v, failure: %v",
engineapi.RuleStatusPass, erPass.PolicyResponse.Rules[0].Status(), erPass.PolicyResponse.Rules[0].Message()))
assert.Equal(t, ivm.IsEmpty(), false)
policyContextSkip := buildContext(t, excludeVerifyImageNotaryPolicy, excludeVerifyImageNotaryResourceSkip, "")
// Skipped as image is excluded
erSkip, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContextSkip, cfg)
assert.Equal(t, len(erSkip.PolicyResponse.Rules), 1)
assert.Equal(t, erSkip.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusSkip,
fmt.Sprintf("expected: %v, got: %v, failure: %v",
engineapi.RuleStatusPass, erSkip.PolicyResponse.Rules[0].Status(), erSkip.PolicyResponse.Rules[0].Message()))
}
var multipleImageVerificationAttestationPolicyPass = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image-attestation"
},
"spec": {
"validationFailureAction": "Enforce",
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "verify-attestation-notary",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"context": [
{
"name": "keys",
"configMap": {
"name": "keys",
"namespace": "notary-verify-attestation"
}
}
],
"verifyImages": [
{
"type": "Notary",
"imageReferences": [
"ghcr.io/kyverno/test-verify-image*"
],
"attestations": [
{
"type": "sbom/cyclone-dx",
"name": "sbom",
"attestors": [
{
"entries": [
{
"certificates": {
"cert": "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG\nTm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx\nMTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0\ndGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+\nb+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL\nhVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m\nIia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0\nVp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f\nETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG\nA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G\nCSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9\nkYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8\nZq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF\nByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ\n5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0\nuOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz\n-----END CERTIFICATE-----"
}
}
]
}
]
},
{
"type": "vulnerability-scan",
"name": "scan",
"attestors": [
{
"entries": [
{
"certificates": {
"cert": "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG\nTm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx\nMTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0\ndGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+\nb+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL\nhVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m\nIia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0\nVp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f\nETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG\nA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G\nCSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9\nkYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8\nZq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF\nByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ\n5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0\nuOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz\n-----END CERTIFICATE-----"
}
}
]
}
]
}
],
"validate": {
"deny": {
"conditions": {
"any": [
{
"key": "{{ time_after('{{ sbom.metadata.timestamp }}', '{{ scan.descriptor.timestamp }}' ) }}",
"operator": "Equals",
"value": "False"
}
]
}
},
"message": "Sample Validation"
}
}
]
}
]
}
}`
var multipleImageVerificationAttestationPolicyFail = `{
"apiVersion": "kyverno.io/v1",
"kind": "ClusterPolicy",
"metadata": {
"name": "check-image-attestation"
},
"spec": {
"validationFailureAction": "Enforce",
"webhookTimeoutSeconds": 30,
"failurePolicy": "Fail",
"rules": [
{
"name": "verify-attestation-notary",
"match": {
"any": [
{
"resources": {
"kinds": [
"Pod"
]
}
}
]
},
"context": [
{
"name": "keys",
"configMap": {
"name": "keys",
"namespace": "notary-verify-attestation"
}
}
],
"verifyImages": [
{
"type": "Notary",
"imageReferences": [
"ghcr.io/kyverno/test-verify-image*"
],
"attestations": [
{
"type": "sbom/cyclone-dx",
"name": "sbom",
"attestors": [
{
"entries": [
{
"certificates": {
"cert": "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG\nTm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx\nMTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0\ndGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+\nb+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL\nhVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m\nIia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0\nVp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f\nETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG\nA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G\nCSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9\nkYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8\nZq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF\nByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ\n5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0\nuOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz\n-----END CERTIFICATE-----"
}
}
]
}
]
},
{
"type": "vulnerability-scan",
"name": "scan",
"attestors": [
{
"entries": [
{
"certificates": {
"cert": "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIJAPI+zAzn4s0xMA0GCSqGSIb3DQEBCwUAMEwxCzAJBgNV\nBAYTAlVTMQswCQYDVQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEPMA0GA1UECgwG\nTm90YXJ5MQ0wCwYDVQQDDAR0ZXN0MB4XDTIzMDUyMjIxMTUxOFoXDTMzMDUxOTIx\nMTUxOFowTDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0\ndGxlMQ8wDQYDVQQKDAZOb3RhcnkxDTALBgNVBAMMBHRlc3QwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDNhTwv+QMk7jEHufFfIFlBjn2NiJaYPgL4eBS+\nb+o37ve5Zn9nzRppV6kGsa161r9s2KkLXmJrojNy6vo9a6g6RtZ3F6xKiWLUmbAL\nhVTCfYw/2n7xNlVMjyyUpE+7e193PF8HfQrfDFxe2JnX5LHtGe+X9vdvo2l41R6m\nIia04DvpMdG4+da2tKPzXIuLUz/FDb6IODO3+qsqQLwEKmmUee+KX+3yw8I6G1y0\nVp0mnHfsfutlHeG8gazCDlzEsuD4QJ9BKeRf2Vrb0ywqNLkGCbcCWF2H5Q80Iq/f\nETVO9z88R7WheVdEjUB8UrY7ZMLdADM14IPhY2Y+tLaSzEVZAgMBAAGjMjAwMAkG\nA1UdEwQCMAAwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA0G\nCSqGSIb3DQEBCwUAA4IBAQBX7x4Ucre8AIUmXZ5PUK/zUBVOrZZzR1YE8w86J4X9\nkYeTtlijf9i2LTZMfGuG0dEVFN4ae3CCpBst+ilhIndnoxTyzP+sNy4RCRQ2Y/k8\nZq235KIh7uucq96PL0qsF9s2RpTKXxyOGdtp9+HO0Ty5txJE2txtLDUIVPK5WNDF\nByCEQNhtHgN6V20b8KU2oLBZ9vyB8V010dQz0NRTDLhkcvJig00535/LUylECYAJ\n5/jn6XKt6UYCQJbVNzBg/YPGc1RF4xdsGVDBben/JXpeGEmkdmXPILTKd9tZ5TC0\nuOKpF5rWAruB5PCIrquamOejpXV9aQA/K2JQDuc0mcKz\n-----END CERTIFICATE-----"
}
}
]
}
]
}
],
"validate": {
"deny": {
"conditions": {
"any": [
{
"key": "{{ time_after('{{ sbom.metadata.timestamp }}', '{{ scan.descriptor.timestamp }}' ) }}",
"operator": "Equals",
"value": "True"
}
]
}
},
"message": "Sample Validation"
}
}
]
}
]
}
}`
func Test_MultipleImageVerificationAttestationPass(t *testing.T) {
policyContextPass := buildContext(t, multipleImageVerificationAttestationPolicyPass, excludeVerifyImageNotaryResourcePass, "")
// Passes as image is included and not excluded
erPass, ivm := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContextPass, cfg)
assert.Equal(t, len(erPass.PolicyResponse.Rules), 1)
assert.Equal(t, erPass.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass,
fmt.Sprintf("expected: %v, got: %v, failure: %v",
engineapi.RuleStatusPass, erPass.PolicyResponse.Rules[0].Status(), erPass.PolicyResponse.Rules[0].Message()))
assert.Equal(t, ivm.IsEmpty(), false)
policyContextSkip := buildContext(t, excludeVerifyImageNotaryPolicy, excludeVerifyImageNotaryResourceSkip, "")
// Skipped as image is excluded
erSkip, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContextSkip, cfg)
assert.Equal(t, len(erSkip.PolicyResponse.Rules), 1)
assert.Equal(t, erSkip.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusSkip,
fmt.Sprintf("expected: %v, got: %v, failure: %v",
engineapi.RuleStatusPass, erSkip.PolicyResponse.Rules[0].Status(), erSkip.PolicyResponse.Rules[0].Message()))
}
func Test_MultipleImageVerificationAttestationFail(t *testing.T) {
policyContextPass := buildContext(t, multipleImageVerificationAttestationPolicyFail, excludeVerifyImageNotaryResourcePass, "")
// Passes as image is included and not excluded
erPass, ivm := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContextPass, cfg)
assert.Equal(t, len(erPass.PolicyResponse.Rules), 1)
assert.Equal(t, erPass.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusPass,
fmt.Sprintf("expected: %v, got: %v, failure: %v",
engineapi.RuleStatusPass, erPass.PolicyResponse.Rules[0].Status(), erPass.PolicyResponse.Rules[0].Message()))
assert.Equal(t, ivm.IsEmpty(), false)
policyContextSkip := buildContext(t, excludeVerifyImageNotaryPolicy, excludeVerifyImageNotaryResourceSkip, "")
// Skipped as image is excluded
erSkip, _ := testVerifyAndPatchImages(context.TODO(), registryclient.NewOrDie(), nil, policyContextSkip, cfg)
assert.Equal(t, len(erSkip.PolicyResponse.Rules), 1)
assert.Equal(t, erSkip.PolicyResponse.Rules[0].Status(), engineapi.RuleStatusSkip,
fmt.Sprintf("expected: %v, got: %v, failure: %v",
engineapi.RuleStatusPass, erSkip.PolicyResponse.Rules[0].Status(), erSkip.PolicyResponse.Rules[0].Message()))
}