diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 96bb964b6aa..95becc0182b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -186,6 +186,7 @@ jobs: run: go test -tags=e2e,registry -v ./test/... env: COSIGN_TEST_REPO: insecure-registry.notlocal:5001 + TUF_ROOT_JSON: ${{ github.workspace }}/root.json - name: Setup local insecure OCI 1.1 registry run: | @@ -236,6 +237,23 @@ jobs: env: OCI11: yes COSIGN_TEST_REPO: insecure-oci-registry.notlocal:5002 + TUF_ROOT_JSON: ${{ github.workspace }}/root.json + + - name: Set up local HTTP registry + run: | + docker run -d --restart=always \ + --name $HTTP_REGISTRY_NAME \ + -p $HTTP_REGISTRY_PORT:5000 registry:2.8.1 + sudo echo "127.0.0.1 $HTTP_REGISTRY_NAME" | sudo tee -a /etc/hosts + env: + HTTP_REGISTRY_NAME: http-registry.notlocal + HTTP_REGISTRY_PORT: 5003 + + - name: Run HTTP registry tests + run: go test -tags=e2e,registry -v ./test/... + env: + COSIGN_TEST_REPO: http-registry.notlocal:5003 + TUF_ROOT_JSON: ${{ github.workspace }}/root.json - name: Collect diagnostics if: ${{ failure() }} diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index 4f17f0ae578..695f8d8c128 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -22,6 +22,7 @@ import ( "fmt" "time" + "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" @@ -86,6 +87,9 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { if err != nil { return err } + if c.RegistryOptions.AllowHTTPRegistry || c.RegistryOptions.AllowInsecure { + ociremoteOpts = append(ociremoteOpts, ociremote.WithNameOptions(name.Insecure)) + } digest, err := ociremote.ResolveDigest(ref, ociremoteOpts...) if err != nil { return err diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 443a62fb447..15f0a9c3b19 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -179,6 +179,9 @@ func signDigestBundle(ctx context.Context, digest name.Digest, ko options.KeyOpt if err != nil { return fmt.Errorf("constructing client options: %w", err) } + if regOpts.AllowHTTPRegistry || regOpts.AllowInsecure { + ociremoteOpts = append(ociremoteOpts, ociremote.WithNameOptions(name.Insecure)) + } if ko.SigningConfig != nil { return signcommon.WriteNewBundleWithSigningConfig(ctx, ko, signOpts.Cert, signOpts.CertChain, payload, digest, types.CosignSignPredicateType, "", ko.SigningConfig, ko.TrustedMaterial, ociremoteOpts...) diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index 3607466dfc8..9f8427863aa 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -109,6 +109,9 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if err != nil { return fmt.Errorf("constructing client options: %w", err) } + if c.AllowHTTPRegistry || c.AllowInsecure { + c.NameOptions = append(c.NameOptions, name.Insecure) + } co := &cosign.CheckOpts{ Annotations: c.Annotations.Annotations, @@ -134,7 +137,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if !c.LocalImage { ref, err := name.ParseReference(images[0], c.NameOptions...) if err == nil && c.NewBundleFormat { - newBundles, _, err := cosign.GetBundles(ctx, ref, co) + newBundles, _, err := cosign.GetBundles(ctx, ref, co, c.NameOptions...) if len(newBundles) == 0 || err != nil { co.NewBundleFormat = false } @@ -209,7 +212,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if co.NewBundleFormat { // OCI bundle always contains attestation - verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co) + verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co, c.NameOptions...) if err != nil { return err } diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index f1d3719eeeb..781626f202b 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -98,6 +98,9 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e if err != nil { return fmt.Errorf("constructing client options: %w", err) } + if c.AllowHTTPRegistry || c.AllowInsecure { + c.NameOptions = append(c.NameOptions, name.Insecure) + } co := &cosign.CheckOpts{ RegistryClientOpts: ociremoteOpts, @@ -119,7 +122,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e if !c.LocalImage { ref, err := name.ParseReference(images[0], c.NameOptions...) if err == nil && c.NewBundleFormat { - newBundles, _, err := cosign.GetBundles(ctx, ref, co) + newBundles, _, err := cosign.GetBundles(ctx, ref, co, c.NameOptions...) if len(newBundles) == 0 || err != nil { co.NewBundleFormat = false } @@ -182,7 +185,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return err } - verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co) + verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co, c.NameOptions...) if err != nil { return err } diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index 0475086afd9..f4755934d53 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -1013,13 +1013,13 @@ func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name // VerifyImageAttestations does all the main cosign checks in a loop, returning the verified attestations. // If there were no valid attestations, we return an error. -func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { +func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil && co.TrustedMaterial == nil { return nil, false, errors.New("one of verifier, root certs, or TrustedMaterial is required") } if co.NewBundleFormat { - return verifyImageAttestationsSigstoreBundle(ctx, signedImgRef, co) + return verifyImageAttestationsSigstoreBundle(ctx, signedImgRef, co, nameOpts...) } // This is a carefully optimized sequence for fetching the attestations of @@ -1621,7 +1621,7 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name return verifySignatures(ctx, sigs, h, co) } -func GetBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ([]*sgbundle.Bundle, *v1.Hash, error) { +func GetBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) ([]*sgbundle.Bundle, *v1.Hash, error) { // This is a carefully optimized sequence for fetching the signatures of the // entity that minimizes registry requests when supplied with a digest input digest, err := ociremote.ResolveDigest(signedImgRef, co.RegistryClientOpts...) @@ -1644,7 +1644,7 @@ func GetBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ( } var bundles = make([]*sgbundle.Bundle, 0, len(index.Manifests)) for _, result := range index.Manifests { - st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, result.Digest.String())) + st, err := name.ParseReference(fmt.Sprintf("%s@%s", digest.Repository, result.Digest.String()), nameOpts...) if err != nil { return nil, nil, err } @@ -1667,8 +1667,8 @@ func GetBundles(_ context.Context, signedImgRef name.Reference, co *CheckOpts) ( } // verifyImageAttestationsSigstoreBundle verifies attestations from attached sigstore bundles -func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, atLeastOneBundleVerified bool, err error) { - bundles, hash, err := GetBundles(ctx, signedImgRef, co) +func verifyImageAttestationsSigstoreBundle(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, nameOpts ...name.Option) (checkedAttestations []oci.Signature, atLeastOneBundleVerified bool, err error) { + bundles, hash, err := GetBundles(ctx, signedImgRef, co, nameOpts...) if err != nil { return nil, false, err } diff --git a/pkg/oci/remote/write.go b/pkg/oci/remote/write.go index b67dd376277..f8e180e7209 100644 --- a/pkg/oci/remote/write.go +++ b/pkg/oci/remote/write.go @@ -299,7 +299,7 @@ func WriteReferrer(d name.Digest, artifactType string, layers []v1.Layer, annota Annotations: annotations, }, artifactType} - targetRef, err := manifest.targetRef(o.TargetRepository) + targetRef, err := manifest.targetRef(o.TargetRepository, opts...) if err != nil { return fmt.Errorf("failed to create target reference: %w", err) } @@ -370,7 +370,8 @@ func (r referrerManifest) RawManifest() ([]byte, error) { return json.Marshal(r) } -func (r referrerManifest) targetRef(repo name.Repository) (name.Reference, error) { +func (r referrerManifest) targetRef(repo name.Repository, opts ...Option) (name.Reference, error) { + o := makeOptions(repo, opts...) manifestBytes, err := r.RawManifest() if err != nil { return nil, err @@ -379,7 +380,7 @@ func (r referrerManifest) targetRef(repo name.Repository) (name.Reference, error if err != nil { return nil, err } - return name.ParseReference(fmt.Sprintf("%s/%s@%s", repo.RegistryStr(), repo.RepositoryStr(), digest.String())) + return name.ParseReference(fmt.Sprintf("%s/%s@%s", repo.RegistryStr(), repo.RepositoryStr(), digest.String()), o.NameOpts...) } func (r referrerManifest) MediaType() (types.MediaType, error) { diff --git a/test/e2e_insecure_registry_test.go b/test/e2e_insecure_registry_test.go index aa826ff91a7..26f546ecaf0 100644 --- a/test/e2e_insecure_registry_test.go +++ b/test/e2e_insecure_registry_test.go @@ -22,15 +22,19 @@ import ( "net/http" "os" "path" + "path/filepath" "testing" + "time" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/attest" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/initialize" "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" cliverify "github.com/sigstore/cosign/v3/cmd/cosign/cli/verify" - "github.com/sigstore/cosign/v3/pkg/cosign/env" + "github.com/sigstore/cosign/v3/pkg/cosign" ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" ) @@ -56,7 +60,13 @@ func TestInsecureRegistry(t *testing.T) { useOCI11 := os.Getenv("oci11Var") != "" rekorURL := os.Getenv(rekorURLVar) - must(downloadAndSetEnv(t, rekorURL+"/api/v1/log/publicKey", env.VariableSigstoreRekorPublicKey.String(), td), t) + + ctx := context.Background() + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + rootPath := os.Getenv("TUF_ROOT_JSON") + mirror := os.Getenv("TUF_MIRROR") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) ko := options.KeyOpts{ KeyRef: privKey, @@ -64,13 +74,19 @@ func TestInsecureRegistry(t *testing.T) { RekorURL: rekorURL, SkipConfirmation: true, } + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + + // Sign without bundle format so := options.SignOptions{ Upload: true, TlogUpload: true, } mustErr(sign.SignCmd(ro, ko, so, []string{imgName}), t) so.Registry = options.RegistryOptions{ - AllowInsecure: true, + AllowInsecure: true, + AllowHTTPRegistry: true, } if useOCI11 { so.RegistryExperimental = options.RegistryExperimentalOptions{ @@ -83,17 +99,105 @@ func TestInsecureRegistry(t *testing.T) { KeyRef: pubKey, CheckClaims: true, RegistryOptions: options.RegistryOptions{ - AllowInsecure: true, + AllowInsecure: true, + AllowHTTPRegistry: true, }, } if useOCI11 { cmd.ExperimentalOCI11 = true } must(cmd.Exec(context.Background(), []string{imgName}), t) + + // Sign new image with new bundle format + // (Must be a new image or the old bundle may be verified instead) + imgName = path.Join(repo, "cosign-registry-e2e-2") + cleanup2 := makeImageIndexWithInsecureRegistry(t, imgName) + defer cleanup2() + + so.NewBundleFormat = true + must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + cmd.NewBundleFormat = true + must(cmd.Exec(context.Background(), []string{imgName}), t) +} + +func TestAttestInsecureRegistry(t *testing.T) { + if os.Getenv("COSIGN_TEST_REPO") == "" { + t.Fatal("COSIGN_TEST_REPO must be set to an insecure registry for this test") + } + repo, stop := reg(t) + defer stop() + td := t.TempDir() + + imgName := path.Join(repo, "cosign-registry-e2e") + cleanup := makeImageIndexWithInsecureRegistry(t, imgName) + defer cleanup() + + _, privKey, pubKey := keypair(t, td) + + rekorURL := os.Getenv(rekorURLVar) + + ctx := context.Background() + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + rootPath := os.Getenv("TUF_ROOT_JSON") + mirror := os.Getenv("TUF_MIRROR") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + ko := options.KeyOpts{ + KeyRef: privKey, + PassFunc: passFunc, + RekorURL: rekorURL, + SkipConfirmation: true, + } + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + + slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` + slsaAttestationPath := filepath.Join(td, "attestation.slsa.json") + if err := os.WriteFile(slsaAttestationPath, []byte(slsaAttestation), 0600); err != nil { + t.Fatal(err) + } + + // Attest without bundle + attestCmd := attest.AttestCommand{ + KeyOpts: ko, + PredicatePath: slsaAttestationPath, + PredicateType: "slsaprovenance", + Timeout: 30 * time.Second, + RekorEntryType: "dsse", + TlogUpload: true, + RegistryOptions: options.RegistryOptions{ + AllowInsecure: true, + AllowHTTPRegistry: true, + }, + } + must(attestCmd.Exec(ctx, imgName), t) + verifyAttestation := cliverify.VerifyAttestationCommand{ + KeyRef: pubKey, + PredicateType: "slsaprovenance", + RegistryOptions: options.RegistryOptions{ + AllowInsecure: true, + AllowHTTPRegistry: true, + }, + } + must(verifyAttestation.Exec(ctx, []string{imgName}), t) + + // Attest with new bundle + imgName = path.Join(repo, "cosign-registry-e2e-2") + cleanup2 := makeImageIndexWithInsecureRegistry(t, imgName) + defer cleanup2() + + ko.NewBundleFormat = true + attestCmd.KeyOpts = ko + must(attestCmd.Exec(ctx, imgName), t) + verifyAttestation.CommonVerifyOptions.NewBundleFormat = true + verifyAttestation.IgnoreTlog = false + must(verifyAttestation.Exec(ctx, []string{imgName}), t) } func makeImageIndexWithInsecureRegistry(t *testing.T, n string) func() { - ref, err := name.ParseReference(n, name.WeakValidation) + ref, err := name.ParseReference(n, name.WeakValidation, name.Insecure) if err != nil { t.Fatal(err) }