mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
373f942ea9
* fix: Allow images to be pulled from insecure registry when allowInsecureRegistry flag is set to true (#10934) Signed-off-by: Pradeep Lakshmi Narasimha <pradeep.vaishnav4@gmail.com> * Update pkg/registryclient/client.go Signed-off-by: Vishal Choudhary <vishal.chdhry.work@gmail.com> --------- Signed-off-by: Pradeep Lakshmi Narasimha <pradeep.vaishnav4@gmail.com> Signed-off-by: Vishal Choudhary <vishal.chdhry.work@gmail.com> Co-authored-by: Vishal Choudhary <vishal.choudhary@nirmata.com> Co-authored-by: Vishal Choudhary <vishal.chdhry.work@gmail.com>
247 lines
7.4 KiB
Go
247 lines
7.4 KiB
Go
package cosign
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/remote"
|
|
"github.com/in-toto/in-toto-golang/in_toto"
|
|
"github.com/kyverno/kyverno/pkg/images"
|
|
"github.com/kyverno/kyverno/pkg/utils/data"
|
|
"github.com/pkg/errors"
|
|
"github.com/sigstore/sigstore-go/pkg/bundle"
|
|
"github.com/sigstore/sigstore-go/pkg/root"
|
|
"github.com/sigstore/sigstore-go/pkg/verify"
|
|
"github.com/sigstore/sigstore/pkg/tuf"
|
|
)
|
|
|
|
var (
|
|
maxLayerSize = int64(10 * 1000 * 1000) // 10 MB
|
|
attestationlimit = 50
|
|
)
|
|
|
|
type VerificationResult struct {
|
|
Bundle *Bundle
|
|
Result *verify.VerificationResult
|
|
Desc *v1.Descriptor
|
|
}
|
|
|
|
type Bundle struct {
|
|
ProtoBundle *bundle.Bundle
|
|
DSSE_Envelope *in_toto.Statement //nolint:staticcheck
|
|
}
|
|
|
|
func verifyBundleAndFetchAttestations(ctx context.Context, opts images.Options) ([]*VerificationResult, error) {
|
|
nameOpts := opts.Client.NameOptions()
|
|
ref, err := name.ParseReference(opts.ImageRef, nameOpts...)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to parse image reference: %v", opts.ImageRef)
|
|
}
|
|
|
|
remoteOpts, err := opts.Client.Options(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create remote opts: %v", opts.ImageRef)
|
|
}
|
|
|
|
bundles, desc, err := fetchBundles(ref, attestationlimit, opts.Type, remoteOpts)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to fetch bundles: %v", opts.ImageRef)
|
|
}
|
|
|
|
policy, err := buildPolicy(desc, opts)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to build policy: %v", opts.ImageRef)
|
|
}
|
|
|
|
verifyOpts := buildVerifyOptions(opts)
|
|
trustedMaterial, err := getTrustedRoot(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get trusted root: %v", opts.ImageRef)
|
|
}
|
|
|
|
results, err := verifyBundles(bundles, desc, trustedMaterial, policy, verifyOpts)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get verify bundles: %v", opts.ImageRef)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func verifyBundles(bundles []*Bundle, desc *v1.Descriptor, trustedRoot *root.TrustedRoot, policy verify.PolicyBuilder, verifierOpts []verify.VerifierOption) ([]*VerificationResult, error) {
|
|
verifier, err := verify.NewSignedEntityVerifier(trustedRoot, verifierOpts...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
verificationResults := make([]*VerificationResult, 0)
|
|
for _, bundle := range bundles {
|
|
result, err := verifier.Verify(bundle.ProtoBundle, policy)
|
|
if err == nil {
|
|
verificationResults = append(verificationResults, &VerificationResult{Bundle: bundle, Result: result, Desc: desc})
|
|
} else {
|
|
logger.V(4).Info("failed to verify sigstore bundle", "err", err.Error(), "bundle", bundle)
|
|
}
|
|
}
|
|
|
|
return verificationResults, nil
|
|
}
|
|
|
|
func fetchBundles(ref name.Reference, limit int, predicateType string, remoteOpts []remote.Option) ([]*Bundle, *v1.Descriptor, error) {
|
|
bundles := make([]*Bundle, 0)
|
|
|
|
desc, err := remote.Head(ref, remoteOpts...)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
referrers, err := remote.Referrers(ref.Context().Digest(desc.Digest.String()), remoteOpts...)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
referrersDescs, err := referrers.IndexManifest()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if len(referrersDescs.Manifests) > limit {
|
|
return nil, nil, fmt.Errorf("failed to fetch referrers: to many referrers found, max limit is %d", limit)
|
|
}
|
|
|
|
for _, manifestDesc := range referrersDescs.Manifests {
|
|
if !strings.HasPrefix(manifestDesc.ArtifactType, "application/vnd.dev.sigstore.bundle") {
|
|
continue
|
|
}
|
|
|
|
refImg, err := remote.Image(ref.Context().Digest(manifestDesc.Digest.String()), remoteOpts...)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to fetch referrer image: %w", err)
|
|
}
|
|
layers, err := refImg.Layers()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to fetch referrer layer: %w", err)
|
|
}
|
|
if len(layers) == 0 {
|
|
return nil, nil, fmt.Errorf("layers not found")
|
|
}
|
|
layer := layers[0]
|
|
layerSize, err := layer.Size()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if layerSize > maxLayerSize {
|
|
return nil, nil, fmt.Errorf("layer size %d exceeds %d", layerSize, maxLayerSize)
|
|
}
|
|
layerBytes, err := layer.Uncompressed()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to fetch referrer layer: %w", err)
|
|
}
|
|
bundleBytes, err := io.ReadAll(layerBytes)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to fetch referrer layer: %w", err)
|
|
}
|
|
b := &bundle.Bundle{}
|
|
err = b.UnmarshalJSON(bundleBytes)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to unmarshal bundle: %w", err)
|
|
}
|
|
bundles = append(bundles, &Bundle{ProtoBundle: b})
|
|
}
|
|
|
|
if predicateType != "" {
|
|
filteredBundles := make([]*Bundle, 0)
|
|
for _, b := range bundles {
|
|
dsseEnvelope := b.ProtoBundle.Bundle.GetDsseEnvelope()
|
|
if dsseEnvelope != nil {
|
|
if dsseEnvelope.PayloadType != "application/vnd.in-toto+json" {
|
|
continue
|
|
}
|
|
var intotoStatement in_toto.Statement //nolint:staticcheck
|
|
if err := json.Unmarshal(dsseEnvelope.Payload, &intotoStatement); err != nil {
|
|
continue
|
|
}
|
|
|
|
if intotoStatement.PredicateType == predicateType {
|
|
filteredBundles = append(filteredBundles, &Bundle{
|
|
ProtoBundle: b.ProtoBundle,
|
|
DSSE_Envelope: &intotoStatement,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
return filteredBundles, desc, nil
|
|
}
|
|
|
|
return bundles, desc, nil
|
|
}
|
|
|
|
func buildPolicy(desc *v1.Descriptor, opts images.Options) (verify.PolicyBuilder, error) {
|
|
digest, err := hex.DecodeString(desc.Digest.Hex)
|
|
if err != nil {
|
|
return verify.PolicyBuilder{}, err
|
|
}
|
|
artifactDigestVerificationOption := verify.WithArtifactDigest(desc.Digest.Algorithm, digest)
|
|
|
|
id, err := verify.NewShortCertificateIdentity(opts.Issuer, opts.IssuerRegExp, opts.Subject, opts.SubjectRegExp)
|
|
if err != nil {
|
|
return verify.PolicyBuilder{}, err
|
|
}
|
|
return verify.NewPolicy(artifactDigestVerificationOption, verify.WithCertificateIdentity(id)), nil
|
|
}
|
|
|
|
func buildVerifyOptions(opts images.Options) []verify.VerifierOption {
|
|
var verifierOptions []verify.VerifierOption
|
|
if !opts.IgnoreTlog {
|
|
verifierOptions = append(verifierOptions, verify.WithTransparencyLog(1))
|
|
}
|
|
|
|
if !opts.IgnoreSCT {
|
|
verifierOptions = append(verifierOptions, verify.WithObserverTimestamps(1))
|
|
}
|
|
|
|
return verifierOptions
|
|
}
|
|
|
|
func getTrustedRoot(ctx context.Context) (*root.TrustedRoot, error) {
|
|
tufClient, err := tuf.NewFromEnv(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("initializing tuf: %w", err)
|
|
}
|
|
targetBytes, err := tufClient.GetTarget("trusted_root.json")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting targets: %w", err)
|
|
}
|
|
trustedRoot, err := root.NewTrustedRootFromJSON(targetBytes)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error creating trusted root: %w", err)
|
|
}
|
|
|
|
return trustedRoot, nil
|
|
}
|
|
|
|
func decodeStatementsFromBundles(bundles []*VerificationResult) ([]map[string]interface{}, error) {
|
|
if len(bundles) == 0 {
|
|
return []map[string]interface{}{}, nil
|
|
}
|
|
|
|
var err error
|
|
var statement map[string]interface{}
|
|
var intotostatement in_toto.Statement //nolint:staticcheck
|
|
decodedStatements := make([]map[string]interface{}, len(bundles))
|
|
for i, b := range bundles {
|
|
intotostatement = *b.Bundle.DSSE_Envelope
|
|
statement, err = data.ToMap(intotostatement)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to decode statement: %v", intotostatement.Type)
|
|
}
|
|
statement["type"] = intotostatement.PredicateType
|
|
decodedStatements[i] = statement
|
|
}
|
|
return decodedStatements, nil
|
|
}
|