Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bundle verifier #53

Closed
wants to merge 12 commits into from
Prev Previous commit
Next Next commit
Implement oci.Signature interface
Signed-off-by: Cody Soyland <codysoyland@github.com>
  • Loading branch information
codysoyland committed May 10, 2024
commit 15797be5e2bdfc74925e3ed44de9b3d5fc31b37c
58 changes: 5 additions & 53 deletions pkg/webhook/validator.go
Original file line number Diff line number Diff line change
@@ -885,6 +885,10 @@ func ValidatePolicyAttestationsForAuthority(ctx context.Context, ref name.Refere
}
logging.FromContext(ctx).Debugf("Found %d valid attestations, validating policies for them", len(verifiedAttestations))

return checkPredicates(ctx, authority, verifiedAttestations)
}

func checkPredicates(ctx context.Context, authority webhookcip.Authority, verifiedAttestations []oci.Signature) (map[string][]PolicyAttestation, error) {
// Now spin through the Attestations that the user specified and validate
// them.
// TODO(vaikas): Pretty inefficient here, figure out a better way if
@@ -1022,59 +1026,7 @@ func ValidatePolicyAttestationsForAuthorityWithBundle(ctx context.Context, ref n
return nil, errors.New("no verified bundles found")
}

// TODO: loop through all verifiedBundles instead of using just the one
bundle := verifiedBundles[0].Bundle
result := verifiedBundles[0].Result

statementBytes, err := json.Marshal(result.Statement)
if err != nil {
return nil, err
}

// sha256 of statement
statementDigest := sha256.Sum256(statementBytes)

// TODO: generate "signature ID" from the signature?
sig := string(bundle.GetDsseEnvelope().Signatures[0].Sig)

ret := make(map[string][]PolicyAttestation, 1)
pa := PolicyAttestation{
PolicySignature: PolicySignature{
ID: sig,
Subject: result.VerifiedIdentity.SubjectAlternativeName.Value,
Issuer: result.VerifiedIdentity.Issuer,
GithubExtensions: GithubExtensions{
WorkflowTrigger: result.VerifiedIdentity.Extensions.GithubWorkflowTrigger,
WorkflowSHA: result.VerifiedIdentity.Extensions.GithubWorkflowSHA,
WorkflowName: result.VerifiedIdentity.Extensions.GithubWorkflowName,
WorkflowRepo: result.VerifiedIdentity.Extensions.GithubWorkflowRepository,
WorkflowRef: result.VerifiedIdentity.Extensions.GithubWorkflowRef,
},
},
PredicateType: result.Statement.PredicateType,
Payload: statementBytes,
Digest: string(statementDigest[:]),
}

// TODO: support more than one attestation
att := authority.Attestations[0]
if att.PredicateType != result.Statement.PredicateType {
return nil, fmt.Errorf("predicate type mismatch: %s != %s", att.PredicateType, result.Statement.PredicateType)
}
ret[att.Name] = []PolicyAttestation{pa}

if att.Type != "" {
warn, err := policy.EvaluatePolicyAgainstJSON(ctx, att.Name, att.Type, att.Data, statementBytes)
if err != nil || warn != nil {
logging.FromContext(ctx).Warnf("failed policy validation for %s: %v", att.Name, err)
if err != nil {
return nil, err
}
return nil, warn
}
}

return ret, nil
return checkPredicates(ctx, authority, verifiedBundles)
}

// ResolvePodScalable implements policyduckv1beta1.PodScalableValidator
97 changes: 88 additions & 9 deletions pkg/webhook/verify/verify.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package verify

