From ea92927b70aaf44902190be4516fd00611f1934a Mon Sep 17 00:00:00 2001 From: Carlos Tadeu Panato Junior Date: Wed, 15 Nov 2023 10:12:26 +0100 Subject: [PATCH] Backport GHSA-vfp6-jrw2-99g9 (#3364) * Merge pull request from GHSA-vfp6-jrw2-99g9 * Add limit to number of sigs and attestations Signed-off-by: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com> * Update pkg/cosign/fetch.go Co-authored-by: Cody Soyland Signed-off-by: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com> * Update error message Signed-off-by: Hayden B * fix compilation error Signed-off-by: Hayden Blauzvern * Add e2e tests Signed-off-by: Hayden Blauzvern --------- Signed-off-by: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com> Signed-off-by: Hayden B Signed-off-by: Hayden Blauzvern Co-authored-by: Cody Soyland Co-authored-by: Hayden B * fix missing import Signed-off-by: cpanato * bump golang to 1.19.13 Signed-off-by: cpanato * update tests Signed-off-by: cpanato * refactor validate release Signed-off-by: cpanato * pin sigstore/scaffolding/actions/setup to v0.4.13 Signed-off-by: cpanato * update ko-local Signed-off-by: cpanato --------- Signed-off-by: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com> Signed-off-by: Hayden B Signed-off-by: Hayden Blauzvern Signed-off-by: cpanato Co-authored-by: AdamKorcz <44787359+AdamKorcz@users.noreply.github.com> Co-authored-by: Cody Soyland Co-authored-by: Hayden B --- .../workflows/kind-verify-attestation.yaml | 2 +- .github/workflows/validate-release.yml | 119 ++++++++++++------ Makefile | 4 +- pkg/cosign/fetch.go | 10 ++ release/cloudbuild.yaml | 6 +- test/e2e_test.go | 109 ++++++++++++++++ 6 files changed, 209 insertions(+), 41 deletions(-) diff --git a/.github/workflows/kind-verify-attestation.yaml b/.github/workflows/kind-verify-attestation.yaml index a821aa073f4..28ee38698fc 100644 --- a/.github/workflows/kind-verify-attestation.yaml +++ b/.github/workflows/kind-verify-attestation.yaml @@ -64,7 +64,7 @@ jobs: make cosign - name: Install cluster + sigstore - uses: sigstore/scaffolding/actions/setup@main + uses: sigstore/scaffolding/actions/setup@v0.4.13 with: legacy-variables: "false" k8s-version: ${{ matrix.k8s-version }} diff --git a/.github/workflows/validate-release.yml b/.github/workflows/validate-release.yml index 34d9c9068fa..2a065f68c5c 100644 --- a/.github/workflows/validate-release.yml +++ b/.github/workflows/validate-release.yml @@ -20,55 +20,104 @@ on: branches: - main - release-* - - 1.0-fork pull_request: jobs: + check-signature: + runs-on: ubuntu-latest + container: + image: gcr.io/projectsigstore/cosign:v1.13.1@sha256:fd5b09be23ef1027e1bdd490ce78dcc65d2b15902e1f4ba8e04f3b4019cc1057 + + steps: + - name: Check Signature + run: | + cosign verify ghcr.io/gythialy/golang-cross:v1.19.13-0@sha256:06e3605b227948431d43f4a868b68d4a771c71c728099f37856e404f2d77cf06 + env: + TUF_ROOT: /tmp + COSIGN_EXPERIMENTAL: true + validate-release-job: runs-on: ubuntu-latest + needs: + - check-signature - permissions: - actions: none - checks: none - contents: none - deployments: none - issues: none - packages: none - pull-requests: none - repository-projects: none - security-events: none - statuses: none + container: + image: ghcr.io/gythialy/golang-cross:v1.19.13-0@sha256:06e3605b227948431d43f4a868b68d4a771c71c728099f37856e404f2d77cf06 - env: - CROSS_BUILDER_IMAGE: ghcr.io/gythialy/golang-cross:v1.19.4-0@sha256:53ee894818ac14377996a6fe7c8fe6156d018a20f82aaf69f2519fc45d897bec - COSIGN_IMAGE: gcr.io/projectsigstore/cosign:v1.13.1@sha256:fd5b09be23ef1027e1bdd490ce78dcc65d2b15902e1f4ba8e04f3b4019cc1057 + permissions: {} steps: - - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.0.2 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Check Signature + # Error: fatal: detected dubious ownership in repository at '/__w/cosign/cosign' + # To add an exception for this directory, call: + # git config --system --add safe.directory /__w/cosign/cosign + # Reason: Recent versions of git require the .git folder to be owned + # by the same user (see https://github.blog/2022-04-12-git-security-vulnerability-announced/). + # Related + # - https://github.com/actions/runner/issues/2033 + # - https://github.com/actions/checkout/issues/1048 + # - https://github.com/actions/runner-images/issues/6775 + - run: git config --system --add safe.directory /__w/cosign/cosign + + # Related to https://github.com/sigstore/cosign/issues/3149 + - name: free up disk space for the release run: | - docker run --rm \ - -e COSIGN_EXPERIMENTAL=true \ - -e TUF_ROOT=/tmp \ - $COSIGN_IMAGE \ - verify \ - $CROSS_BUILDER_IMAGE + rm -rf /usr/share/dotnet/ + rm -rf "$AGENT_TOOLSDIRECTORY" + rm -rf "/usr/local/share/boost" + rm -rf /opt/ghc + docker rmi $(docker image ls -aq) || true + swapoff /swapfile || true + rm -rf /swapfile /usr/share/dotnet /usr/local/lib/android /opt/ghc || true + apt purge aria2 ansible hhvm mono-devel azure-cli shellcheck rpm xorriso zsync \ + clang-6.0 lldb-6.0 lld-6.0 clang-format-6.0 clang-8 lldb-8 lld-8 clang-format-8 \ + clang-9 lldb-9 lld-9 clangd-9 clang-format-9 dotnet-sdk-3.0 dotnet-sdk-3.1=3.1.101-1 \ + esl-erlang firefox g++-8 g++-9 gfortran-8 gfortran-9 google-chrome-stable \ + google-cloud-sdk ghc-8.0.2 ghc-8.2.2 ghc-8.4.4 ghc-8.6.2 ghc-8.6.3 ghc-8.6.4 \ + ghc-8.6.5 ghc-8.8.1 ghc-8.8.2 ghc-8.8.3 ghc-8.10.1 cabal-install-2.0 cabal-install-2.2 \ + cabal-install-2.4 cabal-install-3.0 cabal-install-3.2 heroku imagemagick \ + libmagickcore-dev libmagickwand-dev libmagic-dev ant ant-optional kubectl \ + mercurial apt-transport-https mono-complete mysql-client libmysqlclient-dev \ + mysql-server mssql-tools unixodbc-dev yarn bazel chrpath libssl-dev libxft-dev \ + libfreetype6 libfreetype6-dev libfontconfig1 libfontconfig1-dev php7.1 php7.1-bcmath \ + php7.1-bz2 php7.1-cgi php7.1-cli php7.1-common php7.1-curl php7.1-dba php7.1-dev \ + php7.1-enchant php7.1-fpm php7.1-gd php7.1-gmp php7.1-imap php7.1-interbase php7.1-intl \ + php7.1-json php7.1-ldap php7.1-mbstring php7.1-mcrypt php7.1-mysql php7.1-odbc \ + php7.1-opcache php7.1-pgsql php7.1-phpdbg php7.1-pspell php7.1-readline php7.1-recode \ + php7.1-snmp php7.1-soap php7.1-sqlite3 php7.1-sybase php7.1-tidy php7.1-xml \ + php7.1-xmlrpc php7.1-xsl php7.1-zip php7.2 php7.2-bcmath php7.2-bz2 php7.2-cgi \ + php7.2-cli php7.2-common php7.2-curl php7.2-dba php7.2-dev php7.2-enchant php7.2-fpm \ + php7.2-gd php7.2-gmp php7.2-imap php7.2-interbase php7.2-intl php7.2-json php7.2-ldap \ + php7.2-mbstring php7.2-mysql php7.2-odbc php7.2-opcache php7.2-pgsql php7.2-phpdbg \ + php7.2-pspell php7.2-readline php7.2-recode php7.2-snmp php7.2-soap php7.2-sqlite3 \ + php7.2-sybase php7.2-tidy php7.2-xml php7.2-xmlrpc php7.2-xsl php7.2-zip php7.3 \ + php7.3-bcmath php7.3-bz2 php7.3-cgi php7.3-cli php7.3-common php7.3-curl php7.3-dba \ + php7.3-dev php7.3-enchant php7.3-fpm php7.3-gd php7.3-gmp php7.3-imap php7.3-interbase \ + php7.3-intl php7.3-json php7.3-ldap php7.3-mbstring php7.3-mysql php7.3-odbc \ + php7.3-opcache php7.3-pgsql php7.3-phpdbg php7.3-pspell php7.3-readline php7.3-recode \ + php7.3-snmp php7.3-soap php7.3-sqlite3 php7.3-sybase php7.3-tidy php7.3-xml \ + php7.3-xmlrpc php7.3-xsl php7.3-zip php7.4 php7.4-bcmath php7.4-bz2 php7.4-cgi \ + php7.4-cli php7.4-common php7.4-curl php7.4-dba php7.4-dev php7.4-enchant php7.4-fpm \ + php7.4-gd php7.4-gmp php7.4-imap php7.4-interbase php7.4-intl php7.4-json php7.4-ldap \ + php7.4-mbstring php7.4-mysql php7.4-odbc php7.4-opcache php7.4-pgsql php7.4-phpdbg \ + php7.4-pspell php7.4-readline php7.4-snmp php7.4-soap php7.4-sqlite3 php7.4-sybase \ + php7.4-tidy php7.4-xml php7.4-xmlrpc php7.4-xsl php7.4-zip php-amqp php-apcu \ + php-igbinary php-memcache php-memcached php-mongodb php-redis php-xdebug \ + php-zmq snmp pollinate libpq-dev postgresql-client powershell ruby-full \ + sphinxsearch subversion mongodb-org -yq >/dev/null 2>&1 || true + apt-get remove -y 'php.*' || true + apt-get autoremove -y >/dev/null 2>&1 || true + apt-get autoclean -y >/dev/null 2>&1 || true + - name: check disk space + run: df -h - name: goreleaser snapshot - run: | - docker run --rm --privileged \ - -e PROJECT_ID=honk-fake-project \ - -e CI=$CI \ - -e RUNTIME_IMAGE=gcr.io/distroless/static:debug-nonroot \ - -v ${PWD}:/go/src/sigstore/cosign \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -w /go/src/sigstore/cosign \ - --entrypoint="" \ - $CROSS_BUILDER_IMAGE \ - make snapshot + run: make snapshot + env: + PROJECT_ID: honk-fake-project + RUNTIME_IMAGE: gcr.io/distroless/static:debug-nonroot - name: check binaries run: | ./dist/cosign-linux-amd64 version - ./dist/sget-linux-amd64 version diff --git a/Makefile b/Makefile index 89815494e8a..5f6144ffb76 100644 --- a/Makefile +++ b/Makefile @@ -148,9 +148,9 @@ ko-sget: .PHONY: ko-local ko-local: $(create_kocache_path) - LDFLAGS="$(LDFLAGS)" GIT_HASH=$(GIT_HASH) GIT_VERSION=$(GIT_VERSION) \ + KO_DOCKER_REPO=ko.local LDFLAGS="$(LDFLAGS)" GIT_HASH=$(GIT_HASH) GIT_VERSION=$(GIT_VERSION) \ KOCACHE=$(KOCACHE_PATH) ko build --base-import-paths \ - --tags $(GIT_VERSION) --tags $(GIT_HASH) --local \ + --tags $(GIT_VERSION) --tags $(GIT_HASH) \ $(ARTIFACT_HUB_LABELS) \ github.com/sigstore/cosign/cmd/cosign diff --git a/pkg/cosign/fetch.go b/pkg/cosign/fetch.go index 1fc761157ea..7a3a306f46f 100644 --- a/pkg/cosign/fetch.go +++ b/pkg/cosign/fetch.go @@ -19,6 +19,7 @@ import ( "context" "crypto/x509" "encoding/json" + "errors" "fmt" "os" "runtime" @@ -29,6 +30,8 @@ import ( "golang.org/x/sync/errgroup" ) +const maxAllowedSigsOrAtts = 100 + type SignedPayload struct { Base64Signature string Payload []byte @@ -77,6 +80,9 @@ func FetchSignaturesForReference(ctx context.Context, ref name.Reference, opts . if len(l) == 0 { return nil, fmt.Errorf("no signatures associated with %s", ref) } + if len(l) > maxAllowedSigsOrAtts { + return nil, fmt.Errorf("maximum number of signatures on an image is %d, found %d", maxAllowedSigsOrAtts, len(l)) + } signatures := make([]SignedPayload, len(l)) var g errgroup.Group @@ -129,6 +135,10 @@ func FetchAttestationsForReference(ctx context.Context, ref name.Reference, opts if len(l) == 0 { return nil, fmt.Errorf("no attestations associated with %s", ref) } + if len(l) > maxAllowedSigsOrAtts { + errMsg := fmt.Sprintf("maximum number of attestations on an image is %d, found %d", maxAllowedSigsOrAtts, len(l)) + return nil, errors.New(errMsg) + } attestations := make([]AttestationPayload, len(l)) var g errgroup.Group diff --git a/release/cloudbuild.yaml b/release/cloudbuild.yaml index e0942ed6eee..921dc455ca8 100644 --- a/release/cloudbuild.yaml +++ b/release/cloudbuild.yaml @@ -39,10 +39,10 @@ steps: - TUF_ROOT=/tmp args: - 'verify' - - 'ghcr.io/gythialy/golang-cross:v1.19.4-0@sha256:53ee894818ac14377996a6fe7c8fe6156d018a20f82aaf69f2519fc45d897bec' + - 'ghcr.io/gythialy/golang-cross:v1.19.13-0@sha256:06e3605b227948431d43f4a868b68d4a771c71c728099f37856e404f2d77cf06' # maybe we can build our own image and use that to be more in a safe side -- name: ghcr.io/gythialy/golang-cross:v1.19.4-0@sha256:53ee894818ac14377996a6fe7c8fe6156d018a20f82aaf69f2519fc45d897bec +- name: ghcr.io/gythialy/golang-cross:v1.19.13-0@sha256:06e3605b227948431d43f4a868b68d4a771c71c728099f37856e404f2d77cf06 entrypoint: /bin/sh dir: "go/src/sigstore/cosign" env: @@ -65,7 +65,7 @@ steps: gcloud auth configure-docker \ && make release -- name: ghcr.io/gythialy/golang-cross:v1.19.4-0@sha256:53ee894818ac14377996a6fe7c8fe6156d018a20f82aaf69f2519fc45d897bec +- name: ghcr.io/gythialy/golang-cross:v1.19.13-0@sha256:06e3605b227948431d43f4a868b68d4a771c71c728099f37856e404f2d77cf06 entrypoint: 'bash' dir: "go/src/sigstore/cosign" env: diff --git a/test/e2e_test.go b/test/e2e_test.go index 1458eeba090..7041bed6f33 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -373,6 +373,83 @@ func TestAttestationReplaceCreate(t *testing.T) { } } +func TestExcessiveAttestations(t *testing.T) { + // skipping tst it is falky and taking too long + t.Skip() + repo, stop := reg(t) + defer stop() + td := t.TempDir() + + imgName := path.Join(repo, "cosign-attest-download-e2e") + + _, _, cleanup := mkimage(t, imgName) + defer cleanup() + + _, privKeyPath, _ := keypair(t, td) + ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} + + ctx := context.Background() + + 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) + } + + vulnAttestation := ` + { + "invocation": { + "parameters": null, + "uri": "invocation.example.com/cosign-testing", + "event_id": "", + "builder.id": "" + }, + "scanner": { + "uri": "fakescanner.example.com/cosign-testing", + "version": "", + "db": { + "uri": "", + "version": "" + }, + "result": null + }, + "metadata": { + "scanStartedOn": "2022-04-12T00:00:00Z", + "scanFinishedOn": "2022-04-12T00:10:00Z" + } +} +` + ref, err := name.ParseReference(imgName) + if err != nil { + t.Fatal(err) + } + regOpts := options.RegistryOptions{} + ociremoteOpts, err := regOpts.ClientOpts(ctx) + if err != nil { + t.Fatal(err) + } + + for i := 0; i < 102; i++ { + vulnAttestationPath := filepath.Join(td, fmt.Sprintf("attestation-%d.vuln.json", i)) + if err := os.WriteFile(vulnAttestationPath, []byte(vulnAttestation), 0600); err != nil { + t.Fatal(err) + } + + // Attest to create a vuln attestation + must(attest.AttestCmd(ctx, ko, options.RegistryOptions{}, imgName, "", "", false, vulnAttestationPath, false, + "vuln", false, 30*time.Second, false), t) + } + + _, err = cosign.FetchAttestationsForReference(ctx, ref, ociremoteOpts...) + if err == nil { + t.Fatalf("Expected an error, but 'err' was 'nil'") + } + expectedError := "maximum number of attestations on an image is 100, found 102" + if err.Error() != expectedError { + t.Errorf("Exted the error to be: '%s' but it was '%s'", expectedError, err.Error()) + } +} + func TestAttestationReplace(t *testing.T) { repo, stop := reg(t) defer stop() @@ -523,6 +600,38 @@ func TestDuplicateSign(t *testing.T) { } } +func TestExcessiveSignatures(t *testing.T) { + repo, stop := reg(t) + defer stop() + td := t.TempDir() + + imgName := path.Join(repo, "cosign-e2e") + + _, _, cleanup := mkimage(t, imgName) + defer cleanup() + + ctx := context.Background() + + for i := 0; i < 102; i++ { + _, privKeyPath, _ := keypair(t, td) + + // Sign the image + ko := options.KeyOpts{KeyRef: privKeyPath, PassFunc: passFunc} + so := options.SignOptions{ + Upload: true, + } + must(sign.SignCmd(ro, ko, so, []string{imgName}), t) + } + err := download.SignatureCmd(ctx, options.RegistryOptions{}, imgName) + if err == nil { + t.Fatal("Expected an error, but 'err' was 'nil'") + } + expectedErr := "maximum number of signatures on an image is 100, found 102" + if err.Error() != expectedErr { + t.Fatalf("Expected the error '%s', but got the error '%s'", expectedErr, err.Error()) + } +} + func TestKeyURLVerify(t *testing.T) { // TODO: re-enable once distroless images are being signed by the new client t.Skip()