Skip to content

Commit

Permalink
Add support for Sigstore Bundles using sigstore-go verifier (#151)
Browse files Browse the repository at this point in the history
* Remove dependabot for this fork (#159)

* Add Actions release and attest job (#147)

* update release workflow

Signed-off-by: Meredith Lancaster <[email protected]>

* Grab image digest for attestation step

Signed-off-by: Meredith Lancaster <[email protected]>

* comment

Signed-off-by: Meredith Lancaster <[email protected]>

* update workflow name

Signed-off-by: Meredith Lancaster <[email protected]>

* add release directions

Signed-off-by: Meredith Lancaster <[email protected]>

* undo ko config changes

Signed-off-by: Meredith Lancaster <[email protected]>

* add fork specific options to ko build call

Signed-off-by: Meredith Lancaster <[email protected]>

* Change version format

---------

Signed-off-by: Meredith Lancaster <[email protected]>
Co-authored-by: Cody Soyland <[email protected]>

* set release as target branch (#161)

Signed-off-by: Meredith Lancaster <[email protected]>

* Add support for Sigstore Bundles using sigstore-go verifier

Signed-off-by: Cody Soyland <[email protected]>

* Update docs

Signed-off-by: Cody Soyland <[email protected]>

* Rename func

Signed-off-by: Cody Soyland <[email protected]>

* Comment on observe timestamp setting

Signed-off-by: Cody Soyland <[email protected]>

* Refactor trusted material, add support for default TUF repo in bundle verifier

Signed-off-by: Cody Soyland <[email protected]>

* Remove accidental code

Signed-off-by: Cody Soyland <[email protected]>

* Fix tlog verification options

Signed-off-by: Cody Soyland <[email protected]>

---------

Signed-off-by: Meredith Lancaster <[email protected]>
Signed-off-by: Cody Soyland <[email protected]>
Co-authored-by: Meredith Lancaster <[email protected]>
  • Loading branch information
codysoyland and malancas committed Nov 18, 2024
1 parent 6beed9d commit 2f7018e
Show file tree
Hide file tree
Showing 16 changed files with 729 additions and 14 deletions.
6 changes: 6 additions & 0 deletions config/300-clusterimagepolicy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ spec:
trustRootRef:
description: Use the Certificate Chain from the referred TrustRoot.TimeStampAuthorities
type: string
signatureFormat:
description: SignatureFormat specifies the format the authority expects. Supported formats are "simplesigning" and "bundle". If not specified, the default is "simplesigning" (cosign's default).
type: string
source:
description: Sources sets the configuration to specify the sources from where to consume the signatures.
type: array
Expand Down Expand Up @@ -545,6 +548,9 @@ spec:
trustRootRef:
description: Use the Certificate Chain from the referred TrustRoot.TimeStampAuthorities
type: string
signatureFormat:
description: SignatureFormat specifies the format the authority expects. Supported formats are "simplesigning" and "bundle". If not specified, the default is "simplesigning" (cosign's default).
type: string
source:
description: Sources sets the configuration to specify the sources from where to consume the signatures.
type: array
Expand Down
1 change: 1 addition & 0 deletions docs/api-types/index-v1alpha1.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ Attestation defines the type of attestation to validate and optionally apply a p
| ctlog | CTLog sets the configuration to verify the authority against a Rekor instance. | [TLog](#tlog) | false |
| attestations | Attestations is a list of individual attestations for this authority, once the signature for this authority has been verified. | [][Attestation](#attestation) | false |
| rfc3161timestamp | RFC3161Timestamp sets the configuration to verify the signature timestamp against a RFC3161 time-stamping instance. | [RFC3161Timestamp](#rfc3161timestamp) | false |
| signatureFormat | SignatureFormat specifies the format the authority expects. Supported formats are \"simplesigning\" and \"bundle\". If not specified, the default is \"simplesigning\" (cosign's default). | string | false |

[Back to TOC](#table-of-contents)

Expand Down
1 change: 1 addition & 0 deletions docs/api-types/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ The authorities block defines the rules for discovering and validating signature
| ctlog | CTLog sets the configuration to verify the authority against a Rekor instance. | [TLog](#tlog) | false |
| attestations | Attestations is a list of individual attestations for this authority, once the signature for this authority has been verified. | [][Attestation](#attestation) | false |
| rfc3161timestamp | RFC3161Timestamp sets the configuration to verify the signature timestamp against a RFC3161 time-stamping instance. | [RFC3161Timestamp](#rfc3161timestamp) | false |
| signatureFormat | SignatureFormat specifies the format the authority expects. Supported formats are \"simplesigning\" and \"bundle\". If not specified, the default is \"simplesigning\" (cosign's default). | string | false |

[Back to TOC](#table-of-contents)

Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ require (
github.com/go-jose/go-jose/v4 v4.0.4
github.com/sigstore/protobuf-specs v0.3.2
github.com/sigstore/scaffolding v0.7.11
github.com/sigstore/sigstore-go v0.6.2
github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.10
github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.10
github.com/sigstore/sigstore/pkg/signature/kms/gcp v1.8.10
Expand Down Expand Up @@ -193,6 +194,7 @@ require (
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect
github.com/hashicorp/vault/api v1.15.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/in-toto/attestation v1.1.0 // indirect
github.com/in-toto/in-toto-golang v0.9.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 // indirect
Expand Down Expand Up @@ -231,7 +233,6 @@ require (
github.com/sassoftware/relic v7.2.1+incompatible // indirect
github.com/secure-systems-lab/go-securesystemslib v0.8.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/sigstore/sigstore-go v0.6.2 // indirect
github.com/sigstore/timestamp-authority v1.2.2 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,8 @@ github.com/go-piv/piv-go v1.11.0 h1:5vAaCdRTFSIW4PeqMbnsDlUZ7odMYWnHBDGdmtU/Zhg=
github.com/go-piv/piv-go v1.11.0/go.mod h1:NZ2zmjVkfFaL/CF8cVQ/pXdXtuj110zEKGdJM6fJZZM=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
Expand Down Expand Up @@ -534,6 +536,8 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM=
github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hashicorp/vault/api v1.15.0 h1:O24FYQCWwhwKnF7CuSqP30S51rTV7vz1iACXE/pj5DA=
Expand Down Expand Up @@ -747,6 +751,8 @@ github.com/secure-systems-lab/go-securesystemslib v0.8.0 h1:mr5An6X45Kb2nddcFlbm
github.com/secure-systems-lab/go-securesystemslib v0.8.0/go.mod h1:UH2VZVuJfCYR8WgMlCU1uFsOUU+KeyrTWcSS73NBOzU=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI=
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
github.com/sigstore/cosign/v2 v2.4.1 h1:b8UXEfJFks3hmTwyxrRNrn6racpmccUycBHxDMkEPvU=
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/policy/v1alpha1/clusterimagepolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ type Authority struct {
// RFC3161Timestamp sets the configuration to verify the signature timestamp against a RFC3161 time-stamping instance.
// +optional
RFC3161Timestamp *RFC3161Timestamp `json:"rfc3161timestamp,omitempty"`
// SignatureFormat specifies the format the authority expects. Supported
// formats are "simplesigning" and "bundle". If not specified, the default
// is "simplesigning" (cosign's default).
SignatureFormat string `json:"signatureFormat,omitempty"`
}

// This references a public verification key stored in
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/policy/v1beta1/clusterimagepolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ type Authority struct {
// RFC3161Timestamp sets the configuration to verify the signature timestamp against a RFC3161 time-stamping instance.
// +optional
RFC3161Timestamp *RFC3161Timestamp `json:"rfc3161timestamp,omitempty"`
// SignatureFormat specifies the format the authority expects. Supported
// formats are "simplesigning" and "bundle". If not specified, the default
// is "simplesigning" (cosign's default).
SignatureFormat string `json:"signatureFormat,omitempty"`
}

// This references a public verification key stored in
Expand Down
31 changes: 31 additions & 0 deletions pkg/tuf/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ import (
"path/filepath"
"runtime"
"strings"
"sync"
"testing/fstest"
"time"

"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore/pkg/tuf"
"github.com/theupdateframework/go-tuf/client"
"sigs.k8s.io/release-utils/version"
)
Expand Down Expand Up @@ -294,3 +297,31 @@ func ClientFromRemote(_ context.Context, mirror string, rootJSON []byte, targets
}
return tufClient, nil
}

var (
once sync.Once
trustedRoot *root.TrustedRoot
singletonRootError error
)

// GetTrustedRoot returns the trusted root for the TUF repository.
func GetTrustedRoot() (*root.TrustedRoot, error) {
once.Do(func() {
tufClient, err := tuf.NewFromEnv(context.Background())
if err != nil {
singletonRootError = fmt.Errorf("initializing tuf: %w", err)
return
}
// TODO: add support for custom trusted root path
targetBytes, err := tufClient.GetTarget("trusted_root.json")
if err != nil {
singletonRootError = fmt.Errorf("error getting targets: %w", err)
return
}
trustedRoot, singletonRootError = root.NewTrustedRootFromJSON(targetBytes)
})
if singletonRootError != nil {
return nil, singletonRootError
}
return trustedRoot, nil
}
140 changes: 140 additions & 0 deletions pkg/webhook/bundle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package webhook

import (
"crypto/x509"
"encoding/hex"
"encoding/json"
"errors"
"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/sigstore/sigstore-go/pkg/bundle"
"github.com/sigstore/sigstore-go/pkg/root"
"github.com/sigstore/sigstore-go/pkg/verify"
)

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

// VerifiedBundle implements Signature
var _ Signature = &VerifiedBundle{}

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

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) Cert() (*x509.Certificate, error) {
vc, err := vb.SGBundle.VerificationContent()
if err != nil {
return nil, err
}
cert := vc.GetCertificate()
if cert != nil {
return cert, nil
}
return nil, errors.New("bundle does not contain a certificate")
}

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

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

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

verifiedBundles := make([]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{SGBundle: b, Result: result, Hash: *hash})
}
}
return verifiedBundles, nil
}

func getBundles(ref name.Reference, remoteOpts []remote.Option) ([]*bundle.Bundle, *v1.Hash, error) {
desc, err := remote.Get(ref, remoteOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error getting image descriptor: %w", err)
}

digest := ref.Context().Digest(desc.Digest.String())

referrers, err := remote.Referrers(digest, remoteOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error getting referrers: %w", err)
}
refManifest, err := referrers.IndexManifest()
if err != nil {
return nil, nil, fmt.Errorf("error getting referrers manifest: %w", err)
}

bundles := make([]*bundle.Bundle, 0)

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

refImg, err := remote.Image(ref.Context().Digest(refDesc.Digest.String()), remoteOpts...)
if err != nil {
return nil, nil, fmt.Errorf("error getting referrer image: %w", err)
}
layers, err := refImg.Layers()
if err != nil {
return nil, nil, fmt.Errorf("error getting referrer image: %w", err)
}
layer0, err := layers[0].Uncompressed()
if err != nil {
return nil, nil, fmt.Errorf("error getting referrer image: %w", err)
}
bundleBytes, err := io.ReadAll(layer0)
if err != nil {
return nil, nil, fmt.Errorf("error getting referrer image: %w", err)
}
b := &bundle.Bundle{}
err = b.UnmarshalJSON(bundleBytes)
if err != nil {
return nil, nil, fmt.Errorf("error unmarshalling bundle: %w", err)
}
bundles = append(bundles, b)
}
if len(bundles) == 0 {
return nil, nil, fmt.Errorf("no bundle found in referrers")
}
return bundles, &desc.Digest, nil
}
3 changes: 3 additions & 0 deletions pkg/webhook/clusterimagepolicy/clusterimagepolicy_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ type Authority struct {
Attestations []AttestationPolicy `json:"attestations,omitempty"`
// +optional
RFC3161Timestamp *RFC3161Timestamp `json:"rfc3161timestamp,omitempty"`
// +optional
SignatureFormat string `json:"signatureFormat,omitempty"`
}

// This references a public verification key stored in
Expand Down Expand Up @@ -325,6 +327,7 @@ func convertAuthorityV1Alpha1ToWebhook(in v1alpha1.Authority) *Authority {
CTLog: in.CTLog,
RFC3161Timestamp: rfc3161Timestamp,
Attestations: attestations,
SignatureFormat: in.SignatureFormat,
}
}

Expand Down
19 changes: 13 additions & 6 deletions pkg/webhook/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@ import (
"knative.dev/pkg/logging"

"github.com/sigstore/cosign/v2/pkg/cosign"
"github.com/sigstore/cosign/v2/pkg/oci"
"github.com/sigstore/sigstore/pkg/signature"
)

func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, hashAlgo crypto.Hash, checkOpts *cosign.CheckOpts) ([]oci.Signature, error) {
func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, hashAlgo crypto.Hash, checkOpts *cosign.CheckOpts) ([]Signature, error) {
if len(keys) == 0 {
return validSignatures(ctx, ref, checkOpts)
}
Expand Down Expand Up @@ -58,16 +57,24 @@ func valid(ctx context.Context, ref name.Reference, keys []crypto.PublicKey, has
var cosignVerifySignatures = cosign.VerifyImageSignatures
var cosignVerifyAttestations = cosign.VerifyImageAttestations

func validSignatures(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]oci.Signature, error) {
func validSignatures(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]Signature, error) {
checkOpts.ClaimVerifier = cosign.SimpleClaimVerifier
sigs, _, err := cosignVerifySignatures(ctx, ref, checkOpts)
return sigs, err
sigList := make([]Signature, len(sigs))
for i, s := range sigs {
sigList[i] = s
}
return sigList, err
}

func validAttestations(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]oci.Signature, error) {
func validAttestations(ctx context.Context, ref name.Reference, checkOpts *cosign.CheckOpts) ([]Signature, error) {
checkOpts.ClaimVerifier = cosign.IntotoSubjectClaimVerifier
attestations, _, err := cosignVerifyAttestations(ctx, ref, checkOpts)
return attestations, err
sigList := make([]Signature, len(attestations))
for i, s := range attestations {
sigList[i] = s
}
return sigList, err
}

func parsePems(b []byte) []*pem.Block {
Expand Down
Loading

0 comments on commit 2f7018e

Please sign in to comment.