import (
"crypto/x509"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
@@ -10,41 +13,117 @@ import (
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"

"github.com/google/go-containerregistry/pkg/v1/types"
cbundle "github.com/sigstore/cosign/v2/pkg/cosign/bundle"
"github.com/sigstore/cosign/v2/pkg/oci"
"github.com/sigstore/sigstore-go/pkg/bundle"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/verify"
)

type VerifiedBundle struct {
Bundle *bundle.ProtobufBundle
Result *verify.VerificationResult
SGBundle *bundle.ProtobufBundle
Result *verify.VerificationResult
Hash v1.Hash
}

func AttestationBundles(ref name.Reference, trustedMaterial root.TrustedMaterial, remoteOpts []remote.Option, policyOptions []verify.PolicyOption) ([]VerifiedBundle, error) {
// VerifiedBundle implements oci.Signature
var _ oci.Signature = &VerifiedBundle{}

func (vb *VerifiedBundle) Digest() (v1.Hash, error) {
return vb.Hash, nil
}

func (vb *VerifiedBundle) DiffID() (v1.Hash, error) {
panic("implement me")
}

func (vb *VerifiedBundle) Compressed() (io.ReadCloser, error) {
panic("implement me")
}

func (vb *VerifiedBundle) Uncompressed() (io.ReadCloser, error) {
panic("implement me")
}

func (vb *VerifiedBundle) Size() (int64, error) {
panic("implement me")
}

func (vb *VerifiedBundle) MediaType() (types.MediaType, error) {
panic("implement me")
}

func (vb *VerifiedBundle) Annotations() (map[string]string, error) {
panic("implement me")
}

func (vb *VerifiedBundle) Payload() ([]byte, error) {
// todo: this should return the json-serialized dsse envelope
envelope := vb.SGBundle.GetDsseEnvelope()
if envelope == nil {
return nil, fmt.Errorf("no dsse envelope found")
}
return json.Marshal(envelope)
}

func (vb *VerifiedBundle) Signature() ([]byte, error) {
// TODO: implement this
return []byte{}, nil
}

func (vb *VerifiedBundle) Base64Signature() (string, error) {
panic("implement me")
}

func (vb *VerifiedBundle) Cert() (*x509.Certificate, error) {
vc, err := vb.SGBundle.VerificationContent()
if err != nil {
return nil, err
}
if cert, ok := vc.HasCertificate(); ok {
return &cert, nil
}
return nil, errors.New("bundle does not contain a certificate")
}

func (vb *VerifiedBundle) Chain() ([]*x509.Certificate, error) {
panic("implement me")
}

func (vb *VerifiedBundle) Bundle() (*cbundle.RekorBundle, error) {
panic("implement me")
}

func (vb *VerifiedBundle) RFC3161Timestamp() (*cbundle.RFC3161Timestamp, error) {
panic("implement me")
}

func AttestationBundles(ref name.Reference, trustedMaterial root.TrustedMaterial, remoteOpts []remote.Option, policyOptions []verify.PolicyOption) ([]oci.Signature, error) {
verifierConfig := []verify.VerifierOption{verify.WithObserverTimestamps(1)}
sev, err := verify.NewSignedEntityVerifier(trustedMaterial, verifierConfig...)
if err != nil {
return nil, err
}

bundles, imageDigest, err := getBundles(ref, remoteOpts)
bundles, hash, err := getBundles(ref, remoteOpts)
if err != nil {
return nil, err
}

digestBytes, err := hex.DecodeString(imageDigest.Hex)
digestBytes, err := hex.DecodeString(hash.Hex)
if err != nil {
return nil, err
}
artifactPolicy := verify.WithArtifactDigest(imageDigest.Algorithm, digestBytes)
artifactPolicy := verify.WithArtifactDigest(hash.Algorithm, digestBytes)
policy := verify.NewPolicy(artifactPolicy, policyOptions...)

verifiedBundles := make([]VerifiedBundle, 0)
verifiedBundles := make([]oci.Signature, 0)
for _, b := range bundles {
// TODO: should these be done in parallel? (as is done in cosign?)
result, err := sev.Verify(b, policy)
if err == nil {
verifiedBundles = append(verifiedBundles, VerifiedBundle{Bundle: b, Result: result})
verifiedBundles = append(verifiedBundles, &VerifiedBundle{SGBundle: b, Result: result, Hash: *hash})
}
}
return verifiedBundles, nil
@@ -70,7 +149,7 @@ func getBundles(ref name.Reference, remoteOpts []remote.Option) ([]*bundle.Proto
bundles := make([]*bundle.ProtobufBundle, 0)

for _, refDesc := range refManifest.Manifests {
if !strings.HasPrefix(refDesc.ArtifactType, "application/vnd.dev.sigstore.bundle+json") {
if !strings.HasPrefix(refDesc.ArtifactType, "application/vnd.dev.sigstore.bundle") {
continue
}