From ed6efdc4b8cfbd65047fa29bd1b95d17c54deb4c Mon Sep 17 00:00:00 2001 From: leigh capili Date: Tue, 26 May 2026 07:26:13 +0100 Subject: [PATCH 1/5] cosign: add WithInsecure and WithTLSConfig verifier options WithInsecure passes name.Insecure to GetBundles/VerifyImageAttestations for v3 bundle discovery on HTTP registries. Follows the same pattern as notation's WithInsecureRegistry. WithTLSConfig passes a *tls.Config to the Rekor client, supporting private CAs from certSecretRef. Replaces the cosign CLI rekor wrapper with a direct rekor.GetRekorClient call to thread the option through. Includes a test using a fake non-loopback hostname to verify the insecure option is required for bundle discovery on HTTP registries. Signed-off-by: leigh capili --- internal/oci/cosign/cosign.go | 57 +++++++-- internal/oci/cosign/verify_insecure_test.go | 127 ++++++++++++++++++++ 2 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 internal/oci/cosign/verify_insecure_test.go diff --git a/internal/oci/cosign/cosign.go b/internal/oci/cosign/cosign.go index d87d91dae..3be4ab03e 100644 --- a/internal/oci/cosign/cosign.go +++ b/internal/oci/cosign/cosign.go @@ -19,6 +19,7 @@ package cosign import ( "context" "crypto" + "crypto/tls" "fmt" "sync" "time" @@ -27,9 +28,10 @@ import ( "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/sigstore/cosign/v3/cmd/cosign/cli/fulcio" coptions "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" - "github.com/sigstore/cosign/v3/cmd/cosign/cli/rekor" "github.com/sigstore/cosign/v3/pkg/cosign" "github.com/sigstore/cosign/v3/pkg/oci" + rekorclient "github.com/sigstore/rekor/pkg/client" + rekorgenclient "github.com/sigstore/rekor/pkg/generated/client" ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/sigstore/sigstore-go/pkg/root" @@ -45,6 +47,8 @@ type options struct { rOpt []remote.Option identities []cosign.Identity trustedRoot []byte + insecure bool + tlsConfig *tls.Config } // Options is a function that configures the options applied to a Verifier. @@ -83,9 +87,27 @@ func WithTrustedRoot(trustedRoot []byte) Options { } } +// WithInsecure sets the verifier to use HTTP when discovering v3 bundle +// signatures from the container registry via OCI referrers tag fallback. +// Does not affect Rekor connections. +func WithInsecure(insecure bool) Options { + return func(opts *options) { + opts.insecure = insecure + } +} + +// WithTLSConfig sets the TLS configuration for Rekor client connections. +// When nil, the system trust store is used. +func WithTLSConfig(tlsConfig *tls.Config) Options { + return func(opts *options) { + opts.tlsConfig = tlsConfig + } +} + // CosignVerifier is a struct which is responsible for executing verification logic. type CosignVerifier struct { - opts *cosign.CheckOpts + opts *cosign.CheckOpts + insecure bool } // CosignVerifierFactory is a factory for creating Verifiers with shared state. @@ -152,7 +174,7 @@ func (f *CosignVerifierFactory) NewCosignVerifier(ctx context.Context, opts ...O return nil, err } - return &CosignVerifier{opts: checkOpts}, nil + return &CosignVerifier{opts: checkOpts, insecure: o.insecure}, nil } // Keyless verification: when a custom trusted root is provided, use it @@ -171,16 +193,16 @@ func (f *CosignVerifierFactory) NewCosignVerifier(ctx context.Context, opts ...O return nil, fmt.Errorf("unable to extract Rekor URL from trusted root: %w", err) } - checkOpts.RekorClient, err = rekor.NewClient(rekorURL) + checkOpts.RekorClient, err = newRekorClient(rekorURL, o.tlsConfig) if err != nil { return nil, fmt.Errorf("unable to create Rekor client: %w", err) } - return &CosignVerifier{opts: checkOpts}, nil + return &CosignVerifier{opts: checkOpts, insecure: o.insecure}, nil } // Keyless verification using the public Sigstore infrastructure. - checkOpts.RekorClient, err = rekor.NewClient(coptions.DefaultRekorURL) + checkOpts.RekorClient, err = newRekorClient(coptions.DefaultRekorURL, o.tlsConfig) if err != nil { return nil, fmt.Errorf("unable to create Rekor client: %w", err) } @@ -233,7 +255,17 @@ func (f *CosignVerifierFactory) NewCosignVerifier(ctx context.Context, opts ...O return nil, fmt.Errorf("unable to get Fulcio intermediate certs: %w", err) } - return &CosignVerifier{opts: checkOpts}, nil + return &CosignVerifier{opts: checkOpts, insecure: o.insecure}, nil +} + +// newRekorClient creates a Rekor client with optional TLS configuration. +// If tlsConfig is nil, the default system trust store is used. +func newRekorClient(rekorURL string, tlsConfig *tls.Config) (*rekorgenclient.Rekor, error) { + opts := []rekorclient.Option{rekorclient.WithUserAgent(coptions.UserAgent())} + if tlsConfig != nil { + opts = append(opts, rekorclient.WithTLSConfig(tlsConfig)) + } + return rekorclient.GetRekorClient(rekorURL, opts...) } // rekorURLFromTrustedRoot extracts the Rekor base URL from a trusted root's @@ -265,14 +297,21 @@ func (v *CosignVerifier) Verify(ctx context.Context, ref name.Reference) (soci.V var signatures []oci.Signature // copy options since we'll need to change them based on bundle discovery on the ref opts := *v.opts - newBundles, _, err := cosign.GetBundles(ctx, ref, opts.RegistryClientOpts) + + // Pass insecure to GetBundles for internal bundle digest references. + var nameOpts []name.Option + if v.insecure { + nameOpts = append(nameOpts, name.Insecure) + } + + newBundles, _, err := cosign.GetBundles(ctx, ref, opts.RegistryClientOpts, nameOpts...) // if no bundles are returned, let's fallback to the cosign v2 behavior, similar to the cosign CLI if len(newBundles) == 0 || err != nil { opts.NewBundleFormat = false signatures, _, err = cosign.VerifyImageSignatures(ctx, ref, &opts) } else { opts.NewBundleFormat = true - signatures, _, err = cosign.VerifyImageAttestations(ctx, ref, &opts) + signatures, _, err = cosign.VerifyImageAttestations(ctx, ref, &opts, nameOpts...) } if err != nil { return soci.VerificationResultFailed, err diff --git a/internal/oci/cosign/verify_insecure_test.go b/internal/oci/cosign/verify_insecure_test.go new file mode 100644 index 000000000..8247479b9 --- /dev/null +++ b/internal/oci/cosign/verify_insecure_test.go @@ -0,0 +1,127 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cosign + +import ( + "context" + "fmt" + "net" + "net/http" + "os" + "path" + "testing" + "time" + + "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/types" + . "github.com/onsi/gomega" + coptions "github.com/sigstore/cosign/v3/cmd/cosign/cli/options" + "github.com/sigstore/cosign/v3/cmd/cosign/cli/sign" + "github.com/sigstore/cosign/v3/pkg/cosign" + + soci "github.com/fluxcd/source-controller/internal/oci" + testregistry "github.com/fluxcd/source-controller/tests/registry" +) + +// TestVerifyInsecureV3Bundle tests v3 bundle-format signature verification +// against an HTTP-only registry accessed via a non-loopback hostname. +// +// go-containerregistry uses HTTP implicitly for localhost/127.0.0.1/RFC1918. +// This test uses a fake external hostname to cover the case of in-cluster +// registries like "my-registry:5000" where name.Insecure must be explicit. +// +// GetBundles() creates new name.Reference objects for bundle digests via +// name.ParseReference without carrying over name.Insecure from the original +// ref, so WithInsecure(true) on the verifier is needed to make it work. +func TestVerifyInsecureV3Bundle(t *testing.T) { + g := NewWithT(t) + ctx := context.Background() + + // Start an HTTP-only registry on a random port + registryAddr := testregistry.New(t) + _, port, _ := net.SplitHostPort(registryAddr) + + // Use a fake external hostname that requires name.Insecure + fakeHost := "fake-external-registry.example.com" + fakeAddr := fmt.Sprintf("%s:%s", fakeHost, port) + + // Custom transport that resolves the fake hostname to 127.0.0.1 + transport := &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + if host, p, _ := net.SplitHostPort(addr); host == fakeHost { + addr = net.JoinHostPort("127.0.0.1", p) + } + return (&net.Dialer{}).DialContext(ctx, network, addr) + }, + } + + // Generate cosign key pair + keys, err := cosign.GenerateKeyPair(func(b bool) ([]byte, error) { + return []byte(""), nil + }) + g.Expect(err).NotTo(HaveOccurred()) + + tmpDir := t.TempDir() + keyPath := path.Join(tmpDir, "cosign.key") + err = os.WriteFile(keyPath, keys.PrivateBytes, 0600) + g.Expect(err).NotTo(HaveOccurred()) + + // Push a test image using the real loopback address + realRef := fmt.Sprintf("%s/test/v3bundle:v1", registryAddr) + img := mutate.MediaType(empty.Image, types.OCIManifestSchema1) + err = crane.Push(img, realRef) + g.Expect(err).NotTo(HaveOccurred()) + + // Sign with v3 bundle format using the real loopback address + // (the bundle is stored by digest, so it's discoverable from any hostname) + pf := func(_ bool) ([]byte, error) { return []byte(""), nil } + ko := coptions.KeyOpts{ + KeyRef: keyPath, + PassFunc: pf, + NewBundleFormat: true, + } + ro := &coptions.RootOptions{Timeout: 30 * time.Second} + err = sign.SignCmd(ctx, ro, ko, coptions.SignOptions{ + Upload: true, + SkipConfirmation: true, + TlogUpload: false, + NewBundleFormat: true, + Registry: coptions.RegistryOptions{AllowInsecure: true, AllowHTTPRegistry: true}, + }, []string{realRef}) + g.Expect(err).NotTo(HaveOccurred()) + + // Parse reference with name.Insecure (as source-controller does for spec.insecure=true) + ref, err := name.ParseReference(fmt.Sprintf("%s/test/v3bundle:v1", fakeAddr), name.Insecure) + g.Expect(err).NotTo(HaveOccurred()) + + // Verify using the CosignVerifier with the custom transport + vf := NewCosignVerifierFactory() + verifier, err := vf.NewCosignVerifier(ctx, + WithPublicKey(keys.PublicBytes), + WithRemoteOptions(remote.WithTransport(transport)), + WithInsecure(true), + ) + g.Expect(err).NotTo(HaveOccurred()) + + result, err := verifier.Verify(ctx, ref) + g.Expect(err).NotTo(HaveOccurred(), "v3 bundle verification should succeed on insecure registry with non-loopback hostname") + g.Expect(result).To(Equal(soci.VerificationResultSuccess)) +} From 9f374371efdea6ac6903b8075b24111c5df7bfba Mon Sep 17 00:00:00 2001 From: leigh capili Date: Tue, 26 May 2026 07:26:16 +0100 Subject: [PATCH 2/5] controller: wire insecure and TLS config to cosign verifier Pass obj.Spec.Insecure and transport.TLSClientConfig to the cosign verifier so v3 bundle discovery and Rekor connections use the same transport settings as the registry. Signed-off-by: leigh capili --- internal/controller/ocirepository_controller.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/controller/ocirepository_controller.go b/internal/controller/ocirepository_controller.go index 7ab7ef12e..433ee0b9e 100644 --- a/internal/controller/ocirepository_controller.go +++ b/internal/controller/ocirepository_controller.go @@ -678,6 +678,8 @@ func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *sour case "cosign": defaultCosignOciOpts := []scosign.Options{ scosign.WithRemoteOptions(opt...), + scosign.WithInsecure(obj.Spec.Insecure), + scosign.WithTLSConfig(transport.TLSClientConfig), } // If a trusted root secret is provided, read and pass it to the verifier. From 211fb0a550af7eafecae55deeab6bf6b703d5262 Mon Sep 17 00:00:00 2001 From: leigh capili Date: Tue, 26 May 2026 07:26:21 +0100 Subject: [PATCH 3/5] controller: pass TLS config and insecure to cosign verifier for HelmChart OCI Pass clientOpts.TLSConfig and clientOpts.Insecure to the cosign verifier in makeVerifiers so that HelmChart verification of OCI-sourced charts works against registries behind private CAs and on HTTP. Signed-off-by: leigh capili --- internal/controller/helmchart_controller.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/controller/helmchart_controller.go b/internal/controller/helmchart_controller.go index 963d75dde..d8f2eb679 100644 --- a/internal/controller/helmchart_controller.go +++ b/internal/controller/helmchart_controller.go @@ -1313,6 +1313,7 @@ func (r *HelmChartReconciler) makeVerifiers(ctx context.Context, obj *sourcev1.H case "cosign": defaultCosignOciOpts := []scosign.Options{ scosign.WithRemoteOptions(verifyOpts...), + scosign.WithTLSConfig(clientOpts.TLSConfig), } // get the public keys from the given secret From feb03204de4e099988ce3a412b9020d44c6c9919 Mon Sep 17 00:00:00 2001 From: leigh capili Date: Tue, 26 May 2026 07:26:26 +0100 Subject: [PATCH 4/5] hack: sigstore e2e test harness Scripts and testdata for running cosign verification tests against a local sigstore stack on kind. Uses zot (referrers API) and registry:2 (tag fallback) with the scaffold Helm chart. Covers v2/v3 key-pair, v2/v3 keyless with trustedRootSecretRef, tlog, combined refs, wrong key/identity/rekor material, and registry auth. Signed-off-by: leigh capili --- hack/sigstore-test/Makefile | 33 ++++ hack/sigstore-test/build-and-load.sh | 26 +++ hack/sigstore-test/fetch-cosign.sh | 43 +++++ hack/sigstore-test/kind-down.sh | 22 +++ hack/sigstore-test/kind-up.sh | 85 ++++++++ hack/sigstore-test/port-forward.sh | 29 +++ hack/sigstore-test/setup-sigstore.sh | 52 +++++ hack/sigstore-test/test-signing.sh | 181 ++++++++++++++++++ .../combined-secretref-trustedroot.yaml | 17 ++ .../sigstore-test/testdata/registry-auth.yaml | 17 ++ .../testdata/registry2-fallback.yaml | 48 +++++ hack/sigstore-test/testdata/v2-key.yaml | 15 ++ .../testdata/v2-keyless-trustedroot.yaml | 18 ++ hack/sigstore-test/testdata/v3-key-tlog.yaml | 15 ++ hack/sigstore-test/testdata/v3-key.yaml | 15 ++ .../testdata/v3-keyless-trustedroot.yaml | 18 ++ .../testdata/wrong-identity.yaml | 18 ++ hack/sigstore-test/testdata/wrong-key.yaml | 15 ++ .../testdata/wrong-rekor-key.yaml | 18 ++ 19 files changed, 685 insertions(+) create mode 100644 hack/sigstore-test/Makefile create mode 100755 hack/sigstore-test/build-and-load.sh create mode 100755 hack/sigstore-test/fetch-cosign.sh create mode 100755 hack/sigstore-test/kind-down.sh create mode 100755 hack/sigstore-test/kind-up.sh create mode 100755 hack/sigstore-test/port-forward.sh create mode 100755 hack/sigstore-test/setup-sigstore.sh create mode 100755 hack/sigstore-test/test-signing.sh create mode 100644 hack/sigstore-test/testdata/combined-secretref-trustedroot.yaml create mode 100644 hack/sigstore-test/testdata/registry-auth.yaml create mode 100644 hack/sigstore-test/testdata/registry2-fallback.yaml create mode 100644 hack/sigstore-test/testdata/v2-key.yaml create mode 100644 hack/sigstore-test/testdata/v2-keyless-trustedroot.yaml create mode 100644 hack/sigstore-test/testdata/v3-key-tlog.yaml create mode 100644 hack/sigstore-test/testdata/v3-key.yaml create mode 100644 hack/sigstore-test/testdata/v3-keyless-trustedroot.yaml create mode 100644 hack/sigstore-test/testdata/wrong-identity.yaml create mode 100644 hack/sigstore-test/testdata/wrong-key.yaml create mode 100644 hack/sigstore-test/testdata/wrong-rekor-key.yaml diff --git a/hack/sigstore-test/Makefile b/hack/sigstore-test/Makefile new file mode 100644 index 000000000..7ab2f47e3 --- /dev/null +++ b/hack/sigstore-test/Makefile @@ -0,0 +1,33 @@ +# Sigstore test harness for PR #2003 validation +# Usage: +# make -f hack/sigstore-test/Makefile up # create cluster + registry +# make -f hack/sigstore-test/Makefile sigstore # install sigstore stack +# make -f hack/sigstore-test/Makefile build # build and load source-controller +# make -f hack/sigstore-test/Makefile cosign # fetch cosign v2 and v3 binaries +# make -f hack/sigstore-test/Makefile test # run signing/verification tests +# make -f hack/sigstore-test/Makefile all # do everything +# make -f hack/sigstore-test/Makefile down # tear down + +SCRIPT_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +.PHONY: all up sigstore down build cosign test + +all: up sigstore cosign build test + +up: + bash $(SCRIPT_DIR)/kind-up.sh + +sigstore: + bash $(SCRIPT_DIR)/setup-sigstore.sh + +down: + bash $(SCRIPT_DIR)/kind-down.sh + +build: + bash $(SCRIPT_DIR)/build-and-load.sh + +cosign: + bash $(SCRIPT_DIR)/fetch-cosign.sh + +test: + bash $(SCRIPT_DIR)/test-signing.sh diff --git a/hack/sigstore-test/build-and-load.sh b/hack/sigstore-test/build-and-load.sh new file mode 100755 index 000000000..338ffda43 --- /dev/null +++ b/hack/sigstore-test/build-and-load.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Build source-controller and load it into the kind cluster. +set -euo pipefail + +CLUSTER_NAME="${CLUSTER_NAME:-sigstore-test}" +IMG="${IMG:-test/source-controller}" +TAG="${TAG:-latest}" +BUILD_PLATFORM="${BUILD_PLATFORM:-linux/arm64}" + +REPO_ROOT="$(git rev-parse --show-toplevel)" + +echo ">>> building source-controller image" +cd "${REPO_ROOT}" +make docker-build IMG="${IMG}" TAG="${TAG}" BUILD_PLATFORMS="${BUILD_PLATFORM}" BUILD_ARGS=--load + +echo ">>> loading image into kind cluster ${CLUSTER_NAME}" +kind load docker-image --name "${CLUSTER_NAME}" "${IMG}:${TAG}" + +echo ">>> deploying source-controller" +make dev-deploy IMG="${IMG}" TAG="${TAG}" + +echo ">>> waiting for source-controller rollout" +kubectl -n source-system rollout status deploy/source-controller --timeout=2m + +echo ">>> source-controller deployed" +kubectl -n source-system get pods diff --git a/hack/sigstore-test/fetch-cosign.sh b/hack/sigstore-test/fetch-cosign.sh new file mode 100755 index 000000000..f8fcc2440 --- /dev/null +++ b/hack/sigstore-test/fetch-cosign.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Fetch cosign v2 and v3 binaries for testing. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BIN_DIR="${SCRIPT_DIR}/bin" +mkdir -p "${BIN_DIR}" + +OS="$(uname -s | tr '[:upper:]' '[:lower:]')" +ARCH="$(uname -m)" +case "${ARCH}" in + x86_64|amd64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) echo "unsupported arch: ${ARCH}"; exit 1 ;; +esac + +# cosign v3 (latest) +COSIGN_V3_VERSION="${COSIGN_V3_VERSION:-v3.0.6}" +COSIGN_V3_URL="https://github.com/sigstore/cosign/releases/download/${COSIGN_V3_VERSION}/cosign-${OS}-${ARCH}" + +# cosign v2 (last v2 release) +COSIGN_V2_VERSION="${COSIGN_V2_VERSION:-v2.4.3}" +COSIGN_V2_URL="https://github.com/sigstore/cosign/releases/download/${COSIGN_V2_VERSION}/cosign-${OS}-${ARCH}" + +fetch_binary() { + local name="$1" url="$2" dest="$3" + if [ -f "${dest}" ]; then + echo ">>> ${name} already exists at ${dest}" + else + echo ">>> downloading ${name} from ${url}" + curl -fSL -o "${dest}" "${url}" + chmod +x "${dest}" + fi + "${dest}" version 2>&1 | head -3 + echo "" +} + +fetch_binary "cosign-v3" "${COSIGN_V3_URL}" "${BIN_DIR}/cosign-v3" +fetch_binary "cosign-v2" "${COSIGN_V2_URL}" "${BIN_DIR}/cosign-v2" + +echo "=== Cosign binaries ready ===" +echo " v2: ${BIN_DIR}/cosign-v2" +echo " v3: ${BIN_DIR}/cosign-v3" diff --git a/hack/sigstore-test/kind-down.sh b/hack/sigstore-test/kind-down.sh new file mode 100755 index 000000000..38710eb5b --- /dev/null +++ b/hack/sigstore-test/kind-down.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +CLUSTER_NAME="${CLUSTER_NAME:-sigstore-test}" +REG_NAME="${CLUSTER_NAME}-registry" + +echo ">>> tearing down sigstore test environment" + +echo ">>> killing port-forwards" +pkill -f "kubectl.*port-forward.*sigstore" 2>/dev/null || true + +echo ">>> uninstalling scaffold Helm release" +helm uninstall scaffold -n sigstore 2>/dev/null || true + +echo ">>> deleting kind cluster ${CLUSTER_NAME}" +kind delete cluster --name "${CLUSTER_NAME}" 2>/dev/null || true + +echo ">>> removing registries" +docker rm -f "${REG_NAME}" 2>/dev/null || true +docker rm -f "${CLUSTER_NAME}-registry2" 2>/dev/null || true + +echo ">>> done" diff --git a/hack/sigstore-test/kind-up.sh b/hack/sigstore-test/kind-up.sh new file mode 100755 index 000000000..f228e2ea9 --- /dev/null +++ b/hack/sigstore-test/kind-up.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Spin up a kind cluster with a local OCI registry on the same Docker network. +# Sigstore stack installation is a separate step (see setup-sigstore.sh). +set -euo pipefail + +CLUSTER_NAME="${CLUSTER_NAME:-sigstore-test}" +REG_NAME="${CLUSTER_NAME}-registry" +REG_LOCALHOST_PORT="${REG_LOCALHOST_PORT:-5555}" +REG_CLUSTER_PORT=5000 +NODE_IMAGE="${KIND_NODE_IMAGE:-kindest/node:v1.32.2}" + +echo "=== Phase 1: Local OCI Registries ===" +# Primary registry: zot (supports OCI 1.1 referrers API natively) +if [ "$(docker inspect -f '{{.State.Running}}' "${REG_NAME}" 2>/dev/null || true)" != 'true' ]; then + echo ">>> starting zot ${REG_NAME} on localhost:${REG_LOCALHOST_PORT}" + docker run -d --restart=always \ + -p "127.0.0.1:${REG_LOCALHOST_PORT}:5000" \ + --name "${REG_NAME}" \ + ghcr.io/project-zot/zot-linux-$(uname -m | sed 's/aarch64/arm64/;s/x86_64/amd64/'):latest +else + echo ">>> registry ${REG_NAME} already running" +fi + +# Fallback registry: registry:2 (tag-based referrers only, no referrers API) +REG2_NAME="${CLUSTER_NAME}-registry2" +REG2_LOCALHOST_PORT="${REG2_LOCALHOST_PORT:-5557}" +if [ "$(docker inspect -f '{{.State.Running}}' "${REG2_NAME}" 2>/dev/null || true)" != 'true' ]; then + echo ">>> starting registry:2 ${REG2_NAME} on localhost:${REG2_LOCALHOST_PORT}" + docker run -d --restart=always \ + -p "127.0.0.1:${REG2_LOCALHOST_PORT}:${REG_CLUSTER_PORT}" \ + --name "${REG2_NAME}" \ + registry:2 +else + echo ">>> registry:2 ${REG2_NAME} already running" +fi + +echo "=== Phase 2: Kind Cluster ===" +if ! kind get clusters 2>/dev/null | grep -q "^${CLUSTER_NAME}$"; then + echo ">>> creating kind cluster ${CLUSTER_NAME}" + cat <>> cluster ${CLUSTER_NAME} already exists" +fi + +# Connect registries to kind network +if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${REG_NAME}" 2>/dev/null)" = 'null' ]; then + echo ">>> connecting registry:3 to kind network" + docker network connect "kind" "${REG_NAME}" +fi +if [ "$(docker inspect -f='{{json .NetworkSettings.Networks.kind}}' "${REG2_NAME}" 2>/dev/null)" = 'null' ]; then + echo ">>> connecting registry:2 to kind network" + docker network connect "kind" "${REG2_NAME}" +fi + +echo ">>> waiting for cluster readiness" +kubectl wait node "${CLUSTER_NAME}-control-plane" --for=condition=ready --timeout=2m +kubectl wait --for=condition=ready -n kube-system -l k8s-app=kube-dns pod --timeout=2m + +# Allow unauthenticated OIDC discovery (needed for Fulcio to validate SA tokens) +kubectl create clusterrolebinding oidc-reviewer \ + --clusterrole=system:service-account-issuer-discovery \ + --group=system:unauthenticated 2>/dev/null || true + +echo "" +echo "=== Cluster Ready ===" +echo " cluster: ${CLUSTER_NAME}" +echo " registry3: localhost:${REG_LOCALHOST_PORT} (in-cluster: ${REG_NAME}:${REG_CLUSTER_PORT})" +echo " registry2: localhost:${REG2_LOCALHOST_PORT} (in-cluster: ${REG2_NAME}:${REG_CLUSTER_PORT})" +echo "" +echo "Next: run setup-sigstore.sh to install the sigstore stack" diff --git a/hack/sigstore-test/port-forward.sh b/hack/sigstore-test/port-forward.sh new file mode 100755 index 000000000..5542f97c9 --- /dev/null +++ b/hack/sigstore-test/port-forward.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Set up port-forwarding to sigstore services and export env vars. +# Source this file: source hack/sigstore-test/port-forward.sh +set -euo pipefail + +echo ">>> setting up port-forwarding to sigstore services" + +# Kill any existing port-forwards +pkill -f "kubectl.*port-forward.*sigstore" 2>/dev/null || true +sleep 1 + +# Rekor +kubectl -n rekor-system port-forward svc/rekor-server 3000:80 &>/dev/null & +# Fulcio +kubectl -n fulcio-system port-forward svc/fulcio-server 5555:80 &>/dev/null & +# TUF +kubectl -n tuf-system port-forward svc/tuf 8081:80 &>/dev/null & + +sleep 2 + +export REKOR_URL="http://localhost:3000" +export FULCIO_URL="http://localhost:5555" +export TUF_MIRROR="http://localhost:8081" + +echo " REKOR_URL=${REKOR_URL}" +echo " FULCIO_URL=${FULCIO_URL}" +echo " TUF_MIRROR=${TUF_MIRROR}" +echo "" +echo "Port-forwarding active. Use 'kill %1 %2 %3' to stop." diff --git a/hack/sigstore-test/setup-sigstore.sh b/hack/sigstore-test/setup-sigstore.sh new file mode 100755 index 000000000..3052eacfa --- /dev/null +++ b/hack/sigstore-test/setup-sigstore.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Install sigstore stack into the kind cluster using the scaffold Helm chart. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "=== Installing Sigstore Stack ===" +helm repo add sigstore https://sigstore.github.io/helm-charts 2>/dev/null || true +helm repo update sigstore + +echo ">>> installing sigstore/scaffold (this takes a few minutes)..." +helm upgrade --install scaffold sigstore/scaffold \ + --namespace sigstore --create-namespace \ + --timeout 10m \ + --wait + +echo ">>> waiting for sigstore namespaces" +for ns in trillian-system rekor-system fulcio-system ctlog-system tuf-system; do + if kubectl get ns "${ns}" &>/dev/null; then + echo " ${ns}: waiting for deployments..." + for deploy in $(kubectl get deploy -n "${ns}" -o name 2>/dev/null); do + kubectl rollout status --timeout=5m -n "${ns}" "${deploy}" 2>/dev/null || true + done + kubectl wait --timeout=5m -n "${ns}" --for=condition=Complete jobs --all 2>/dev/null || true + fi +done + +echo "=== Extracting PKI Material ===" +mkdir -p "${SCRIPT_DIR}/pki" + +kubectl -n fulcio-system get secrets fulcio-pub-key -ojsonpath='{.data.cert}' 2>/dev/null \ + | base64 -d > "${SCRIPT_DIR}/pki/fulcio.crt.pem" && echo " extracted fulcio.crt.pem" || echo " WARN: fulcio cert not found" + +kubectl -n ctlog-system get secret ctlog-public-key -ojsonpath='{.data.public}' 2>/dev/null \ + | base64 -d > "${SCRIPT_DIR}/pki/ctfe.pub" && echo " extracted ctfe.pub" || echo " WARN: ctlog pub key not found" + +# Rekor public key is fetched via API since the scaffold chart uses an in-memory signer +echo " fetching rekor public key via API..." +kubectl -n rekor-system port-forward svc/rekor-server 3000:80 &>/dev/null & +PF_PID=$! +sleep 2 +if curl -sf http://localhost:3000/api/v1/log/publicKey > "${SCRIPT_DIR}/pki/rekor.pub" 2>/dev/null; then + echo " extracted rekor.pub" +else + echo " WARN: could not fetch rekor public key" +fi +kill $PF_PID 2>/dev/null || true + +echo "" +echo "=== Sigstore Stack Ready ===" +echo " pki: ${SCRIPT_DIR}/pki/" +ls -la "${SCRIPT_DIR}/pki/" 2>/dev/null diff --git a/hack/sigstore-test/test-signing.sh b/hack/sigstore-test/test-signing.sh new file mode 100755 index 000000000..367e5b265 --- /dev/null +++ b/hack/sigstore-test/test-signing.sh @@ -0,0 +1,181 @@ +#!/usr/bin/env bash +# test-signing.sh: Validate cosign v2/v3 x key-pair/keyless verification flows. +# +# Prerequisites: +# - kind cluster running (hack/sigstore-test/kind-up.sh) +# - sigstore stack installed (hack/sigstore-test/setup-sigstore.sh) +# - source-controller deployed (hack/sigstore-test/build-and-load.sh) +# - fulcio config patched for cluster.local issuer +# - rekor-np and fulcio-np NodePort services created +set -eoux pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PKI_DIR="${SCRIPT_DIR}/pki" +KEYS_DIR="${SCRIPT_DIR}/keys" +TESTDATA="${SCRIPT_DIR}/testdata" + +REG="localhost:5555" +REG2="localhost:5557" +NS="source-system" + +NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}') +REKOR_NP=$(kubectl -n rekor-system get svc rekor-np -o jsonpath='{.spec.ports[0].nodePort}') +FULCIO_NP=$(kubectl -n fulcio-system get svc fulcio-np -o jsonpath='{.spec.ports[0].nodePort}') +REKOR_URL="http://${NODE_IP}:${REKOR_NP}" +FULCIO_URL="http://${NODE_IP}:${FULCIO_NP}" + +# --- Setup keys and secrets --- + +mkdir -p "$KEYS_DIR" "$PKI_DIR" + +if [ ! -f "$KEYS_DIR/test.key" ]; then + COSIGN_PASSWORD="" cosign generate-key-pair --output-key-prefix="$KEYS_DIR/test" +fi +if [ ! -f "$KEYS_DIR/wrong.key" ]; then + COSIGN_PASSWORD="" cosign generate-key-pair --output-key-prefix="$KEYS_DIR/wrong" +fi +if [ ! -f "$KEYS_DIR/signing-config-notlog.json" ]; then + cosign signing-config create --out "$KEYS_DIR/signing-config-notlog.json" +fi + +if [ ! -s "$PKI_DIR/trusted_root.json" ]; then + cosign trusted-root create \ + --fulcio="url=http://fulcio-server.fulcio-system.svc,certificate-chain=$PKI_DIR/fulcio.crt.pem" \ + --rekor="url=http://rekor-server.rekor-system.svc,public-key=$PKI_DIR/rekor.pub,start-time=2024-01-01T00:00:00Z" \ + --ctfe="url=http://ctlog.ctlog-system.svc,public-key=$PKI_DIR/ctfe.pub,start-time=2024-01-01T00:00:00Z" \ + --out "$PKI_DIR/trusted_root.json" +fi + +# Wrong trusted root (uses wrong.pub as rekor key) +cosign trusted-root create \ + --fulcio="url=http://fulcio-server.fulcio-system.svc,certificate-chain=$PKI_DIR/fulcio.crt.pem" \ + --rekor="url=http://rekor-server.rekor-system.svc,public-key=$KEYS_DIR/wrong.pub,start-time=2024-01-01T00:00:00Z" \ + --ctfe="url=http://ctlog.ctlog-system.svc,public-key=$PKI_DIR/ctfe.pub,start-time=2024-01-01T00:00:00Z" \ + --out "$PKI_DIR/wrong_trusted_root.json" + +kubectl -n "$NS" create secret generic cosign-test-key \ + --from-file=cosign.pub="$KEYS_DIR/test.pub" --dry-run=client -o yaml | kubectl apply -f - +kubectl -n "$NS" create secret generic cosign-wrong-key \ + --from-file=cosign.pub="$KEYS_DIR/wrong.pub" --dry-run=client -o yaml | kubectl apply -f - +kubectl -n "$NS" create secret generic sigstore-trusted-root \ + --from-file=trusted_root.json="$PKI_DIR/trusted_root.json" --dry-run=client -o yaml | kubectl apply -f - +kubectl -n "$NS" create secret generic sigstore-wrong-root \ + --from-file=trusted_root.json="$PKI_DIR/wrong_trusted_root.json" --dry-run=client -o yaml | kubectl apply -f - +kubectl -n "$NS" create secret docker-registry registry-creds \ + --docker-server="sigstore-test-registry:5000" \ + --docker-username=user --docker-password=pass \ + --dry-run=client -o yaml | kubectl apply -f - + +# --- Helper --- + +push_artifact() { + local ref="$1" + local tmp + tmp=$(mktemp -d) + echo "{\"test\":\"$(basename "$ref")\"}" > "$tmp/data.yaml" + flux push artifact "oci://$ref" --path="$tmp" --source=test --revision=v1 + rm -rf "$tmp" +} + +# --- Sign artifacts --- + +echo "Run cosign v2-style key-pair tests" +push_artifact "$REG/test/v2-key:v1" +COSIGN_PASSWORD="" cosign sign --key="$KEYS_DIR/test.key" \ + --tlog-upload=false --use-signing-config=false --new-bundle-format=false \ + --allow-insecure-registry "$REG/test/v2-key:v1" + +echo "Run cosign v3 bundle key-pair tests" +push_artifact "$REG/test/v3-key:v1" +COSIGN_PASSWORD="" cosign sign --key="$KEYS_DIR/test.key" \ + --signing-config="$KEYS_DIR/signing-config-notlog.json" \ + --allow-insecure-registry "$REG/test/v3-key:v1" + +echo "Run cosign v2-style keyless tests" +push_artifact "$REG/test/v2-keyless:v1" +cosign sign \ + --trusted-root="$PKI_DIR/trusted_root.json" \ + --fulcio-url="$FULCIO_URL" --rekor-url="$REKOR_URL" \ + --allow-insecure-registry --use-signing-config=false --new-bundle-format=false \ + --identity-token="$(kubectl create token default -n default --audience=sigstore)" \ + --yes "$REG/test/v2-keyless:v1" + +echo "Run cosign v3 bundle keyless tests" +push_artifact "$REG/test/v3-keyless:v1" +cosign sign \ + --trusted-root="$PKI_DIR/trusted_root.json" \ + --fulcio-url="$FULCIO_URL" --rekor-url="$REKOR_URL" \ + --allow-insecure-registry --use-signing-config=false \ + --identity-token="$(kubectl create token default -n default --audience=sigstore)" \ + --yes "$REG/test/v3-keyless:v1" + +echo "Run cosign v3 key-pair with tlog tests" +push_artifact "$REG/test/v3-key-tlog:v1" +COSIGN_PASSWORD="" cosign sign --key="$KEYS_DIR/test.key" \ + --trusted-root="$PKI_DIR/trusted_root.json" \ + --rekor-url="$REKOR_URL" \ + --allow-insecure-registry --use-signing-config=false \ + --yes "$REG/test/v3-key-tlog:v1" + +echo "Run registry auth test" +push_artifact "$REG/test/authed:v1" +COSIGN_PASSWORD="" cosign sign --key="$KEYS_DIR/test.key" \ + --tlog-upload=false --use-signing-config=false --new-bundle-format=false \ + --allow-insecure-registry "$REG/test/authed:v1" + +echo "Run registry:2 fallback tests" +push_artifact "$REG2/test/v3-key-fallback:v1" +COSIGN_PASSWORD="" cosign sign --key="$KEYS_DIR/test.key" \ + --signing-config="$KEYS_DIR/signing-config-notlog.json" \ + --allow-insecure-registry "$REG2/test/v3-key-fallback:v1" + +push_artifact "$REG2/test/v3-keyless-fallback:v1" +cosign sign \ + --trusted-root="$PKI_DIR/trusted_root.json" \ + --fulcio-url="$FULCIO_URL" --rekor-url="$REKOR_URL" \ + --allow-insecure-registry --use-signing-config=false \ + --identity-token="$(kubectl create token default -n default --audience=sigstore)" \ + --yes "$REG2/test/v3-keyless-fallback:v1" + +push_artifact "$REG2/test/v3-key-tlog-fallback:v1" +COSIGN_PASSWORD="" cosign sign --key="$KEYS_DIR/test.key" \ + --trusted-root="$PKI_DIR/trusted_root.json" \ + --rekor-url="$REKOR_URL" \ + --allow-insecure-registry --use-signing-config=false \ + --yes "$REG2/test/v3-key-tlog-fallback:v1" + +# --- Apply and verify --- + +echo "Run OCIRepository verify tests" +kubectl -n "$NS" apply -f "${TESTDATA}/v2-key.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/v3-key.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/v2-keyless-trustedroot.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/v3-keyless-trustedroot.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/v3-key-tlog.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/combined-secretref-trustedroot.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/registry-auth.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/registry2-fallback.yaml" + +kubectl -n "$NS" wait ocirepository/test-v2-key --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-v3-key --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-v2-keyless --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-v3-keyless --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-v3-key-tlog --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-combined --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-authed --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-v3-key-fallback --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-v3-keyless-fallback --for=condition=ready --timeout=1m +kubectl -n "$NS" wait ocirepository/test-v3-key-tlog-fallback --for=condition=ready --timeout=1m + +echo "Run negative verification tests" +kubectl -n "$NS" apply -f "${TESTDATA}/wrong-key.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/wrong-identity.yaml" +kubectl -n "$NS" apply -f "${TESTDATA}/wrong-rekor-key.yaml" + +# Negative tests: wait for VerificationError condition +sleep 30 +kubectl -n "$NS" get ocirepository test-wrong-key -o jsonpath='{.status.conditions[?(@.type=="Ready")].reason}' | grep -q "VerificationError" +kubectl -n "$NS" get ocirepository test-wrong-identity -o jsonpath='{.status.conditions[?(@.type=="Ready")].reason}' | grep -q "VerificationError" +kubectl -n "$NS" get ocirepository test-wrong-rekor -o jsonpath='{.status.conditions[?(@.type=="Ready")].reason}' | grep -q "VerificationError" + +echo "All sigstore verification tests passed!" diff --git a/hack/sigstore-test/testdata/combined-secretref-trustedroot.yaml b/hack/sigstore-test/testdata/combined-secretref-trustedroot.yaml new file mode 100644 index 000000000..af45c4c0f --- /dev/null +++ b/hack/sigstore-test/testdata/combined-secretref-trustedroot.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-combined +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v2-key + ref: + tag: v1 + insecure: true + verify: + provider: cosign + secretRef: + name: cosign-test-key + trustedRootSecretRef: + name: sigstore-trusted-root diff --git a/hack/sigstore-test/testdata/registry-auth.yaml b/hack/sigstore-test/testdata/registry-auth.yaml new file mode 100644 index 000000000..dd27bce12 --- /dev/null +++ b/hack/sigstore-test/testdata/registry-auth.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-authed +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/authed + ref: + tag: v1 + insecure: true + secretRef: + name: registry-creds + verify: + provider: cosign + secretRef: + name: cosign-test-key diff --git a/hack/sigstore-test/testdata/registry2-fallback.yaml b/hack/sigstore-test/testdata/registry2-fallback.yaml new file mode 100644 index 000000000..aa0437ce8 --- /dev/null +++ b/hack/sigstore-test/testdata/registry2-fallback.yaml @@ -0,0 +1,48 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-v3-key-fallback +spec: + interval: 5m + url: oci://sigstore-test-registry2:5000/test/v3-key-fallback + ref: + tag: v1 + insecure: true + verify: + provider: cosign + secretRef: + name: cosign-test-key +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-v3-keyless-fallback +spec: + interval: 5m + url: oci://sigstore-test-registry2:5000/test/v3-keyless-fallback + ref: + tag: v1 + insecure: true + verify: + provider: cosign + trustedRootSecretRef: + name: sigstore-trusted-root + matchOIDCIdentity: + - issuer: "^https://kubernetes\\.default\\.svc" + subject: "^https://kubernetes\\.io/namespaces/default/serviceaccounts/default$" +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-v3-key-tlog-fallback +spec: + interval: 5m + url: oci://sigstore-test-registry2:5000/test/v3-key-tlog-fallback + ref: + tag: v1 + insecure: true + verify: + provider: cosign + secretRef: + name: cosign-test-key diff --git a/hack/sigstore-test/testdata/v2-key.yaml b/hack/sigstore-test/testdata/v2-key.yaml new file mode 100644 index 000000000..bc3bfaa49 --- /dev/null +++ b/hack/sigstore-test/testdata/v2-key.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-v2-key +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v2-key + ref: + tag: v1 + insecure: true + verify: + provider: cosign + secretRef: + name: cosign-test-key diff --git a/hack/sigstore-test/testdata/v2-keyless-trustedroot.yaml b/hack/sigstore-test/testdata/v2-keyless-trustedroot.yaml new file mode 100644 index 000000000..be437b5f9 --- /dev/null +++ b/hack/sigstore-test/testdata/v2-keyless-trustedroot.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-v2-keyless +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v2-keyless + ref: + tag: v1 + insecure: true + verify: + provider: cosign + trustedRootSecretRef: + name: sigstore-trusted-root + matchOIDCIdentity: + - issuer: "^https://kubernetes\\.default\\.svc" + subject: "^https://kubernetes\\.io/namespaces/default/serviceaccounts/default$" diff --git a/hack/sigstore-test/testdata/v3-key-tlog.yaml b/hack/sigstore-test/testdata/v3-key-tlog.yaml new file mode 100644 index 000000000..1e4b21915 --- /dev/null +++ b/hack/sigstore-test/testdata/v3-key-tlog.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-v3-key-tlog +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v3-key-tlog + ref: + tag: v1 + insecure: true + verify: + provider: cosign + secretRef: + name: cosign-test-key diff --git a/hack/sigstore-test/testdata/v3-key.yaml b/hack/sigstore-test/testdata/v3-key.yaml new file mode 100644 index 000000000..97b5e46e2 --- /dev/null +++ b/hack/sigstore-test/testdata/v3-key.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-v3-key +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v3-key + ref: + tag: v1 + insecure: true + verify: + provider: cosign + secretRef: + name: cosign-test-key diff --git a/hack/sigstore-test/testdata/v3-keyless-trustedroot.yaml b/hack/sigstore-test/testdata/v3-keyless-trustedroot.yaml new file mode 100644 index 000000000..e81324884 --- /dev/null +++ b/hack/sigstore-test/testdata/v3-keyless-trustedroot.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-v3-keyless +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v3-keyless + ref: + tag: v1 + insecure: true + verify: + provider: cosign + trustedRootSecretRef: + name: sigstore-trusted-root + matchOIDCIdentity: + - issuer: "^https://kubernetes\\.default\\.svc" + subject: "^https://kubernetes\\.io/namespaces/default/serviceaccounts/default$" diff --git a/hack/sigstore-test/testdata/wrong-identity.yaml b/hack/sigstore-test/testdata/wrong-identity.yaml new file mode 100644 index 000000000..3c6b67ec2 --- /dev/null +++ b/hack/sigstore-test/testdata/wrong-identity.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-wrong-identity +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v2-keyless + ref: + tag: v1 + insecure: true + verify: + provider: cosign + trustedRootSecretRef: + name: sigstore-trusted-root + matchOIDCIdentity: + - issuer: "^https://wrong-issuer\\.example\\.com$" + subject: "^wrong-subject@example\\.com$" diff --git a/hack/sigstore-test/testdata/wrong-key.yaml b/hack/sigstore-test/testdata/wrong-key.yaml new file mode 100644 index 000000000..42ba87c39 --- /dev/null +++ b/hack/sigstore-test/testdata/wrong-key.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-wrong-key +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v2-key + ref: + tag: v1 + insecure: true + verify: + provider: cosign + secretRef: + name: cosign-wrong-key diff --git a/hack/sigstore-test/testdata/wrong-rekor-key.yaml b/hack/sigstore-test/testdata/wrong-rekor-key.yaml new file mode 100644 index 000000000..bdcf5482a --- /dev/null +++ b/hack/sigstore-test/testdata/wrong-rekor-key.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: test-wrong-rekor +spec: + interval: 5m + url: oci://sigstore-test-registry:5000/test/v2-keyless + ref: + tag: v1 + insecure: true + verify: + provider: cosign + trustedRootSecretRef: + name: sigstore-wrong-root + matchOIDCIdentity: + - issuer: "^https://kubernetes\\.default\\.svc" + subject: "^https://kubernetes\\.io/namespaces/default/serviceaccounts/default$" From 5e8d2e6ab424e013761c10d80f5567e13bf136bd Mon Sep 17 00:00:00 2001 From: leigh capili Date: Tue, 26 May 2026 07:26:28 +0100 Subject: [PATCH 5/5] ci: run sigstore e2e in parallel with existing e2e New sigstore-linux-amd64 job deploys a local sigstore stack and runs the verification test suite concurrently with kind-linux-amd64. Signed-off-by: leigh capili --- .github/workflows/e2e.yaml | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 570d4edd5..ea4c7f9c4 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -33,3 +33,73 @@ jobs: continue-on-error: true run: | kubectl -n source-system logs -l app=source-controller + + sigstore-linux-amd64: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: 1.26.x + - name: Setup Kubernetes + uses: helm/kind-action@v1 + with: + install_only: true + - name: Setup tools + run: | + curl -fsSL https://fluxcd.io/install.sh | bash + helm repo add sigstore https://sigstore.github.io/helm-charts + - name: Create cluster and registries + run: hack/sigstore-test/kind-up.sh + - name: Install sigstore stack + run: hack/sigstore-test/setup-sigstore.sh + - name: Patch Fulcio config + run: | + kubectl -n fulcio-system get cm fulcio-server-config -o json | \ + python3 -c " + import json, sys + cm = json.load(sys.stdin) + config = json.loads(cm['data']['config.json']) + config['OIDCIssuers']['https://kubernetes.default.svc.cluster.local'] = { + 'IssuerURL': 'https://kubernetes.default.svc.cluster.local', + 'ClientID': 'sigstore', + 'Type': 'kubernetes' + } + cm['data']['config.json'] = json.dumps(config, indent=2) + json.dump(cm, sys.stdout) + " | kubectl apply -f - + kubectl -n fulcio-system rollout restart deploy/fulcio-server + kubectl -n fulcio-system rollout status deploy/fulcio-server --timeout=2m + - name: Expose sigstore services + run: | + kubectl -n rekor-system expose deploy rekor-server --name=rekor-np --type=NodePort --port=80 --target-port=3000 + cat <