mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +02:00
Sign the published Docker images with cosign (#11129)
* ci: composite action that signs and verifies an image with cosign Keyless, by digest, with a verification pass against the calling workflow's own identity right after signing. Signatures use the .sig tag layout rather than the OCI-referrer bundle cosign 3 writes by default, since that is what the verifiers people run today read. Dependabot is pointed at the action so the cosign-installer pin keeps moving. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker release: sign every variant on both registries The merge job signs each variant's multi-arch index on GHCR and Docker Hub once the tag exists, recursively so the platform images are covered too. latest re-tags the same manifest and inherits the signature. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker dev: sign the dev image Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker latest: sign a latest rebuilt by hand Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker release: sign the foundationdb image Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker: sign the per-version foundationdb and rocksdb builds They push to the same repository as the releases, so an admission policy that verifies chrislusf/seaweedfs would otherwise reject them. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker: document image signature verification Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * ci: pin the actions the signing jobs newly run by commit These run with registry credentials and the OIDC token that signs under the repository's identity, so a retargeted tag upstream must not be able to reach them. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker latest: pass the dispatch tag through env, not the script Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker: complete Kyverno policy, digest note, identity scope Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker latest: keep the dispatch tag out of the manifest script too The step predates signing, but the job now holds the OIDC identity. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * ci: pin every action in the jobs that sign The jobs that hold the OIDC identity run these with registry credentials, so a retargeted tag upstream must not reach them. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker release: copy and sign the digest the run created, pin the rest crane copy and the signature both resolved the tag, which another publisher could move between the two steps. The index digest is read once, right after it is created, and the Docker Hub copy and both signatures use it. The manual latest rebuild gets the same treatment. The actions in these jobs are pinned to commits, crane to v0.22.0 by checksum, and the sparse checkout no longer keeps the token. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker latest: the signing job checks out the workflow's own commit The job only assembles and signs manifests, so nothing there needs the source_ref checkout; the local signing action now comes from the same revision as the workflow file that calls it. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa * docker release: take the index digest from the create result imagetools create writes the descriptor it pushed with --metadata-file (buildx 0.32+, the runners ship 0.36), so the digest no longer comes from re-resolving the tag even within the same step. Claude-Session: https://claude.ai/code/session_01A5zMqzaUg1Snur4Yg8xJGa
This commit is contained in:
@@ -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/<owner>/<repo>/.github/workflows/<file>@<ref>.
|
||||||
|
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
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directories:
|
||||||
|
- "/"
|
||||||
|
- "/.github/actions/sign-image"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
- package-ecosystem: gomod
|
- package-ecosystem: gomod
|
||||||
|
|||||||
@@ -101,13 +101,16 @@ jobs:
|
|||||||
build-dev-containers:
|
build-dev-containers:
|
||||||
needs: [build-rust-binaries]
|
needs: [build-rust-binaries]
|
||||||
runs-on: [ubuntu-latest]
|
runs-on: [ubuntu-latest]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
|
|
||||||
- name: Download pre-built Rust binaries
|
- name: Download pre-built Rust binaries
|
||||||
uses: actions/download-artifact@v8
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||||
with:
|
with:
|
||||||
pattern: rust-bins-*
|
pattern: rust-bins-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -136,7 +139,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
uses: docker/metadata-action@v6
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
chrislusf/seaweedfs
|
chrislusf/seaweedfs
|
||||||
@@ -149,7 +152,7 @@ jobs:
|
|||||||
org.opencontainers.image.vendor=Chris Lu
|
org.opencontainers.image.vendor=Chris Lu
|
||||||
|
|
||||||
- name: Set up QEMU
|
- 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
|
- name: Create BuildKit config
|
||||||
run: |
|
run: |
|
||||||
@@ -159,28 +162,29 @@ jobs:
|
|||||||
EOF
|
EOF
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
|
||||||
with:
|
with:
|
||||||
buildkitd-flags: "--debug"
|
buildkitd-flags: "--debug"
|
||||||
buildkitd-config: /tmp/buildkitd.toml
|
buildkitd-config: /tmp/buildkitd.toml
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v4.6.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v4.6.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ secrets.GHCR_USERNAME }}
|
username: ${{ secrets.GHCR_USERNAME }}
|
||||||
password: ${{ secrets.GHCR_TOKEN }}
|
password: ${{ secrets.GHCR_TOKEN }}
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
uses: docker/build-push-action@v7
|
id: build
|
||||||
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||||
with:
|
with:
|
||||||
context: ./docker
|
context: ./docker
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
@@ -188,3 +192,11 @@ jobs:
|
|||||||
platforms: linux/amd64, linux/arm64
|
platforms: linux/amd64, linux/arm64
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
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 }}
|
||||||
|
|||||||
@@ -30,10 +30,13 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build-foundationdb-image:
|
build-foundationdb-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -58,7 +61,7 @@ jobs:
|
|||||||
sudo ldconfig
|
sudo ldconfig
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v7
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
|
||||||
with:
|
with:
|
||||||
go-version-file: go.mod
|
go-version-file: go.mod
|
||||||
|
|
||||||
@@ -126,14 +129,14 @@ jobs:
|
|||||||
echo "seaweedfs_ref=$seaweed" >> "$GITHUB_OUTPUT"
|
echo "seaweedfs_ref=$seaweed" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Set up QEMU
|
- 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
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
|
||||||
|
|
||||||
- name: Login to Docker Hub
|
- name: Login to Docker Hub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v4.6.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
@@ -154,7 +157,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@v7
|
id: build
|
||||||
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||||
with:
|
with:
|
||||||
context: ./docker
|
context: ./docker
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
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.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
|
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 }}
|
||||||
|
|||||||
@@ -441,14 +441,18 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [setup, build, trivy-scan]
|
needs: [setup, build, trivy-scan]
|
||||||
if: needs.setup.outputs.publish == 'true' && github.event_name != 'pull_request'
|
if: needs.setup.outputs.publish == 'true' && github.event_name != 'pull_request'
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
variant: ${{ fromJSON(needs.setup.outputs.variants) }}
|
variant: ${{ fromJSON(needs.setup.outputs.variants) }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout the signing action
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.source_ref || github.ref }}
|
sparse-checkout: .github/actions
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Configure variant
|
- name: Configure variant
|
||||||
id: config
|
id: config
|
||||||
@@ -461,19 +465,19 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
uses: docker/metadata-action@v6
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
chrislusf/seaweedfs
|
chrislusf/seaweedfs
|
||||||
ghcr.io/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 }}
|
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
|
- name: Login to Docker Hub
|
||||||
uses: docker/login-action@v4.6.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
- name: Login to GHCR
|
- name: Login to GHCR
|
||||||
uses: docker/login-action@v4.6.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ secrets.GHCR_USERNAME }}
|
username: ${{ secrets.GHCR_USERNAME }}
|
||||||
@@ -482,21 +486,29 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# Install crane for efficient multi-arch image copying
|
# Install crane for efficient multi-arch image copying
|
||||||
cd $(mktemp -d)
|
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/
|
sudo mv crane /usr/local/bin/
|
||||||
crane version
|
crane version
|
||||||
- name: Create and push manifest
|
- name: Create and push manifest
|
||||||
|
id: manifest
|
||||||
|
env:
|
||||||
|
BASE_TAG: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.image_tag || 'latest' }}
|
||||||
run: |
|
run: |
|
||||||
SUFFIX="${{ steps.config.outputs.tag_suffix }}"
|
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)
|
# Create manifest on GHCR first (no rate limits)
|
||||||
echo "Creating GHCR manifest (no rate limits)..."
|
echo "Creating GHCR manifest (no rate limits)..."
|
||||||
docker buildx imagetools create -t ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX} \
|
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}-amd64 \
|
||||||
ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm64 \
|
ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm64 \
|
||||||
ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm \
|
ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-arm \
|
||||||
ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-386
|
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
|
# 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
|
# 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
|
# Use crane or skopeo to copy, fallback to docker if not available
|
||||||
if command -v crane &> /dev/null; then
|
if command -v crane &> /dev/null; then
|
||||||
echo "Using crane to copy..."
|
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
|
elif command -v skopeo &> /dev/null; then
|
||||||
echo "Using skopeo to copy..."
|
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
|
else
|
||||||
echo "Using docker buildx imagetools (pulling 4 images from Docker Hub)..."
|
echo "Using docker buildx imagetools (pulling 4 images from Docker Hub)..."
|
||||||
# Fallback: create manifest directly on Docker Hub (pulls from Docker Hub - rate limited)
|
# 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}-arm \
|
||||||
ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-386
|
ghcr.io/chrislusf/seaweedfs:${BASE_TAG}${SUFFIX}-386
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Sign
|
||||||
|
uses: ./.github/actions/sign-image
|
||||||
|
with:
|
||||||
|
images: >-
|
||||||
|
ghcr.io/chrislusf/seaweedfs@${{ steps.manifest.outputs.digest }}
|
||||||
|
chrislusf/seaweedfs@${{ steps.manifest.outputs.digest }}
|
||||||
|
|||||||
@@ -21,15 +21,18 @@ jobs:
|
|||||||
|
|
||||||
build-large-release-container_foundationdb:
|
build-large-release-container_foundationdb:
|
||||||
runs-on: [ubuntu-latest]
|
runs-on: [ubuntu-latest]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v7
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
uses: docker/metadata-action@v6
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
chrislusf/seaweedfs
|
chrislusf/seaweedfs
|
||||||
@@ -43,14 +46,14 @@ jobs:
|
|||||||
org.opencontainers.image.vendor=Chris Lu
|
org.opencontainers.image.vendor=Chris Lu
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
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
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v4
|
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4
|
||||||
-
|
-
|
||||||
name: Login to Docker Hub
|
name: Login to Docker Hub
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v4.6.0
|
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
@@ -65,7 +68,8 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
-
|
-
|
||||||
name: Build
|
name: Build
|
||||||
uses: docker/build-push-action@v7
|
id: build
|
||||||
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||||
with:
|
with:
|
||||||
context: ./docker
|
context: ./docker
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
@@ -76,4 +80,10 @@ jobs:
|
|||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||||
labels: ${{ steps.docker_meta.outputs.labels }}
|
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 }}
|
||||||
|
|
||||||
|
|||||||
@@ -295,10 +295,14 @@ jobs:
|
|||||||
if-no-files-found: error
|
if-no-files-found: error
|
||||||
retention-days: 1
|
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:
|
merge:
|
||||||
needs: [build]
|
needs: [build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -309,9 +313,16 @@ jobs:
|
|||||||
- { variant: large_disk_full, tag_suffix: _large_disk_full }
|
- { variant: large_disk_full, tag_suffix: _large_disk_full }
|
||||||
- { variant: rocksdb, tag_suffix: _large_disk_rocksdb }
|
- { variant: rocksdb, tag_suffix: _large_disk_rocksdb }
|
||||||
steps:
|
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
|
- name: Download digests
|
||||||
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
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:
|
with:
|
||||||
pattern: digest-${{ matrix.variant }}-*
|
pattern: digest-${{ matrix.variant }}-*
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -319,11 +330,11 @@ jobs:
|
|||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
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
|
- name: Login to GHCR
|
||||||
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
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:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ secrets.GHCR_USERNAME }}
|
username: ${{ secrets.GHCR_USERNAME }}
|
||||||
@@ -331,16 +342,21 @@ jobs:
|
|||||||
|
|
||||||
- name: Create multi-arch tag on GHCR
|
- name: Create multi-arch tag on GHCR
|
||||||
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
||||||
|
id: manifest
|
||||||
working-directory: /tmp/digests
|
working-directory: /tmp/digests
|
||||||
run: |
|
run: |
|
||||||
docker buildx imagetools create \
|
docker buildx imagetools create \
|
||||||
-t ${{ env.IMAGE }}:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} \
|
-t ${{ env.IMAGE }}:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }} \
|
||||||
|
--metadata-file /tmp/manifest.json \
|
||||||
$(printf '${{ env.IMAGE }}@sha256:%s ' *)
|
$(printf '${{ env.IMAGE }}@sha256:%s ' *)
|
||||||
docker buildx imagetools inspect ${{ env.IMAGE }}:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }}
|
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
|
- name: Login to Docker Hub
|
||||||
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
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:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
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
|
if: github.event_name != 'workflow_dispatch' || github.event.inputs.variant == 'all' || github.event.inputs.variant == matrix.variant
|
||||||
run: |
|
run: |
|
||||||
cd $(mktemp -d)
|
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/
|
sudo mv crane /usr/local/bin/
|
||||||
crane version
|
crane version
|
||||||
|
|
||||||
@@ -380,10 +398,18 @@ jobs:
|
|||||||
|
|
||||||
echo "Copying ${{ matrix.variant }} from GHCR to Docker Hub..."
|
echo "Copying ${{ matrix.variant }} from GHCR to Docker Hub..."
|
||||||
retry_with_backoff crane copy \
|
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 }}
|
chrislusf/seaweedfs:${{ env.RELEASE_TAG }}${{ matrix.tag_suffix }}
|
||||||
echo "Copied ${{ matrix.variant }} to Docker Hub"
|
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
|
# Report-only trivy scan: uploads fixable HIGH/CRITICAL findings to GitHub
|
||||||
# Security for visibility, but never blocks the release. Releases (including
|
# Security for visibility, but never blocks the release. Releases (including
|
||||||
# `latest`) ship regardless — vulnerabilities are tracked, not gated, since
|
# `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,
|
# image. crane tag adds an extra tag to an existing manifest — no rebuild,
|
||||||
# no QEMU, no separate workflow. Replaces the old container_latest.yml
|
# no QEMU, no separate workflow. Replaces the old container_latest.yml
|
||||||
# rebuild that often failed or lagged behind the release. Independent of
|
# 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:
|
tag-latest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [merge]
|
needs: [merge]
|
||||||
@@ -468,7 +495,9 @@ jobs:
|
|||||||
- name: Install crane
|
- name: Install crane
|
||||||
run: |
|
run: |
|
||||||
cd $(mktemp -d)
|
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/
|
sudo mv crane /usr/local/bin/
|
||||||
crane version
|
crane version
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build-rocksdb-image:
|
build-rocksdb-image:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
id-token: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
@@ -94,6 +97,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
|
id: build
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v2
|
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v2
|
||||||
with:
|
with:
|
||||||
context: ./docker
|
context: ./docker
|
||||||
@@ -108,3 +112,8 @@ jobs:
|
|||||||
org.opencontainers.image.title=seaweedfs
|
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.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
|
org.opencontainers.image.vendor=Chris Lu
|
||||||
|
|
||||||
|
- name: Sign
|
||||||
|
uses: ./.github/actions/sign-image
|
||||||
|
with:
|
||||||
|
images: chrislusf/seaweedfs@${{ steps.build.outputs.digest }}
|
||||||
|
|||||||
@@ -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/<workflow>@<ref>`. The ref is `refs/tags/<version>` 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
|
## Local Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Reference in New Issue
Block a user