diff --git a/.github/actions/sign-image/action.yml b/.github/actions/sign-image/action.yml new file mode 100644 index 000000000..32b7db04e --- /dev/null +++ b/.github/actions/sign-image/action.yml @@ -0,0 +1,39 @@ +name: Sign container images +description: > + Keyless cosign signature on each image, then a verification pass against the + identity the signature should carry, so a misconfigured job fails here and not + on someone's cluster. That identity is the calling workflow's own, + https://github.com///.github/workflows/@. + The calling job needs `id-token: write` and a registry login for every image. + +inputs: + images: + description: Image references by digest (name@sha256:...), whitespace separated. + required: true + +runs: + using: composite + steps: + - name: Install cosign + uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2 + + - name: Sign + shell: bash + env: + IMAGES: ${{ inputs.images }} + run: | + set -euo pipefail + # The .sig tag layout: the OCI-referrer bundle cosign 3 writes by default + # is not read by the Kyverno and policy-controller releases in use today. + cosign sign --yes --recursive --new-bundle-format=false $IMAGES + + - name: Verify + shell: bash + env: + IMAGES: ${{ inputs.images }} + run: | + set -euo pipefail + cosign verify \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + --certificate-identity "https://github.com/$GITHUB_WORKFLOW_REF" \ + $IMAGES diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2e7a327bd..57d44a6ac 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,9 @@ version: 2 updates: - package-ecosystem: "github-actions" - directory: "/" + directories: + - "/" + - "/.github/actions/sign-image" schedule: interval: "weekly" - package-ecosystem: gomod diff --git a/.github/workflows/container_dev.yml b/.github/workflows/container_dev.yml index 2aa5a702b..53d98b94d 100644 --- a/.github/workflows/container_dev.yml +++ b/.github/workflows/container_dev.yml @@ -101,13 +101,16 @@ jobs: build-dev-containers: needs: [build-rust-binaries] runs-on: [ubuntu-latest] + permissions: + contents: read + id-token: write steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Download pre-built Rust binaries - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: rust-bins-* merge-multiple: true @@ -136,7 +139,7 @@ jobs: - name: Docker meta id: docker_meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 with: images: | chrislusf/seaweedfs @@ -149,7 +152,7 @@ jobs: org.opencontainers.image.vendor=Chris Lu - name: Set up QEMU - uses: docker/setup-qemu-action@v4.2.0 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Create BuildKit config run: | @@ -159,28 +162,29 @@ jobs: EOF - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 with: buildkitd-flags: "--debug" buildkitd-config: /tmp/buildkitd.toml - name: Login to Docker Hub if: github.event_name != 'pull_request' - uses: docker/login-action@v4.6.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GHCR if: github.event_name != 'pull_request' - uses: docker/login-action@v4.6.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} - name: Build - uses: docker/build-push-action@v7 + id: build + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 with: context: ./docker push: ${{ github.event_name != 'pull_request' }} @@ -188,3 +192,11 @@ jobs: platforms: linux/amd64, linux/arm64 tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} + + - name: Sign + if: github.event_name != 'pull_request' + uses: ./.github/actions/sign-image + with: + images: >- + chrislusf/seaweedfs@${{ steps.build.outputs.digest }} + ghcr.io/chrislusf/seaweedfs@${{ steps.build.outputs.digest }} diff --git a/.github/workflows/container_foundationdb_version.yml b/.github/workflows/container_foundationdb_version.yml index 5e2ac35e1..076cf3781 100644 --- a/.github/workflows/container_foundationdb_version.yml +++ b/.github/workflows/container_foundationdb_version.yml @@ -30,10 +30,13 @@ permissions: jobs: build-foundationdb-image: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: fetch-depth: 0 @@ -58,7 +61,7 @@ jobs: sudo ldconfig - name: Set up Go - uses: actions/setup-go@v7 + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 with: go-version-file: go.mod @@ -126,14 +129,14 @@ jobs: echo "seaweedfs_ref=$seaweed" >> "$GITHUB_OUTPUT" - name: Set up QEMU - uses: docker/setup-qemu-action@v4.2.0 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 - name: Login to Docker Hub if: github.event_name != 'pull_request' - uses: docker/login-action@v4.6.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -154,7 +157,8 @@ jobs: fi - name: Build and push image - uses: docker/build-push-action@v7 + id: build + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 with: context: ./docker push: ${{ github.event_name != 'pull_request' }} @@ -171,3 +175,8 @@ jobs: org.opencontainers.image.description=SeaweedFS is a distributed storage system for blobs, objects, files, and data lake, to store and serve billions of files fast! org.opencontainers.image.vendor=Chris Lu + - name: Sign + if: github.event_name != 'pull_request' + uses: ./.github/actions/sign-image + with: + images: chrislusf/seaweedfs@${{ steps.build.outputs.digest }} diff --git a/.github/workflows/container_latest.yml b/.github/workflows/container_latest.yml index 2a611c5dd..cedfa27d7 100644 --- a/.github/workflows/container_latest.yml +++ b/.github/workflows/container_latest.yml @@ -441,15 +441,19 @@ jobs: runs-on: ubuntu-latest needs: [setup, build, trivy-scan] if: needs.setup.outputs.publish == 'true' && github.event_name != 'pull_request' + permissions: + contents: read + id-token: write strategy: matrix: variant: ${{ fromJSON(needs.setup.outputs.variants) }} steps: - - name: Checkout - uses: actions/checkout@v7 + - name: Checkout the signing action + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: - ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref || github.ref }} - + sparse-checkout: .github/actions + persist-credentials: false + - name: Configure variant id: config run: | @@ -461,19 +465,19 @@ jobs: - name: Docker meta id: docker_meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 with: images: | chrislusf/seaweedfs ghcr.io/chrislusf/seaweedfs tags: type=raw,value=${{ github.event_name == 'workflow_dispatch' && github.event.inputs.image_tag || 'latest' }},suffix=${{ steps.config.outputs.tag_suffix }} - name: Login to Docker Hub - uses: docker/login-action@v4.6.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GHCR - uses: docker/login-action@v4.6.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ secrets.GHCR_USERNAME }} @@ -482,21 +486,29 @@ jobs: run: | # Install crane for efficient multi-arch image copying cd $(mktemp -d) - curl -sL "https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz" | tar xz + curl -sLO https://github.com/google/go-containerregistry/releases/download/v0.22.0/go-containerregistry_Linux_x86_64.tar.gz + echo "edb74d53fad9a596860f59d1c5d04a43dfb5f441dc71f57060dd0bf39483c833 go-containerregistry_Linux_x86_64.tar.gz" | sha256sum -c - + tar xzf go-containerregistry_Linux_x86_64.tar.gz crane sudo mv crane /usr/local/bin/ crane version - name: Create and push manifest + id: manifest + env: + BASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.image_tag || 'latest' }} run: | SUFFIX="${{ steps.config.outputs.tag_suffix }}" - BASE_TAG="${{ github.event_name == 'workflow_dispatch' && github.event.inputs.image_tag || 'latest' }}" # Create manifest on GHCR first (no rate limits) echo "Creating GHCR manifest (no rate limits)..." docker buildx imagetools create -t ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} \ + --metadata-file /tmp/manifest.json \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-amd64 \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm64 \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-386 + # The copy and the signature below use the digest this run pushed, not whatever the tag points at by then. + DIGEST=$(jq -er '."containerimage.descriptor".digest' /tmp/manifest.json) + echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" # Copy the complete multi-arch image from GHCR to Docker Hub # This only requires one pull from GHCR (no rate limit) and one push to Docker Hub @@ -532,10 +544,10 @@ jobs: # Use crane or skopeo to copy, fallback to docker if not available if command -v crane &> /dev/null; then echo "Using crane to copy..." - retry_with_backoff crane copy ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} + retry_with_backoff crane copy ghcr.io/chrislusf/seaweedfs@${DIGEST} chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} elif command -v skopeo &> /dev/null; then echo "Using skopeo to copy..." - retry_with_backoff skopeo copy --all docker://ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} docker://chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} + retry_with_backoff skopeo copy --all docker://ghcr.io/chrislusf/seaweedfs@${DIGEST} docker://chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} else echo "Using docker buildx imagetools (pulling 4 images from Docker Hub)..." # Fallback: create manifest directly on Docker Hub (pulls from Docker Hub - rate limited) @@ -545,3 +557,10 @@ jobs: ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm \ ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-386 fi + + - name: Sign + uses: ./.github/actions/sign-image + with: + images: >- + ghcr.io/chrislusf/seaweedfs@${{ steps.manifest.outputs.digest }} + chrislusf/seaweedfs@${{ steps.manifest.outputs.digest }} diff --git a/.github/workflows/container_release_foundationdb.yml b/.github/workflows/container_release_foundationdb.yml index ebb098aab..73dae1142 100644 --- a/.github/workflows/container_release_foundationdb.yml +++ b/.github/workflows/container_release_foundationdb.yml @@ -21,15 +21,18 @@ jobs: build-large-release-container_foundationdb: runs-on: [ubuntu-latest] + permissions: + contents: read + id-token: write steps: - name: Checkout - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 - name: Docker meta id: docker_meta - uses: docker/metadata-action@v6 + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6 with: images: | chrislusf/seaweedfs @@ -43,14 +46,14 @@ jobs: org.opencontainers.image.vendor=Chris Lu - name: Set up QEMU - uses: docker/setup-qemu-action@v4.2.0 + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 - name: Login to Docker Hub if: github.event_name != 'pull_request' - uses: docker/login-action@v4.6.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -65,7 +68,8 @@ jobs: fi - name: Build - uses: docker/build-push-action@v7 + id: build + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 with: context: ./docker push: ${{ github.event_name != 'pull_request' }} @@ -76,4 +80,10 @@ jobs: platforms: linux/amd64 tags: ${{ steps.docker_meta.outputs.tags }} labels: ${{ steps.docker_meta.outputs.labels }} + - + name: Sign + if: github.event_name != 'pull_request' + uses: ./.github/actions/sign-image + with: + images: chrislusf/seaweedfs@${{ steps.build.outputs.digest }} diff --git a/.github/workflows/container_release_unified.yml b/.github/workflows/container_release_unified.yml index 97caaa3a3..007b259f6 100644 --- a/.github/workflows/container_release_unified.yml +++ b/.github/workflows/container_release_unified.yml @@ -295,10 +295,14 @@ jobs: if-no-files-found: error retention-days: 1 - # Assemble each variant's per-platform digests into one tag, then mirror to Docker Hub. + # Assemble each variant's per-platform digests into one tag, mirror it to + # Docker Hub, and sign the result on both registries. merge: needs: [build] runs-on: ubuntu-latest + permissions: + contents: read + id-token: write strategy: fail-fast: false matrix: @@ -309,9 +313,16 @@ jobs: - { variant: large_disk_full, tag_suffix: _large_disk_full } - { variant: rocksdb, tag_suffix: _large_disk_rocksdb } steps: + - name: Checkout the signing action + if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + sparse-checkout: .github/actions + persist-credentials: false + - name: Download digests if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant - uses: actions/download-artifact@v8 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: pattern: digest-${{ matrix.variant }}-* merge-multiple: true @@ -319,11 +330,11 @@ jobs: - name: Set up Docker Buildx if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4 - name: Login to GHCR if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant - uses: docker/login-action@v4.6.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: registry: ghcr.io username: ${{ secrets.GHCR_USERNAME }} @@ -331,16 +342,21 @@ jobs: - name: Create multi-arch tag on GHCR if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant + id: manifest working-directory: /tmp/digests run: | docker buildx imagetools create \ -t ${{ env.IMAGE }}:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} \ + --metadata-file /tmp/manifest.json \ $(printf '${{ env.IMAGE }}@sha256:%s ' *) docker buildx imagetools inspect ${{ env.IMAGE }}:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} + # The copy and the signature below use the digest this run pushed, not whatever the tag points at by then. + digest=$(jq -er '."containerimage.descriptor".digest' /tmp/manifest.json) + echo "digest=${digest}" >> "$GITHUB_OUTPUT" - name: Login to Docker Hub if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant - uses: docker/login-action@v4.6.0 + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} @@ -349,7 +365,9 @@ jobs: if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant run: | cd $(mktemp -d) - curl -sL "https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz" | tar xz + curl -sLO https://github.com/google/go-containerregistry/releases/download/v0.22.0/go-containerregistry_Linux_x86_64.tar.gz + echo "edb74d53fad9a596860f59d1c5d04a43dfb5f441dc71f57060dd0bf39483c833 go-containerregistry_Linux_x86_64.tar.gz" | sha256sum -c - + tar xzf go-containerregistry_Linux_x86_64.tar.gz crane sudo mv crane /usr/local/bin/ crane version @@ -380,10 +398,18 @@ jobs: echo "Copying ${{ matrix.variant }} from GHCR to Docker Hub..." retry_with_backoff crane copy \ - ${{ env.IMAGE }}:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} \ + ${{ env.IMAGE }}@${{ steps.manifest.outputs.digest }} \ chrislusf/seaweedfs:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} echo "Copied ${{ matrix.variant }} to Docker Hub" + - name: Sign ${{ matrix.variant }} + if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant + uses: ./.github/actions/sign-image + with: + images: >- + ${{ env.IMAGE }}@${{ steps.manifest.outputs.digest }} + chrislusf/seaweedfs@${{ steps.manifest.outputs.digest }} + # Report-only trivy scan: uploads fixable HIGH/CRITICAL findings to GitHub # Security for visibility, but never blocks the release. Releases (including # `latest`) ship regardless — vulnerabilities are tracked, not gated, since @@ -439,7 +465,8 @@ jobs: # image. crane tag adds an extra tag to an existing manifest — no rebuild, # no QEMU, no separate workflow. Replaces the old container_latest.yml # rebuild that often failed or lagged behind the release. Independent of - # trivy-scan: vuln findings are reported but do not block `latest`. + # trivy-scan: vuln findings are reported but do not block `latest`. The + # cosign signature is attached to the digest, so `latest` carries it too. tag-latest: runs-on: ubuntu-latest needs: [merge] @@ -468,7 +495,9 @@ jobs: - name: Install crane run: | cd $(mktemp -d) - curl -sL "https://github.com/google/go-containerregistry/releases/latest/download/go-containerregistry_Linux_x86_64.tar.gz" | tar xz + curl -sLO https://github.com/google/go-containerregistry/releases/download/v0.22.0/go-containerregistry_Linux_x86_64.tar.gz + echo "edb74d53fad9a596860f59d1c5d04a43dfb5f441dc71f57060dd0bf39483c833 go-containerregistry_Linux_x86_64.tar.gz" | sha256sum -c - + tar xzf go-containerregistry_Linux_x86_64.tar.gz crane sudo mv crane /usr/local/bin/ crane version diff --git a/.github/workflows/container_rocksdb_version.yml b/.github/workflows/container_rocksdb_version.yml index 0ab6eeb3e..f54190d10 100644 --- a/.github/workflows/container_rocksdb_version.yml +++ b/.github/workflows/container_rocksdb_version.yml @@ -22,6 +22,9 @@ permissions: jobs: build-rocksdb-image: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - name: Checkout @@ -94,6 +97,7 @@ jobs: password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push image + id: build uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v2 with: context: ./docker @@ -108,3 +112,8 @@ jobs: org.opencontainers.image.title=seaweedfs org.opencontainers.image.description=SeaweedFS is a distributed storage system for blobs, objects, files, and data lake, to store and serve billions of files fast! org.opencontainers.image.vendor=Chris Lu + + - name: Sign + uses: ./.github/actions/sign-image + with: + images: chrislusf/seaweedfs@${{ steps.build.outputs.digest }} diff --git a/docker/README.md b/docker/README.md index bec6c4bf9..731587ca1 100644 --- a/docker/README.md +++ b/docker/README.md @@ -31,6 +31,49 @@ docker compose -f seaweedfs-dev-compose.yml -p seaweedfs up ``` +## Verify an image signature + +Every image CI pushes to `chrislusf/seaweedfs` and `ghcr.io/chrislusf/seaweedfs` is signed with [cosign](https://docs.sigstore.dev/cosign/verifying/verify/), keyless, by the GitHub Actions workflow that built it, so there is no key to fetch or pin. The signature is attached to the image digest and covers the multi-arch index and each platform image in it; `latest` is the release image under another tag and verifies the same way. Images published before September 2026 predate signing. + +```bash +cosign verify \ + --certificate-oidc-issuer https://token.actions.githubusercontent.com \ + --certificate-identity-regexp '^https://github.com/seaweedfs/seaweedfs/\.github/workflows/container_release_unified\.yml@' \ + chrislusf/seaweedfs:latest +``` + +cosign prints the digest it verified. Deploy by that digest, or let an admission controller resolve the tag, so what runs is what was checked. + +The identity is `https://github.com/seaweedfs/seaweedfs/.github/workflows/@`. The ref is `refs/tags/` for a release and `refs/heads/master` when a variant was republished by hand. The workflow is `container_release_unified.yml` for the release images, `container_dev.yml` for `dev`, `container_latest.yml` for a `latest` rebuilt by hand, `container_release_foundationdb.yml` for the `_large_disk_foundationdb` release image, and `container_foundationdb_version.yml` or `container_rocksdb_version.yml` for the per-version builds. The regexp above accepts release images only; `container_[a-z_]+\.yml@` accepts everything this repository publishes, `dev` included. + +The same check as a Kyverno policy, release images only: + +```yaml +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: verify-seaweedfs-images +spec: + validationFailureAction: Enforce + webhookTimeoutSeconds: 30 + rules: + - name: signed-by-the-release-workflow + match: + any: + - resources: + kinds: + - Pod + verifyImages: + - imageReferences: + - "docker.io/chrislusf/seaweedfs:*" + - "ghcr.io/chrislusf/seaweedfs:*" + attestors: + - entries: + - keyless: + issuer: https://token.actions.githubusercontent.com + subject: https://github.com/seaweedfs/seaweedfs/.github/workflows/container_release_unified.yml@refs/tags/* +``` + ## Local Development ```bash