mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
fix attestation checks (#3999)
* fix attestation checks Signed-off-by: Jim Bugwadia <jim@nirmata.com> * make fmt Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix linter issues Signed-off-by: Jim Bugwadia <jim@nirmata.com> * make codegen Signed-off-by: Jim Bugwadia <jim@nirmata.com> * fix tests Signed-off-by: Jim Bugwadia <jim@nirmata.com> * dos2unix Signed-off-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
parent
88f769cb39
commit
8fe9163f4e
5 changed files with 454 additions and 152 deletions
|
@ -13,7 +13,6 @@ import (
|
|||
"github.com/google/go-containerregistry/pkg/name"
|
||||
"github.com/in-toto/in-toto-golang/in_toto"
|
||||
wildcard "github.com/kyverno/go-wildcard"
|
||||
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/registryclient"
|
||||
"github.com/kyverno/kyverno/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -35,6 +34,7 @@ var ImageSignatureRepository string
|
|||
|
||||
type Options struct {
|
||||
ImageRef string
|
||||
FetchAttestations bool
|
||||
Key string
|
||||
Cert string
|
||||
CertChain string
|
||||
|
@ -47,30 +47,95 @@ type Options struct {
|
|||
RekorURL string
|
||||
}
|
||||
|
||||
// VerifySignature verifies that the image has the expected signatures
|
||||
func VerifySignature(opts Options) (digest string, err error) {
|
||||
ctx := context.Background()
|
||||
var remoteOpts []remote.Option
|
||||
ro := options.RegistryOptions{}
|
||||
remoteOpts, err = ro.ClientOpts(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "constructing client options")
|
||||
type Response struct {
|
||||
Digest string
|
||||
Statements []map[string]interface{}
|
||||
}
|
||||
|
||||
func Verify(opts Options) (*Response, error) {
|
||||
if opts.FetchAttestations {
|
||||
return fetchAttestations(opts)
|
||||
} else {
|
||||
return verifySignature(opts)
|
||||
}
|
||||
}
|
||||
|
||||
// verifySignature verifies that the image has the expected signatures
|
||||
func verifySignature(opts Options) (*Response, error) {
|
||||
ref, err := name.ParseReference(opts.ImageRef)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse image %s", opts.ImageRef)
|
||||
}
|
||||
|
||||
cosignOpts, err := buildCosignOptions(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signatures, bundleVerified, err := client.VerifyImageSignatures(context.Background(), ref, cosignOpts)
|
||||
if err != nil {
|
||||
logger.Info("image verification failed", "error", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.V(3).Info("verified image", "count", len(signatures), "bundleVerified", bundleVerified)
|
||||
payload, err := extractPayload(signatures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := matchSubjectAndIssuer(signatures, opts.Subject, opts.Issuer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := matchExtensions(signatures, opts.AdditionalExtensions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkAnnotations(payload, opts.Annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
digest, err := extractDigest(opts.ImageRef, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Response{Digest: digest}, nil
|
||||
}
|
||||
|
||||
func buildCosignOptions(opts Options) (*cosign.CheckOpts, error) {
|
||||
var remoteOpts []remote.Option
|
||||
var err error
|
||||
ro := options.RegistryOptions{}
|
||||
remoteOpts, err = ro.ClientOpts(context.Background())
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "constructing client options")
|
||||
}
|
||||
|
||||
o, err := registryclient.GetOptions()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "getting remote options")
|
||||
return nil, errors.Wrap(err, "getting remote options")
|
||||
}
|
||||
|
||||
remoteOpts = append(remoteOpts, remote.WithRemoteOptions(o))
|
||||
|
||||
cosignOpts := &cosign.CheckOpts{
|
||||
Annotations: map[string]interface{}{},
|
||||
RegistryClientOpts: remoteOpts,
|
||||
ClaimVerifier: cosign.SimpleClaimVerifier,
|
||||
}
|
||||
|
||||
if opts.FetchAttestations {
|
||||
cosignOpts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
|
||||
} else {
|
||||
cosignOpts.ClaimVerifier = cosign.SimpleClaimVerifier
|
||||
}
|
||||
|
||||
if opts.Roots != "" {
|
||||
cp, err := loadCertPool([]byte(opts.Roots))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to load Root certificates")
|
||||
return nil, errors.Wrap(err, "failed to load Root certificates")
|
||||
}
|
||||
cosignOpts.RootCerts = cp
|
||||
}
|
||||
|
@ -79,13 +144,13 @@ func VerifySignature(opts Options) (digest string, err error) {
|
|||
if strings.HasPrefix(opts.Key, "-----BEGIN PUBLIC KEY-----") {
|
||||
cosignOpts.SigVerifier, err = decodePEM([]byte(opts.Key))
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to load public key from PEM")
|
||||
return nil, errors.Wrap(err, "failed to load public key from PEM")
|
||||
}
|
||||
} else {
|
||||
// this supports Kubernetes secrets and KMS
|
||||
cosignOpts.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, opts.Key)
|
||||
cosignOpts.SigVerifier, err = sigs.PublicKeyFromKeyRef(context.Background(), opts.Key)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to load public key from %s", opts.Key)
|
||||
return nil, errors.Wrapf(err, "failed to load public key from %s", opts.Key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -93,30 +158,30 @@ func VerifySignature(opts Options) (digest string, err error) {
|
|||
// load cert and optionally a cert chain as a verifier
|
||||
cert, err := loadCert([]byte(opts.Cert))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to load certificate from %s", opts.Cert)
|
||||
return nil, errors.Wrapf(err, "failed to load certificate from %s", string(opts.Cert))
|
||||
}
|
||||
|
||||
if opts.CertChain == "" {
|
||||
cosignOpts.SigVerifier, err = signature.LoadVerifier(cert.PublicKey, crypto.SHA256)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to load signature from certificate")
|
||||
return nil, errors.Wrap(err, "failed to load signature from certificate")
|
||||
}
|
||||
} else {
|
||||
// Verify certificate with chain
|
||||
chain, err := loadCertChain([]byte(opts.CertChain))
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, errors.Wrap(err, "failed to load load certificate chain")
|
||||
}
|
||||
cosignOpts.SigVerifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, cosignOpts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, errors.Wrap(err, "failed to load validate certificate chain")
|
||||
}
|
||||
}
|
||||
} else if opts.CertChain != "" {
|
||||
// load cert chain as roots
|
||||
cp, err := loadCertPool([]byte(opts.CertChain))
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to load cert chain")
|
||||
return nil, errors.Wrap(err, "failed to load certificates")
|
||||
}
|
||||
cosignOpts.RootCerts = cp
|
||||
} else {
|
||||
|
@ -130,63 +195,20 @@ func VerifySignature(opts Options) (digest string, err error) {
|
|||
if opts.RekorURL != "" {
|
||||
cosignOpts.RekorClient, err = rekor.NewClient(opts.RekorURL)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to create Rekor client from URL %s", opts.RekorURL)
|
||||
return nil, errors.Wrapf(err, "failed to create Rekor client from URL %s", opts.RekorURL)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Repository != "" {
|
||||
signatureRepo, err := name.NewRepository(opts.Repository)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse signature repository %s", opts.Repository)
|
||||
return nil, errors.Wrapf(err, "failed to parse signature repository %s", opts.Repository)
|
||||
}
|
||||
|
||||
cosignOpts.RegistryClientOpts = append(cosignOpts.RegistryClientOpts, remote.WithTargetRepository(signatureRepo))
|
||||
}
|
||||
|
||||
ref, err := name.ParseReference(opts.ImageRef)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to parse image")
|
||||
}
|
||||
|
||||
signatures, bundleVerified, err := client.VerifyImageSignatures(ctx, ref, cosignOpts)
|
||||
if err != nil {
|
||||
logger.Info("image verification failed", "error", err.Error())
|
||||
return "", err
|
||||
}
|
||||
|
||||
logger.V(3).Info("verified image", "count", len(signatures), "bundleVerified", bundleVerified)
|
||||
pld, err := extractPayload(signatures)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to get pld")
|
||||
}
|
||||
|
||||
if err := matchSubjectAndIssuer(signatures, opts.Subject, opts.Issuer); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := matchExtensions(signatures, opts.AdditionalExtensions); err != nil {
|
||||
return "", errors.Wrap(err, "extensions mismatch")
|
||||
}
|
||||
|
||||
err = checkAnnotations(pld, opts.Annotations)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "annotation mismatch")
|
||||
}
|
||||
|
||||
digest, err = extractDigest(opts.ImageRef, pld)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "failed to get digest")
|
||||
}
|
||||
|
||||
return digest, nil
|
||||
}
|
||||
|
||||
func getFulcioRoots(roots []byte) (*x509.CertPool, error) {
|
||||
if len(roots) == 0 {
|
||||
return fulcio.GetRoots(), nil
|
||||
}
|
||||
|
||||
return loadCertPool(roots)
|
||||
return cosignOpts, nil
|
||||
}
|
||||
|
||||
func loadCertPool(roots []byte) (*x509.CertPool, error) {
|
||||
|
@ -220,35 +242,15 @@ func loadCertChain(pem []byte) ([]*x509.Certificate, error) {
|
|||
return cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(pem))
|
||||
}
|
||||
|
||||
// FetchAttestations retrieves signed attestations and decodes them into in-toto statements
|
||||
// fetchAttestations retrieves signed attestations and decodes them into in-toto statements
|
||||
// https://github.com/in-toto/attestation/blob/main/spec/README.md#statement
|
||||
func FetchAttestations(imageRef string, imageVerify kyvernov1.ImageVerification) ([]map[string]interface{}, error) {
|
||||
ctx := context.Background()
|
||||
var err error
|
||||
|
||||
cosignOpts := &cosign.CheckOpts{
|
||||
ClaimVerifier: cosign.IntotoSubjectClaimVerifier,
|
||||
}
|
||||
|
||||
if imageVerify.Key != "" {
|
||||
if strings.HasPrefix(imageVerify.Key, "-----BEGIN PUBLIC KEY-----") {
|
||||
cosignOpts.SigVerifier, err = decodePEM([]byte(imageVerify.Key))
|
||||
} else {
|
||||
cosignOpts.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, imageVerify.Key)
|
||||
}
|
||||
} else {
|
||||
cosignOpts.CertEmail = ""
|
||||
cosignOpts.RootCerts, err = getFulcioRoots([]byte(imageVerify.Roots))
|
||||
if err == nil {
|
||||
cosignOpts.RekorClient, err = rekor.NewClient("https://rekor.sigstore.dev")
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAttestations(opts Options) (*Response, error) {
|
||||
cosignOpts, err := buildCosignOptions(opts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "loading credentials")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref, err := name.ParseReference(imageRef)
|
||||
ref, err := name.ParseReference(opts.ImageRef)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse image")
|
||||
}
|
||||
|
@ -258,51 +260,61 @@ func FetchAttestations(imageRef string, imageVerify kyvernov1.ImageVerification)
|
|||
msg := err.Error()
|
||||
logger.Info("failed to fetch attestations", "error", msg)
|
||||
if strings.Contains(msg, "MANIFEST_UNKNOWN: manifest unknown") {
|
||||
return nil, fmt.Errorf("not found")
|
||||
return nil, errors.Wrap(fmt.Errorf("not found"), "")
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.V(3).Info("verified images", "count", len(signatures), "bundleVerified", bundleVerified)
|
||||
inTotoStatements, err := decodeStatements(signatures)
|
||||
logger.V(3).Info("verified images", "signatures", len(signatures), "bundleVerified", bundleVerified)
|
||||
inTotoStatements, digest, err := decodeStatements(signatures)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return inTotoStatements, nil
|
||||
return &Response{Digest: digest, Statements: inTotoStatements}, nil
|
||||
}
|
||||
|
||||
func decodeStatements(sigs []oci.Signature) ([]map[string]interface{}, error) {
|
||||
func decodeStatements(sigs []oci.Signature) ([]map[string]interface{}, string, error) {
|
||||
if len(sigs) == 0 {
|
||||
return []map[string]interface{}{}, nil
|
||||
return []map[string]interface{}{}, "", nil
|
||||
}
|
||||
|
||||
var digest string
|
||||
decodedStatements := make([]map[string]interface{}, len(sigs))
|
||||
for i, sig := range sigs {
|
||||
pld, err := sig.Payload()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode payload")
|
||||
return nil, "", errors.Wrap(err, "failed to decode payload")
|
||||
}
|
||||
|
||||
sci := payload.SimpleContainerImage{}
|
||||
if err := json.Unmarshal(pld, &sci); err != nil {
|
||||
return nil, "", errors.Wrap(err, "error decoding the payload")
|
||||
}
|
||||
|
||||
if d := sci.Critical.Image.DockerManifestDigest; d != "" {
|
||||
digest = d
|
||||
}
|
||||
|
||||
data := make(map[string]interface{})
|
||||
if err := json.Unmarshal(pld, &data); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to unmarshal JSON payload: %v", sig)
|
||||
return nil, "", errors.Wrapf(err, "failed to unmarshal JSON payload: %v", sig)
|
||||
}
|
||||
|
||||
if dataPayload, ok := data["payload"]; !ok {
|
||||
return nil, fmt.Errorf("missing payload in %v", data)
|
||||
return nil, "", fmt.Errorf("missing payload in %v", data)
|
||||
} else {
|
||||
decodedStatement, err := decodeStatement(dataPayload.(string))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to decode statement %s", string(pld))
|
||||
return nil, "", errors.Wrapf(err, "failed to decode statement %s", string(pld))
|
||||
}
|
||||
|
||||
decodedStatements[i] = decodedStatement
|
||||
}
|
||||
}
|
||||
|
||||
return decodedStatements, nil
|
||||
return decodedStatements, digest, nil
|
||||
}
|
||||
|
||||
func decodeStatement(payloadBase64 string) (map[string]interface{}, error) {
|
||||
|
@ -482,7 +494,8 @@ func checkAnnotations(payload []payload.SimpleContainerImage, annotations map[st
|
|||
for _, p := range payload {
|
||||
for key, val := range annotations {
|
||||
if val != p.Optional[key] {
|
||||
return fmt.Errorf("annotation value for %s does not match", key)
|
||||
return fmt.Errorf("annotations mismatch: %s does not match expected value %s for key %s",
|
||||
p.Optional[key], val, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,14 +70,14 @@ func TestCosignKeyless(t *testing.T) {
|
|||
Subject: "jim",
|
||||
}
|
||||
|
||||
_, err := VerifySignature(opts)
|
||||
_, err := verifySignature(opts)
|
||||
assert.Error(t, err, "subject mismatch: expected jim@nirmata.com, got jim")
|
||||
|
||||
opts.Subject = "jim@nirmata.com"
|
||||
_, err = VerifySignature(opts)
|
||||
_, err = verifySignature(opts)
|
||||
assert.Error(t, err, "issuer mismatch: expected https://github.com/login/oauth, got https://github.com/")
|
||||
|
||||
opts.Issuer = "https://github.com/login/oauth"
|
||||
_, err = VerifySignature(opts)
|
||||
_, err = verifySignature(opts)
|
||||
assert.NilError(t, err)
|
||||
}
|
||||
|
|
264
pkg/engine/attestation_test.go
Normal file
264
pkg/engine/attestation_test.go
Normal file
|
@ -0,0 +1,264 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/utils/api"
|
||||
"github.com/kyverno/kyverno/pkg/utils/image"
|
||||
"gotest.tools/assert"
|
||||
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
var scanPredicate = `
|
||||
{
|
||||
"predicate": {
|
||||
"matches": [
|
||||
{
|
||||
"vulnerability": {
|
||||
"id": "CVE-2021-22946",
|
||||
"dataSource": "http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22946",
|
||||
"namespace": "alpine:3.11",
|
||||
"severity": "High",
|
||||
"urls": [
|
||||
"http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22946"
|
||||
],
|
||||
"cvss": [],
|
||||
"fix": {
|
||||
"versions": [
|
||||
"7.79.0-r0"
|
||||
],
|
||||
"state": "fixed"
|
||||
},
|
||||
"advisories": []
|
||||
},
|
||||
"relatedVulnerabilities": [
|
||||
{
|
||||
"id": "CVE-2021-22946",
|
||||
"dataSource": "https://nvd.nist.gov/vuln/detail/CVE-2021-22946",
|
||||
"namespace": "nvd",
|
||||
"severity": "High",
|
||||
"urls": [
|
||||
"https://hackerone.com/reports/1334111",
|
||||
"https://lists.debian.org/debian-lts-announce/2021/09/msg00022.html",
|
||||
"https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/RWLEC6YVEM2HWUBX67SDGPSY4CQB72OE/",
|
||||
"https://www.oracle.com/security-alerts/cpuoct2021.html",
|
||||
"https://security.netapp.com/advisory/ntap-20211029-0003/",
|
||||
"https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/APOAK4X73EJTAPTSVT7IRVDMUWVXNWGD/",
|
||||
"https://security.netapp.com/advisory/ntap-20220121-0008/",
|
||||
"https://www.oracle.com/security-alerts/cpujan2022.html",
|
||||
"https://cert-portal.siemens.com/productcert/pdf/ssa-389290.pdf",
|
||||
"https://support.apple.com/kb/HT213183",
|
||||
"http://seclists.org/fulldisclosure/2022/Mar/29",
|
||||
"https://www.oracle.com/security-alerts/cpuapr2022.html"
|
||||
],
|
||||
"description": "A user can tell curl >= 7.20.0 and <= 7.78.0 to require a successful upgrade to TLS when speaking to an IMAP, POP3 or FTP server (--ssl-reqd on the command line or CURLOPT_USE_SSL set to CURLUSESSL_CONTROL or CURLUSESSL_ALL withlibcurl). This requirement could be bypassed if the server would return a properly crafted but perfectly legitimate response.This flaw would then make curl silently continue its operations **withoutTLS** contrary to the instructions and expectations, exposing possibly sensitive data in clear text over the network.",
|
||||
"cvss": [
|
||||
{
|
||||
"version": "2.0",
|
||||
"vector": "AV:N/AC:L/Au:N/C:P/I:N/A:N",
|
||||
"metrics": {
|
||||
"baseScore": 5,
|
||||
"exploitabilityScore": 10,
|
||||
"impactScore": 2.9
|
||||
},
|
||||
"vendorMetadata": {}
|
||||
},
|
||||
{
|
||||
"version": "3.1",
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
|
||||
"metrics": {
|
||||
"baseScore": 7.5,
|
||||
"exploitabilityScore": 3.9,
|
||||
"impactScore": 3.6
|
||||
},
|
||||
"vendorMetadata": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"matchDetails": [
|
||||
{
|
||||
"matcher": "apk-matcher",
|
||||
"searchedBy": {
|
||||
"distro": {
|
||||
"type": "alpine",
|
||||
"version": "3.11.7"
|
||||
},
|
||||
"namespace": "alpine:3.11",
|
||||
"package": {
|
||||
"name": "curl",
|
||||
"version": "7.67.0-r3"
|
||||
}
|
||||
},
|
||||
"found": {
|
||||
"versionConstraint": "< 7.79.0-r0 (apk)"
|
||||
}
|
||||
}
|
||||
],
|
||||
"artifact": {
|
||||
"name": "libcurl",
|
||||
"version": "7.67.0-r3",
|
||||
"type": "apk",
|
||||
"locations": [
|
||||
{
|
||||
"path": "/lib/apk/db/installed",
|
||||
"layerID": "sha256:165c22a332e306497ffa210ce9f284906fe0bf6340d20c5f8521e064323ba52a"
|
||||
}
|
||||
],
|
||||
"language": "",
|
||||
"licenses": [
|
||||
"MIT"
|
||||
],
|
||||
"cpes": [
|
||||
"cpe:2.3:a:libcurl:libcurl:7.67.0-r3:*:*:*:*:*:*:*"
|
||||
],
|
||||
"purl": "pkg:alpine/libcurl@7.67.0-r3?arch=x86_64",
|
||||
"metadata": {
|
||||
"OriginPackage": "curl"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"source": {
|
||||
"type": "image",
|
||||
"target": {
|
||||
"userInput": "ghcr.io/tap8stry/git-init:v0.21.0@sha256:322e3502c1e6fba5f1869efb55cfd998a3679e073840d33eb0e3c482b5d5609b",
|
||||
"imageID": "sha256:ebbe9df4abf4dd9a739b33ab75d1fee2086713829a437f9d1e5e3de7b21e8d5f",
|
||||
"manifestDigest": "sha256:5fe577767eba4cca2fe7594f6df94ca2b0f639a2ee8794f99f2ac49b81b5d419",
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"tags": [
|
||||
"ghcr.io/tap8stry/git-init:v0.21.0"
|
||||
],
|
||||
"imageSize": 81343568,
|
||||
"layers": [
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:0fcbbeeeb0d7fc5c06362d7a6717b999e605574c7210eff4f7418f6e9be9fbfe",
|
||||
"size": 5610661
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:8f1d7de99bcffd39d4461b917a5313cfa0415f33eac9412a9b6138a27121c7e6",
|
||||
"size": 4686
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:165c22a332e306497ffa210ce9f284906fe0bf6340d20c5f8521e064323ba52a",
|
||||
"size": 37280295
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:0519ec3ee06ceaaa19b5682db6a01d408f9be6d74dc0f453e416fc92b654ce2f",
|
||||
"size": 9503892
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||
"digest": "sha256:a79b016ff7714845775dd921c15fab70652344015e91ebff2ccec49d6792ac11",
|
||||
"size": 28944034
|
||||
}
|
||||
],
|
||||
"manifest": "eyJzY2hlbWFWZXJzaW9uIjoyLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmRpc3RyaWJ1dGlvbi5tYW5pZmVzdC52Mitqc29uIiwiY29uZmlnIjp7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuY29udGFpbmVyLmltYWdlLnYxK2pzb24iLCJzaXplIjoxNzM1LCJkaWdlc3QiOiJzaGEyNTY6ZWJiZTlkZjRhYmY0ZGQ5YTczOWIzM2FiNzVkMWZlZTIwODY3MTM4MjlhNDM3ZjlkMWU1ZTNkZTdiMjFlOGQ1ZiJ9LCJsYXllcnMiOlt7Im1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5kb2NrZXIuaW1hZ2Uucm9vdGZzLmRpZmYudGFyLmd6aXAiLCJzaXplIjo1ODgxMzQ0LCJkaWdlc3QiOiJzaGEyNTY6MGZjYmJlZWViMGQ3ZmM1YzA2MzYyZDdhNjcxN2I5OTllNjA1NTc0YzcyMTBlZmY0Zjc0MThmNmU5YmU5ZmJmZSJ9LHsibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjExNzc2LCJkaWdlc3QiOiJzaGEyNTY6OGYxZDdkZTk5YmNmZmQzOWQ0NDYxYjkxN2E1MzEzY2ZhMDQxNWYzM2VhYzk0MTJhOWI2MTM4YTI3MTIxYzdlNiJ9LHsibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjM3NzQ5NzYwLCJkaWdlc3QiOiJzaGEyNTY6MTY1YzIyYTMzMmUzMDY0OTdmZmEyMTBjZTlmMjg0OTA2ZmUwYmY2MzQwZDIwYzVmODUyMWUwNjQzMjNiYTUyYSJ9LHsibWVkaWFUeXBlIjoiYXBwbGljYXRpb24vdm5kLmRvY2tlci5pbWFnZS5yb290ZnMuZGlmZi50YXIuZ3ppcCIsInNpemUiOjk2NTIyMjQsImRpZ2VzdCI6InNoYTI1NjowNTE5ZWMzZWUwNmNlYWFhMTliNTY4MmRiNmEwMWQ0MDhmOWJlNmQ3NGRjMGY0NTNlNDE2ZmM5MmI2NTRjZTJmIn0seyJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuZG9ja2VyLmltYWdlLnJvb3Rmcy5kaWZmLnRhci5nemlwIiwic2l6ZSI6Mjg5NDY0MzIsImRpZ2VzdCI6InNoYTI1NjphNzliMDE2ZmY3NzE0ODQ1Nzc1ZGQ5MjFjMTVmYWI3MDY1MjM0NDAxNWU5MWViZmYyY2NlYzQ5ZDY3OTJhYzExIn1dfQ==",
|
||||
"config": "eyJhcmNoaXRlY3R1cmUiOiJhbWQ2NCIsImF1dGhvciI6ImdpdGh1Yi5jb20vZ29vZ2xlL2tvIiwiY3JlYXRlZCI6IjIwMjEtMDItMTZUMTk6MzU6NDNaIiwiaGlzdG9yeSI6W3siY3JlYXRlZCI6IjIwMjAtMTItMTdUMDA6MTk6NDkuMTEyNDQ1OTY1WiIsImNyZWF0ZWRfYnkiOiIvYmluL3NoIC1jICMobm9wKSBBREQgZmlsZTo4ZWQ4MDAxMGU0NDNkYTE5ZDcyNTQ2YmNlZTlhMzVlMGE4ZDI0NGM3MjA1MmIxOTk0NjEwYmY1OTM5ZDQ3OWMyIGluIC8gIn0seyJjcmVhdGVkIjoiMjAyMC0xMi0xN1QwMDoxOTo0OS4yODQyMTExNDhaIiwiY3JlYXRlZF9ieSI6Ii9iaW4vc2ggLWMgIyhub3ApICBDTUQgW1wiL2Jpbi9zaFwiXSIsImVtcHR5X2xheWVyIjp0cnVlfSx7ImNyZWF0ZWQiOiIyMDIxLTAyLTE2VDE5OjM1OjI4LjkzODk1MjU1M1oiLCJjcmVhdGVkX2J5IjoiUlVOIC9iaW4vc2ggLWMgYWRkZ3JvdXAgLVMgLWcgNjU1MzIgbm9ucm9vdCBcdTAwMjZcdTAwMjYgYWRkdXNlciAtUyAtdSA2NTUzMiBub25yb290IC1HIG5vbnJvb3QgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJjcmVhdGVkIjoiMjAyMS0wMi0xNlQxOTozNTozMi41NTU3NTk2NTRaIiwiY3JlYXRlZF9ieSI6IlJVTiAvYmluL3NoIC1jIGFwayBhZGQgLS11cGRhdGUgZ2l0IGdpdC1sZnMgb3BlbnNzaC1jbGllbnQgICAgIFx1MDAyNlx1MDAyNiBhcGsgdXBkYXRlICAgICBcdTAwMjZcdTAwMjYgYXBrIHVwZ3JhZGUgIyBidWlsZGtpdCIsImNvbW1lbnQiOiJidWlsZGtpdC5kb2NrZXJmaWxlLnYwIn0seyJhdXRob3IiOiJrbyIsImNyZWF0ZWQiOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiIsImNyZWF0ZWRfYnkiOiJrbyBwdWJsaXNoIGtvOi8vZ2l0aHViLmNvbS90ZWt0b25jZC9waXBlbGluZS9jbWQvZ2l0LWluaXQiLCJjb21tZW50Ijoia29kYXRhIGNvbnRlbnRzLCBhdCAkS09fREFUQV9QQVRIIn0seyJhdXRob3IiOiJrbyIsImNyZWF0ZWQiOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiIsImNyZWF0ZWRfYnkiOiJrbyBwdWJsaXNoIGtvOi8vZ2l0aHViLmNvbS90ZWt0b25jZC9waXBlbGluZS9jbWQvZ2l0LWluaXQiLCJjb21tZW50IjoiZ28gYnVpbGQgb3V0cHV0LCBhdCAva28tYXBwL2dpdC1pbml0In1dLCJvcyI6ImxpbnV4Iiwicm9vdGZzIjp7InR5cGUiOiJsYXllcnMiLCJkaWZmX2lkcyI6WyJzaGEyNTY6MGZjYmJlZWViMGQ3ZmM1YzA2MzYyZDdhNjcxN2I5OTllNjA1NTc0YzcyMTBlZmY0Zjc0MThmNmU5YmU5ZmJmZSIsInNoYTI1Njo4ZjFkN2RlOTliY2ZmZDM5ZDQ0NjFiOTE3YTUzMTNjZmEwNDE1ZjMzZWFjOTQxMmE5YjYxMzhhMjcxMjFjN2U2Iiwic2hhMjU2OjE2NWMyMmEzMzJlMzA2NDk3ZmZhMjEwY2U5ZjI4NDkwNmZlMGJmNjM0MGQyMGM1Zjg1MjFlMDY0MzIzYmE1MmEiLCJzaGEyNTY6MDUxOWVjM2VlMDZjZWFhYTE5YjU2ODJkYjZhMDFkNDA4ZjliZTZkNzRkYzBmNDUzZTQxNmZjOTJiNjU0Y2UyZiIsInNoYTI1NjphNzliMDE2ZmY3NzE0ODQ1Nzc1ZGQ5MjFjMTVmYWI3MDY1MjM0NDAxNWU5MWViZmYyY2NlYzQ5ZDY3OTJhYzExIl19LCJjb25maWciOnsiQ21kIjpbIi9iaW4vc2giXSwiRW50cnlwb2ludCI6WyIva28tYXBwL2dpdC1pbml0Il0sIkVudiI6WyJQQVRIPS91c3IvbG9jYWwvc2JpbjovdXNyL2xvY2FsL2JpbjovdXNyL3NiaW46L3Vzci9iaW46L3NiaW46L2Jpbjova28tYXBwIiwiS09fREFUQV9QQVRIPS92YXIvcnVuL2tvIl19fQ==",
|
||||
"repoDigests": [
|
||||
"ghcr.io/tap8stry/git-init@sha256:322e3502c1e6fba5f1869efb55cfd998a3679e073840d33eb0e3c482b5d5609b"
|
||||
]
|
||||
}
|
||||
},
|
||||
"distro": {
|
||||
"name": "Alpine Linux",
|
||||
"version": "",
|
||||
"idLike": null
|
||||
},
|
||||
"descriptor": {
|
||||
"name": "grype",
|
||||
"version": "0.32.0",
|
||||
"configuration": {
|
||||
"configPath": "",
|
||||
"output": "json",
|
||||
"file": "",
|
||||
"output-template-file": "",
|
||||
"quiet": false,
|
||||
"check-for-app-update": true,
|
||||
"only-fixed": false,
|
||||
"search": {
|
||||
"scope": "Squashed",
|
||||
"unindexed-archives": false,
|
||||
"indexed-archives": true
|
||||
},
|
||||
"ignore": null,
|
||||
"exclude": [],
|
||||
"db": {
|
||||
"cache-dir": "/home/jim/.cache/grype/db",
|
||||
"update-url": "https://toolbox-data.anchore.io/grype/databases/listing.json",
|
||||
"ca-cert": "",
|
||||
"auto-update": true,
|
||||
"validate-by-hash-on-start": false
|
||||
},
|
||||
"dev": {
|
||||
"profile-cpu": false,
|
||||
"profile-mem": false
|
||||
},
|
||||
"fail-on-severity": "",
|
||||
"registry": {
|
||||
"insecure-skip-tls-verify": false,
|
||||
"insecure-use-http": false,
|
||||
"auth": []
|
||||
},
|
||||
"log": {
|
||||
"structured": false,
|
||||
"level": "",
|
||||
"file": ""
|
||||
}
|
||||
},
|
||||
"db": {
|
||||
"built": "2022-05-15T08:15:19Z",
|
||||
"schemaVersion": 3,
|
||||
"location": "/home/jim/.cache/grype/db/3",
|
||||
"checksum": "sha256:4e6836ac8db4fbe1488c6a81f37cdc044e3d22041573c7a552aa0e053efa5c29",
|
||||
"error": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
func Test_Conditions(t *testing.T) {
|
||||
|
||||
conditions := []v1.AnyAllConditions{
|
||||
{
|
||||
AnyConditions: []v1.Condition{
|
||||
{
|
||||
RawKey: &apiextv1.JSON{Raw: []byte("\"{{ matches[].vulnerability[].cvss[?metrics.impactScore > '8.0'][] | length(@) }}\"")},
|
||||
Operator: "Equals",
|
||||
RawValue: &apiextv1.JSON{Raw: []byte("\"0\"")},
|
||||
},
|
||||
{
|
||||
RawKey: &apiextv1.JSON{Raw: []byte("\"{{ source.target.userInput }}\"")},
|
||||
Operator: "Equals",
|
||||
RawValue: &apiextv1.JSON{Raw: []byte("\"ghcr.io/tap8stry/git-init:v0.21.0@sha256:322e3502c1e6fba5f1869efb55cfd998a3679e073840d33eb0e3c482b5d5609b\"")},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.NewContext()
|
||||
img := api.ImageInfo{Pointer: "/spec/containers/0/image"}
|
||||
img.ImageInfo = image.ImageInfo{
|
||||
Registry: "docker.io",
|
||||
Name: "nginx",
|
||||
Path: "test/nginx",
|
||||
Tag: "latest",
|
||||
}
|
||||
|
||||
var dataMap map[string]interface{}
|
||||
err := json.Unmarshal([]byte(scanPredicate), &dataMap)
|
||||
assert.NilError(t, err)
|
||||
|
||||
pass, err := evaluateConditions(conditions, ctx, dataMap, img, log.Log)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, pass, true)
|
||||
}
|
|
@ -163,17 +163,14 @@ func (iv *imageVerifier) verify(imageVerify kyvernov1.ImageVerification, images
|
|||
continue
|
||||
}
|
||||
|
||||
var ruleResp *response.RuleResponse
|
||||
var digest string
|
||||
|
||||
if len(imageVerify.Attestors) > 0 {
|
||||
if len(imageVerify.Attestations) > 0 {
|
||||
ruleResp = iv.verifyAttestations(imageVerify, imageInfo)
|
||||
} else {
|
||||
ruleResp, digest = iv.verifySignatures(imageVerify, imageInfo)
|
||||
}
|
||||
verified, err := isImageVerified(iv.policyContext.NewResource, image, iv.logger)
|
||||
if err == nil && verified {
|
||||
iv.logger.Info("image was previously verified, skipping check", "image", image)
|
||||
continue
|
||||
}
|
||||
|
||||
ruleResp, digest := iv.verifyImage(imageVerify, imageInfo)
|
||||
|
||||
if imageVerify.MutateDigest {
|
||||
patch, retrievedDigest, err := iv.handleMutateDigest(digest, imageInfo)
|
||||
if err != nil {
|
||||
|
@ -261,15 +258,20 @@ func imageMatches(image string, imagePatterns []string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (iv *imageVerifier) verifySignatures(imageVerify kyvernov1.ImageVerification, imageInfo apiutils.ImageInfo) (*response.RuleResponse, string) {
|
||||
image := imageInfo.String()
|
||||
iv.logger.V(2).Info("verifying image signatures", "image", image, "attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations))
|
||||
func (iv *imageVerifier) verifyImage(imageVerify kyvernov1.ImageVerification, imageInfo apiutils.ImageInfo) (*response.RuleResponse, string) {
|
||||
if len(imageVerify.Attestors) <= 0 {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
var digest string
|
||||
image := imageInfo.String()
|
||||
iv.logger.V(2).Info("verifying image signatures", "image", image,
|
||||
"attestors", len(imageVerify.Attestors), "attestations", len(imageVerify.Attestations))
|
||||
|
||||
var cosignResponse *cosign.Response
|
||||
for i, attestorSet := range imageVerify.Attestors {
|
||||
var err error
|
||||
path := fmt.Sprintf(".attestors[%d]", i)
|
||||
digest, err = iv.verifyAttestorSet(attestorSet, imageVerify, image, path)
|
||||
cosignResponse, err = iv.verifyAttestorSet(attestorSet, imageVerify, imageInfo, path)
|
||||
if err != nil {
|
||||
iv.logger.Error(err, "failed to verify signature")
|
||||
msg := fmt.Sprintf("failed to verify signature for %s: %s", image, err.Error())
|
||||
|
@ -277,19 +279,26 @@ func (iv *imageVerifier) verifySignatures(imageVerify kyvernov1.ImageVerificatio
|
|||
}
|
||||
}
|
||||
|
||||
if cosignResponse == nil {
|
||||
return ruleError(iv.rule, response.ImageVerify, "invalid response", fmt.Errorf("nil")), ""
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("verified image signatures for %s", image)
|
||||
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil), digest
|
||||
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil), cosignResponse.Digest
|
||||
}
|
||||
|
||||
func (iv *imageVerifier) verifyAttestorSet(attestorSet kyvernov1.AttestorSet, imageVerify kyvernov1.ImageVerification, image, path string) (string, error) {
|
||||
func (iv *imageVerifier) verifyAttestorSet(attestorSet kyvernov1.AttestorSet, imageVerify kyvernov1.ImageVerification,
|
||||
imageInfo apiutils.ImageInfo, path string) (*cosign.Response, error) {
|
||||
|
||||
var errorList []error
|
||||
verifiedCount := 0
|
||||
attestorSet = expandStaticKeys(attestorSet)
|
||||
requiredCount := getRequiredCount(attestorSet)
|
||||
image := imageInfo.String()
|
||||
|
||||
for i, a := range attestorSet.Entries {
|
||||
var digest string
|
||||
var entryError error
|
||||
var cosignResp *cosign.Response
|
||||
attestorPath := fmt.Sprintf("%s.entries[%d]", path, i)
|
||||
|
||||
if a.Attestor != nil {
|
||||
|
@ -298,11 +307,15 @@ func (iv *imageVerifier) verifyAttestorSet(attestorSet kyvernov1.AttestorSet, im
|
|||
entryError = errors.Wrapf(err, "failed to unmarshal nested attestor %s", attestorPath)
|
||||
} else {
|
||||
attestorPath += ".attestor"
|
||||
digest, entryError = iv.verifyAttestorSet(*nestedAttestorSet, imageVerify, image, attestorPath)
|
||||
cosignResp, entryError = iv.verifyAttestorSet(*nestedAttestorSet, imageVerify, imageInfo, attestorPath)
|
||||
}
|
||||
} else {
|
||||
opts, subPath := iv.buildOptionsAndPath(a, imageVerify, image)
|
||||
digest, entryError = cosign.VerifySignature(*opts)
|
||||
cosignResp, entryError = cosign.Verify(*opts)
|
||||
if opts.FetchAttestations && entryError == nil {
|
||||
entryError = iv.verifyAttestations(cosignResp.Statements, imageVerify, imageInfo)
|
||||
}
|
||||
|
||||
if entryError != nil {
|
||||
entryError = fmt.Errorf("%s: %s", attestorPath+subPath, entryError.Error())
|
||||
}
|
||||
|
@ -312,7 +325,7 @@ func (iv *imageVerifier) verifyAttestorSet(attestorSet kyvernov1.AttestorSet, im
|
|||
verifiedCount++
|
||||
if verifiedCount >= requiredCount {
|
||||
iv.logger.V(2).Info("image verification succeeded", "verifiedCount", verifiedCount, "requiredCount", requiredCount)
|
||||
return digest, nil
|
||||
return cosignResp, nil
|
||||
}
|
||||
} else {
|
||||
errorList = append(errorList, entryError)
|
||||
|
@ -321,7 +334,7 @@ func (iv *imageVerifier) verifyAttestorSet(attestorSet kyvernov1.AttestorSet, im
|
|||
|
||||
iv.logger.Info("image verification failed", "verifiedCount", verifiedCount, "requiredCount", requiredCount, "errors", errorList)
|
||||
err := engineUtils.CombineErrors(errorList)
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func expandStaticKeys(attestorSet kyvernov1.AttestorSet) kyvernov1.AttestorSet {
|
||||
|
@ -388,6 +401,10 @@ func (iv *imageVerifier) buildOptionsAndPath(attestor kyvernov1.Attestor, imageV
|
|||
opts.Roots = imageVerify.Roots
|
||||
}
|
||||
|
||||
if len(imageVerify.Attestations) > 0 {
|
||||
opts.FetchAttestations = true
|
||||
}
|
||||
|
||||
if attestor.Keys != nil {
|
||||
path = path + ".keys"
|
||||
opts.Key = attestor.Keys.PublicKeys
|
||||
|
@ -432,46 +449,39 @@ func makeAddDigestPatch(imageInfo apiutils.ImageInfo, digest string) ([]byte, er
|
|||
return json.Marshal(patch)
|
||||
}
|
||||
|
||||
func (iv *imageVerifier) verifyAttestations(imageVerify kyvernov1.ImageVerification, imageInfo apiutils.ImageInfo) *response.RuleResponse {
|
||||
func (iv *imageVerifier) verifyAttestations(statements []map[string]interface{}, imageVerify kyvernov1.ImageVerification, imageInfo apiutils.ImageInfo) error {
|
||||
image := imageInfo.String()
|
||||
start := time.Now()
|
||||
|
||||
statements, err := cosign.FetchAttestations(image, imageVerify)
|
||||
if err != nil {
|
||||
iv.logger.Info("failed to fetch attestations", "image", image, "error", err, "duration", time.Since(start).Seconds())
|
||||
return ruleError(iv.rule, response.ImageVerify, fmt.Sprintf("failed to fetch attestations for %s", image), err)
|
||||
}
|
||||
|
||||
iv.logger.V(4).Info("received attestations", "count", len(statements))
|
||||
statementsByPredicate := buildStatementMap(statements)
|
||||
statementsByPredicate, types := buildStatementMap(statements)
|
||||
iv.logger.V(4).Info("checking attestations", "predicates", types, "image", image)
|
||||
|
||||
for _, ac := range imageVerify.Attestations {
|
||||
statements := statementsByPredicate[ac.PredicateType]
|
||||
if statements == nil {
|
||||
msg := fmt.Sprintf("predicate type %s not found", ac.PredicateType)
|
||||
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil)
|
||||
iv.logger.Info("attestation predicate type %s not found", "predicates", types, "image", imageInfo.String())
|
||||
return fmt.Errorf("predicate type %s not found", ac.PredicateType)
|
||||
}
|
||||
|
||||
iv.logger.Info("checking attestation predicate type %s", "predicates", types, "image", imageInfo.String())
|
||||
|
||||
for _, s := range statements {
|
||||
val, err := iv.checkAttestations(ac, s, imageInfo)
|
||||
if err != nil {
|
||||
return ruleError(iv.rule, response.ImageVerify, "failed to check attestation", err)
|
||||
return errors.Wrap(err, "failed to check attestations")
|
||||
}
|
||||
|
||||
if !val {
|
||||
msg := fmt.Sprintf("attestation checks failed for %s and predicate %s", imageInfo.String(), ac.PredicateType)
|
||||
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil)
|
||||
return fmt.Errorf("attestation checks failed for %s and predicate %s", imageInfo.String(), ac.PredicateType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("attestation checks passed for %s", imageInfo.String())
|
||||
iv.logger.V(2).Info(msg)
|
||||
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil)
|
||||
iv.logger.V(3).Info("attestation checks passed for %s", imageInfo.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildStatementMap(statements []map[string]interface{}) map[string][]map[string]interface{} {
|
||||
func buildStatementMap(statements []map[string]interface{}) (map[string][]map[string]interface{}, []string) {
|
||||
results := map[string][]map[string]interface{}{}
|
||||
var predicateTypes []string
|
||||
for _, s := range statements {
|
||||
predicateType := s["predicateType"].(string)
|
||||
if results[predicateType] != nil {
|
||||
|
@ -479,9 +489,11 @@ func buildStatementMap(statements []map[string]interface{}) map[string][]map[str
|
|||
} else {
|
||||
results[predicateType] = []map[string]interface{}{s}
|
||||
}
|
||||
|
||||
predicateTypes = append(predicateTypes, predicateType)
|
||||
}
|
||||
|
||||
return results
|
||||
return results, predicateTypes
|
||||
}
|
||||
|
||||
func (iv *imageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string]interface{}, img apiutils.ImageInfo) (bool, error) {
|
||||
|
@ -492,24 +504,34 @@ func (iv *imageVerifier) checkAttestations(a kyvernov1.Attestation, s map[string
|
|||
iv.policyContext.JSONContext.Checkpoint()
|
||||
defer iv.policyContext.JSONContext.Restore()
|
||||
|
||||
return evaluateConditions(a.Conditions, iv.policyContext.JSONContext, s, img, iv.logger)
|
||||
}
|
||||
|
||||
func evaluateConditions(
|
||||
conditions []kyvernov1.AnyAllConditions,
|
||||
ctx context.Interface,
|
||||
s map[string]interface{},
|
||||
img apiutils.ImageInfo,
|
||||
log logr.Logger) (bool, error) {
|
||||
|
||||
predicate, ok := s["predicate"].(map[string]interface{})
|
||||
if !ok {
|
||||
return false, fmt.Errorf("failed to extract predicate from statement: %v", s)
|
||||
}
|
||||
|
||||
if err := context.AddJSONObject(iv.policyContext.JSONContext, predicate); err != nil {
|
||||
if err := context.AddJSONObject(ctx, predicate); err != nil {
|
||||
return false, errors.Wrapf(err, fmt.Sprintf("failed to add Statement to the context %v", s))
|
||||
}
|
||||
|
||||
if err := iv.policyContext.JSONContext.AddImageInfo(img); err != nil {
|
||||
if err := ctx.AddImageInfo(img); err != nil {
|
||||
return false, errors.Wrapf(err, fmt.Sprintf("failed to add image to the context %v", s))
|
||||
}
|
||||
|
||||
conditions, err := variables.SubstituteAllInConditions(iv.logger, iv.policyContext.JSONContext, a.Conditions)
|
||||
c, err := variables.SubstituteAllInConditions(log, ctx, conditions)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "failed to substitute variables in attestation conditions")
|
||||
}
|
||||
|
||||
pass := variables.EvaluateAnyAllConditions(iv.logger, iv.policyContext.JSONContext, conditions)
|
||||
pass := variables.EvaluateAnyAllConditions(log, ctx, c)
|
||||
return pass, nil
|
||||
}
|
||||
|
|
|
@ -52,12 +52,15 @@ func evaluateAnyAllConditions(log logr.Logger, ctx context.EvalInterface, condit
|
|||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("no condition passed for 'any' block", "any", anyConditions)
|
||||
}
|
||||
|
||||
// update the allConditionsResult if they are present
|
||||
for _, condition := range allConditions {
|
||||
if !Evaluate(log, ctx, condition) {
|
||||
allConditionsResult = false
|
||||
log.Info("a condition failed in 'all' block", "condition", condition)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue