Skip to content

Commit

Permalink
Upgrade to TUF v2 client
Browse files Browse the repository at this point in the history
Swap the use of the go-tuf v0.7.0 client from sigstore/sigstore to the
v2.0.0 client from sigstore/sigstore-go. Sigstore-go provides a way to
check for a trusted root and automatically use it if available, but can
also fetch individual targets as needed if the provided TUF mirror does
not supply a trusted_root.json.

This change is intended to be backwards compatible and transparent.

TODO:
- e2e tests
- deprecate `cosign initialize`

Signed-off-by: Colleen Murphy <[email protected]>
  • Loading branch information
cmurphy committed Sep 16, 2024
1 parent 780780b commit 6c89e27
Show file tree
Hide file tree
Showing 20 changed files with 427 additions and 213 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/kind-verify-attestation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,24 +108,34 @@ jobs:
- name: Sign demoimage with cosign
run: |
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
./cosign sign --rekor-url ${{ env.REKOR_URL }} --fulcio-url ${{ env.FULCIO_URL }} --yes --allow-insecure-registry ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}
- name: Create attestation for it
run: |
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
echo -n 'foobar e2e test' > ./predicate-file
./cosign attest --predicate ./predicate-file --fulcio-url ${{ env.FULCIO_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry --yes ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}
- name: Sign a blob
run: |
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
./cosign sign-blob README.md --fulcio-url ${{ env.FULCIO_URL }} --rekor-url ${{ env.REKOR_URL }} --output-certificate cert.pem --output-signature sig --yes --identity-token ${{ env.OIDC_TOKEN }}
- name: Verify with cosign
run: |
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
./cosign verify --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local"
- name: Verify custom attestation with cosign, works
run: |
echo '::group:: test custom verify-attestation success'
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
if ! ./cosign verify-attestation --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" --policy ./test/testdata/policies/cue-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} ; then
echo Failed to verify attestation with a valid policy
exit 1
Expand All @@ -137,6 +147,8 @@ jobs:
- name: Verify custom attestation with cosign, fails
run: |
echo '::group:: test custom verify-attestation success'
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
if ./cosign verify-attestation --policy ./test/testdata/policies/cue-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then
echo custom verify-attestation succeeded with cue policy that should not work
exit 1
Expand All @@ -147,6 +159,8 @@ jobs:
- name: Verify a blob
run: |
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
./cosign verify-blob README.md --rekor-url ${{ env.REKOR_URL }} --certificate ./cert.pem --signature sig --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local"
- name: Collect diagnostics
Expand All @@ -155,11 +169,15 @@ jobs:

- name: Create vuln attestation for it
run: |
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
./cosign attest --predicate ./test/testdata/attestations/vuln-predicate.json --type vuln --fulcio-url ${{ env.FULCIO_URL }} --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry --yes ${{ env.demoimage }} --identity-token ${{ env.OIDC_TOKEN }}
- name: Verify vuln attestation with cosign, works
run: |
echo '::group:: test vuln verify-attestation success'
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
if ! ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-works.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then
echo Failed to verify attestation with a valid policy
exit 1
Expand All @@ -171,6 +189,8 @@ jobs:
- name: Verify vuln attestation with cosign, fails
run: |
echo '::group:: test vuln verify-attestation success'
export TUF_MIRROR=$(kubectl -n tuf-system get ksvc tuf -ojsonpath='{.status.url}')
export TUF_ROOT_JSON=$(pwd)/root.json
if ./cosign verify-attestation --type vuln --policy ./test/testdata/policies/cue-vuln-fails.cue --rekor-url ${{ env.REKOR_URL }} --allow-insecure-registry ${{ env.demoimage }} --certificate-identity https://kubernetes.io/namespaces/default/serviceaccounts/default --certificate-oidc-issuer "https://kubernetes.default.svc.cluster.local" ; then
echo verify-attestation succeeded with cue policy that should not work
exit 1
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/fulcio/fulcioverifier/fulcioverifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewSigner(ctx context.Context, ko options.KeyOpts, signer signature.SignerV
}

// Grab the PublicKeys for the CTFE, either from tuf or env.
pubKeys, err := cosign.GetCTLogPubs(ctx)
pubKeys, err := cosign.GetCTLogPubs()
if err != nil {
return nil, fmt.Errorf("getting CTFE public keys: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/cosign/cli/sign/sign.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef strin
return nil, err
}
if contains {
pubKeys, err := cosign.GetCTLogPubs(ctx)
pubKeys, err := cosign.GetCTLogPubs()
if err != nil {
return nil, fmt.Errorf("getting CTLog public keys: %w", err)
}
Expand Down
10 changes: 5 additions & 5 deletions cmd/cosign/cli/verify/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,11 @@ type VerifyCommand struct {
ExperimentalOCI11 bool
}

func (c *VerifyCommand) loadTSACertificates(ctx context.Context) (*cosign.TSACertificates, error) {
func (c *VerifyCommand) loadTSACertificates() (*cosign.TSACertificates, error) {
if c.TSACertChainPath == "" && !c.UseSignedTimestamps {
return nil, fmt.Errorf("TSA certificate chain path not provided and use-signed-timestamps not set")
}
tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets)
tsaCertificates, err := cosign.GetTSACerts(c.TSACertChainPath)
if err != nil {
return nil, fmt.Errorf("unable to load TSA certificates: %w", err)
}
Expand Down Expand Up @@ -150,7 +150,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
}

if c.TSACertChainPath != "" || c.UseSignedTimestamps {
tsaCertificates, err := c.loadTSACertificates(ctx)
tsaCertificates, err := c.loadTSACertificates()
if err != nil {
return fmt.Errorf("unable to load TSA certificates: %w", err)
}
Expand All @@ -169,7 +169,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {
}
// This performs an online fetch of the Rekor public keys, but this is needed
// for verifying tlog entries (both online and offline).
co.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
co.RekorPubKeys, err = cosign.GetRekorPubs()
if err != nil {
return fmt.Errorf("getting Rekor public keys: %w", err)
}
Expand All @@ -185,7 +185,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) {

// Ignore Signed Certificate Timestamp if the flag is set or a key is provided
if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) {
co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
co.CTLogPubKeys, err = cosign.GetCTLogPubs()
if err != nil {
return fmt.Errorf("getting ctlog public keys: %w", err)
}
Expand Down
10 changes: 5 additions & 5 deletions cmd/cosign/cli/verify/verify_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ type VerifyAttestationCommand struct {
UseSignedTimestamps bool
}

func (c *VerifyAttestationCommand) loadTSACertificates(ctx context.Context) (*cosign.TSACertificates, error) {
func (c *VerifyAttestationCommand) loadTSACertificates() (*cosign.TSACertificates, error) {
if c.TSACertChainPath == "" && !c.UseSignedTimestamps {
return nil, fmt.Errorf("TSA certificate chain path not provided and use-signed-timestamps not set")
}
tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets)
tsaCertificates, err := cosign.GetTSACerts(c.TSACertChainPath)
if err != nil {
return nil, fmt.Errorf("unable to load TSA certificates: %w", err)
}
Expand Down Expand Up @@ -125,14 +125,14 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
}
// Ignore Signed Certificate Timestamp if the flag is set or a key is provided
if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) {
co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
co.CTLogPubKeys, err = cosign.GetCTLogPubs()
if err != nil {
return fmt.Errorf("getting ctlog public keys: %w", err)
}
}

if c.TSACertChainPath != "" || c.UseSignedTimestamps {
tsaCertificates, err := c.loadTSACertificates(ctx)
tsaCertificates, err := c.loadTSACertificates()
if err != nil {
return fmt.Errorf("unable to load TSA certificates: %w", err)
}
Expand All @@ -151,7 +151,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e
}
// This performs an online fetch of the Rekor public keys, but this is needed
// for verifying tlog entries (both online and offline).
co.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
co.RekorPubKeys, err = cosign.GetRekorPubs()
if err != nil {
return fmt.Errorf("getting Rekor public keys: %w", err)
}
Expand Down
10 changes: 5 additions & 5 deletions cmd/cosign/cli/verify/verify_blob.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ type VerifyBlobCmd struct {
IgnoreTlog bool
}

func (c *VerifyBlobCmd) loadTSACertificates(ctx context.Context) (*cosign.TSACertificates, error) {
func (c *VerifyBlobCmd) loadTSACertificates() (*cosign.TSACertificates, error) {
if c.TSACertChainPath == "" && !c.UseSignedTimestamps {
return nil, fmt.Errorf("either TSA certificate chain path must be provided or use-signed-timestamps must be set")
}
tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets)
tsaCertificates, err := cosign.GetTSACerts(c.TSACertChainPath)
if err != nil {
return nil, fmt.Errorf("unable to load TSA certificates: %w", err)
}
Expand Down Expand Up @@ -142,7 +142,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
return fmt.Errorf("either TSA certificate chain path must be provided or use-signed-timestamps must be set when using RFC3161 timestamp path")
}
if c.TSACertChainPath != "" || c.UseSignedTimestamps {
tsaCertificates, err := c.loadTSACertificates(ctx)
tsaCertificates, err := c.loadTSACertificates()
if err != nil {
return err
}
Expand All @@ -161,7 +161,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {
}
// This performs an online fetch of the Rekor public keys, but this is needed
// for verifying tlog entries (both online and offline).
co.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
co.RekorPubKeys, err = cosign.GetRekorPubs()
if err != nil {
return fmt.Errorf("getting Rekor public keys: %w", err)
}
Expand Down Expand Up @@ -294,7 +294,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error {

// Ignore Signed Certificate Timestamp if the flag is set or a key is provided
if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) {
co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
co.CTLogPubKeys, err = cosign.GetCTLogPubs()
if err != nil {
return fmt.Errorf("getting ctlog public keys: %w", err)
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/cosign/cli/verify/verify_blob_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
}

if c.TSACertChainPath != "" || c.UseSignedTimestamps {
tsaCertificates, err := cosign.GetTSACerts(ctx, c.TSACertChainPath, cosign.GetTufTargets)
tsaCertificates, err := cosign.GetTSACerts(c.TSACertChainPath)
if err != nil {
return fmt.Errorf("unable to load or get TSA certificates: %w", err)
}
Expand All @@ -179,7 +179,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st
}
// This performs an online fetch of the Rekor public keys, but this is needed
// for verifying tlog entries (both online and offline).
co.RekorPubKeys, err = cosign.GetRekorPubs(ctx)
co.RekorPubKeys, err = cosign.GetRekorPubs()
if err != nil {
return fmt.Errorf("getting Rekor public keys: %w", err)
}
Expand All @@ -192,7 +192,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st

// Ignore Signed Certificate Timestamp if the flag is set or a key is provided
if shouldVerifySCT(c.IgnoreSCT, c.KeyRef, c.Sk) {
co.CTLogPubKeys, err = cosign.GetCTLogPubs(ctx)
co.CTLogPubKeys, err = cosign.GetCTLogPubs()
if err != nil {
return fmt.Errorf("getting ctlog public keys: %w", err)
}
Expand Down
45 changes: 2 additions & 43 deletions internal/pkg/cosign/fulcio/fulcioroots/fulcioroots.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@
package fulcioroots

import (
"bytes"
"crypto/x509"
"fmt"
"os"
"sync"

"github.com/sigstore/cosign/v2/pkg/cosign/env"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/fulcioroots"
"github.com/sigstore/cosign/v2/pkg/cosign"
)

var (
Expand Down Expand Up @@ -64,41 +59,5 @@ func ReInit() error {
}

func initRoots() (*x509.CertPool, *x509.CertPool, error) {
rootPool := x509.NewCertPool()
// intermediatePool should be nil if no intermediates are found
var intermediatePool *x509.CertPool

rootEnv := env.Getenv(env.VariableSigstoreRootFile)
if rootEnv != "" {
raw, err := os.ReadFile(rootEnv)
if err != nil {
return nil, nil, fmt.Errorf("error reading root PEM file: %w", err)
}
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(raw)
if err != nil {
return nil, nil, fmt.Errorf("error unmarshalling certificates: %w", err)
}
for _, cert := range certs {
// root certificates are self-signed
if bytes.Equal(cert.RawSubject, cert.RawIssuer) {
rootPool.AddCert(cert)
} else {
if intermediatePool == nil {
intermediatePool = x509.NewCertPool()
}
intermediatePool.AddCert(cert)
}
}
} else {
var err error
rootPool, err = fulcioroots.Get()
if err != nil {
return nil, nil, err
}
intermediatePool, err = fulcioroots.GetIntermediates()
if err != nil {
return nil, nil, err
}
}
return rootPool, intermediatePool, nil
return cosign.GetFulcioCerts()
}
47 changes: 22 additions & 25 deletions pkg/cosign/ctlog.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,11 @@
package cosign

import (
"context"
"errors"
"fmt"
"os"

"github.com/sigstore/cosign/v2/pkg/cosign/env"
"github.com/sigstore/sigstore/pkg/tuf"
"github.com/sigstore/sigstore-go/pkg/root"
)

// This is the CT log public key target name
Expand All @@ -32,37 +30,36 @@ var ctPublicKeyStr = `ctfe.pub`
// By default the public keys comes from TUF, but you can override this for test
// purposes by using an env variable `SIGSTORE_CT_LOG_PUBLIC_KEY_FILE`. If using
// an alternate, the file can be PEM, or DER format.
func GetCTLogPubs(ctx context.Context) (*TrustedTransparencyLogPubKeys, error) {
func GetCTLogPubs() (*TrustedTransparencyLogPubKeys, error) {
publicKeys := NewTrustedTransparencyLogPubKeys()
altCTLogPub := env.Getenv(env.VariableSigstoreCTLogPublicKeyFile)

if altCTLogPub != "" {
raw, err := os.ReadFile(altCTLogPub)
if err != nil {
return nil, fmt.Errorf("error reading alternate CTLog public key file: %w", err)
}
if err := publicKeys.AddTransparencyLogPubKey(raw, tuf.Active); err != nil {
return nil, fmt.Errorf("AddCTLogPubKey: %w", err)
}
} else {
tufClient, err := tuf.NewFromEnv(ctx)
if err != nil {
return nil, err
}
targets, err := tufClient.GetTargetsByMeta(tuf.CTFE, []string{ctPublicKeyStr})
if err != nil {
return nil, err
}
for _, t := range targets {
if err := publicKeys.AddTransparencyLogPubKey(t.Target, t.Status); err != nil {
return nil, fmt.Errorf("AddCTLogPubKey: %w", err)
}
return addKeyFromFile(&publicKeys, altCTLogPub, "CT log public key")
}

opts, err := setTUFOpts()
if err != nil {
return nil, err
}

// Try getting keys from trusted_root.json
trustedRoot, _ := root.NewLiveTrustedRoot(opts)
if trustedRoot == nil {
// The TUF repository didn't have a trusted_root.json, try getting the individual target
return addKeyFromTUF(&publicKeys, opts, ctPublicKeyStr, "CT log public key")
}

ctlogs := trustedRoot.CTLogs()
for _, ct := range ctlogs {
validity := checkValidityPeriod(ct.ValidityPeriodStart, ct.ValidityPeriodEnd)
if err := publicKeys.AddTransparencyLogPubKey(ct.PublicKey, validity); err != nil {
return nil, fmt.Errorf("error adding CT log public key: %w", err)
}
}

if len(publicKeys.Keys) == 0 {
return nil, errors.New("none of the CTLog public keys have been found")
}

return &publicKeys, nil
}
Loading

0 comments on commit 6c89e27

Please sign in to comment.