From f4ad34c35a96593fa158554c59412285341ede4a Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Thu, 20 Aug 2026 20:04:30 +0300 Subject: [PATCH] Rework docker workflow, add build provenance attestations Docker: single workflow, one job per image per platform instead of one workflow per registry. Platforms are built in parallel and pushed by digest, then combined into a manifest list pushed to Docker Hub and GHCR at once, so both registries get identical digests. arm64 and arm/v7 build on native arm runners, ppc64le is dropped. Registry provenance/sbom attestations are disabled (they were shown as unknown/unknown entries in the registries), build provenance is attested with actions/attest-build-provenance instead and is verifiable with 'gh attestation verify oci://...'. Release binaries (rpm, deb, zip) are attested the same way. cosign version is pinned and images are signed by digest. --- .github/workflows/build-rpm-arm64.yml | 9 ++ .github/workflows/build-rpm-armhf.yml | 9 ++ .github/workflows/build-rpm-x86-64.yml | 9 ++ .github/workflows/build-watcom.yml | 8 + .github/workflows/build-win32.yml | 8 + .github/workflows/build-win64.yml | 8 + .github/workflows/build-winarm64.yml | 8 + .github/workflows/docker-ghcr.yml | 99 ------------ .github/workflows/docker.yml | 199 ++++++++++++++++++------- 9 files changed, 205 insertions(+), 152 deletions(-) delete mode 100644 .github/workflows/docker-ghcr.yml diff --git a/.github/workflows/build-rpm-arm64.yml b/.github/workflows/build-rpm-arm64.yml index ffbe2c2..f975b9d 100644 --- a/.github/workflows/build-rpm-arm64.yml +++ b/.github/workflows/build-rpm-arm64.yml @@ -7,6 +7,8 @@ on: permissions: contents: write + id-token: write + attestations: write jobs: ci: @@ -81,3 +83,10 @@ jobs: GH_TOKEN: ${{ github.token }} TAG: ${{ github.event.release.tag_name }} run: gh release upload "$TAG" 3proxy-${{ env.RELEASE }}.arm64.deb + - name: Attest build provenance + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + *.rpm + *.deb diff --git a/.github/workflows/build-rpm-armhf.yml b/.github/workflows/build-rpm-armhf.yml index f406d53..fe3291f 100644 --- a/.github/workflows/build-rpm-armhf.yml +++ b/.github/workflows/build-rpm-armhf.yml @@ -7,6 +7,8 @@ on: permissions: contents: write + id-token: write + attestations: write jobs: ci: @@ -114,3 +116,10 @@ jobs: GH_TOKEN: ${{ github.token }} TAG: ${{ github.event.release.tag_name }} run: gh release upload "$TAG" 3proxy-${{ env.RELEASE }}.arm.deb + - name: Attest build provenance + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + *.rpm + *.deb diff --git a/.github/workflows/build-rpm-x86-64.yml b/.github/workflows/build-rpm-x86-64.yml index c9d5681..2e19720 100644 --- a/.github/workflows/build-rpm-x86-64.yml +++ b/.github/workflows/build-rpm-x86-64.yml @@ -7,6 +7,8 @@ on: permissions: contents: write + id-token: write + attestations: write jobs: ci: @@ -82,3 +84,10 @@ jobs: TAG: ${{ github.event.release.tag_name }} run: gh release upload "$TAG" 3proxy-${{ env.RELEASE }}.x86_64.deb + - name: Attest build provenance + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + *.rpm + *.deb diff --git a/.github/workflows/build-watcom.yml b/.github/workflows/build-watcom.yml index 9f6e1ed..5b17146 100644 --- a/.github/workflows/build-watcom.yml +++ b/.github/workflows/build-watcom.yml @@ -7,6 +7,8 @@ on: permissions: contents: write + id-token: write + attestations: write jobs: ci: @@ -93,3 +95,9 @@ jobs: GH_TOKEN: ${{ github.token }} TAG: ${{ github.event.release.tag_name }} run: gh release upload "$TAG" 3proxy-${{ env.RELEASE }}-lite.zip + - name: Attest build provenance + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + *.zip diff --git a/.github/workflows/build-win32.yml b/.github/workflows/build-win32.yml index 48c4cbb..141283b 100644 --- a/.github/workflows/build-win32.yml +++ b/.github/workflows/build-win32.yml @@ -7,6 +7,8 @@ on: permissions: contents: write + id-token: write + attestations: write jobs: ci: @@ -108,3 +110,9 @@ jobs: GH_TOKEN: ${{ github.token }} TAG: ${{ github.event.release.tag_name }} run: gh release upload "$TAG" 3proxy-${{ env.RELEASE }}-x86.zip + - name: Attest build provenance + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + *.zip diff --git a/.github/workflows/build-win64.yml b/.github/workflows/build-win64.yml index b3623f0..aeb51c2 100644 --- a/.github/workflows/build-win64.yml +++ b/.github/workflows/build-win64.yml @@ -7,6 +7,8 @@ on: permissions: contents: write + id-token: write + attestations: write jobs: ci: @@ -109,3 +111,9 @@ jobs: GH_TOKEN: ${{ github.token }} TAG: ${{ github.event.release.tag_name }} run: gh release upload "$TAG" 3proxy-${{ env.RELEASE }}-x64.zip + - name: Attest build provenance + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + *.zip diff --git a/.github/workflows/build-winarm64.yml b/.github/workflows/build-winarm64.yml index 59c110c..600967a 100644 --- a/.github/workflows/build-winarm64.yml +++ b/.github/workflows/build-winarm64.yml @@ -7,6 +7,8 @@ on: permissions: contents: write + id-token: write + attestations: write jobs: ci: @@ -108,3 +110,9 @@ jobs: GH_TOKEN: ${{ github.token }} TAG: ${{ github.event.release.tag_name }} run: gh release upload "$TAG" 3proxy-${{ env.RELEASE }}-arm64.zip + - name: Attest build provenance + if: github.event_name == 'release' + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + *.zip diff --git a/.github/workflows/docker-ghcr.yml b/.github/workflows/docker-ghcr.yml deleted file mode 100644 index abff4bb..0000000 --- a/.github/workflows/docker-ghcr.yml +++ /dev/null @@ -1,99 +0,0 @@ -name: Build Docker images (GHCR) - -on: - release: - types: [published] - workflow_dispatch: - -permissions: - contents: read - packages: write - id-token: write - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build: - name: Build and push Docker images - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v7 - - - name: Determine tags - id: tags - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} - IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - run: | - if [[ "$RELEASE_TAG" != "" ]]; then - RELEASE="${RELEASE_TAG#v}" - echo "minimal=${IMAGE}:${RELEASE}.minimal,${IMAGE}:minimal" >> "$GITHUB_OUTPUT" - echo "busybox=${IMAGE}:${RELEASE}.busybox,${IMAGE}:busybox" >> "$GITHUB_OUTPUT" - echo "full=${IMAGE}:${RELEASE},${IMAGE}:latest" >> "$GITHUB_OUTPUT" - else - DATETIME=$(date +%d%m%y%H%M%S) - BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | tr "/" "-") - echo "minimal=${IMAGE}:${DATETIME}-${BRANCH}.minimal,${IMAGE}:${BRANCH}.minimal" >> "$GITHUB_OUTPUT" - echo "busybox=${IMAGE}:${DATETIME}-${BRANCH}.busybox,${IMAGE}:${BRANCH}.busybox" >> "$GITHUB_OUTPUT" - echo "full=${IMAGE}:${DATETIME}-${BRANCH},${IMAGE}:${BRANCH}" >> "$GITHUB_OUTPUT" - fi - - - name: Set up QEMU - uses: docker/setup-qemu-action@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 - - - name: Install cosign - uses: sigstore/cosign-installer@v3 - - - name: Login to GHCR - if: github.event_name != 'pull_request' - uses: docker/login-action@v4 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push minimal - uses: docker/build-push-action@v7 - with: - context: . - file: Dockerfile.minimal - platforms: linux/amd64,linux/arm64,linux/arm/v7,ppc64le - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.tags.outputs.minimal }} - - - name: Build and push busybox - uses: docker/build-push-action@v7 - with: - context: . - file: Dockerfile.busybox - platforms: linux/amd64,linux/arm64,linux/arm/v7,ppc64le - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.tags.outputs.busybox }} - - - name: Build and push full - uses: docker/build-push-action@v7 - with: - context: . - file: Dockerfile.full - platforms: linux/amd64,linux/arm64,linux/arm/v7,ppc64le - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.tags.outputs.full }} - - - name: Sign images - if: github.event_name != 'pull_request' - env: - TAGS_MINIMAL: ${{ steps.tags.outputs.minimal }} - TAGS_BUSYBOX: ${{ steps.tags.outputs.busybox }} - TAGS_FULL: ${{ steps.tags.outputs.full }} - run: | - IFS=',' read -ra TAGS <<< "$TAGS_MINIMAL,$TAGS_BUSYBOX,$TAGS_FULL" - for tag in "${TAGS[@]}"; do - cosign sign --yes "$tag" - done - diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2658f91..a65bbe6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,4 +1,4 @@ -name: Build Docker images (Docker Hub) +name: Build Docker images on: release: @@ -7,89 +7,182 @@ on: permissions: contents: read + packages: write id-token: write + attestations: write env: - REGISTRY: docker.io - IMAGE_NAME: 3proxy/3proxy + DOCKERHUB_IMAGE: docker.io/3proxy/3proxy + GHCR_IMAGE: ghcr.io/3proxy/3proxy jobs: build: - name: Build and push Docker images - runs-on: ubuntu-latest + name: ${{ matrix.image }} ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + image: [full, busybox, minimal] + platform: [linux/amd64, linux/arm64, linux/arm/v7] + include: + - platform: linux/amd64 + runner: ubuntu-latest + - platform: linux/arm64 + runner: ubuntu-24.04-arm + - platform: linux/arm/v7 + runner: ubuntu-24.04-arm steps: - name: Checkout uses: actions/checkout@v7 - - name: Determine tags - id: tags - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} - run: | - if [[ "$RELEASE_TAG" != "" ]]; then - RELEASE="${RELEASE_TAG#v}" - echo "minimal=${REGISTRY}/${IMAGE_NAME}:${RELEASE}.minimal,${REGISTRY}/${IMAGE_NAME}:minimal" >> "$GITHUB_OUTPUT" - echo "busybox=${REGISTRY}/${IMAGE_NAME}:${RELEASE}.busybox,${REGISTRY}/${IMAGE_NAME}:busybox" >> "$GITHUB_OUTPUT" - echo "full=${REGISTRY}/${IMAGE_NAME}:${RELEASE},${REGISTRY}/${IMAGE_NAME}:latest" >> "$GITHUB_OUTPUT" - else - DATETIME=$(date +%d%m%y%H%M%S) - BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | tr "/" "-") - echo "minimal=${REGISTRY}/${IMAGE_NAME}:${DATETIME}-${BRANCH}.minimal,${REGISTRY}/${IMAGE_NAME}:${BRANCH}.minimal" >> "$GITHUB_OUTPUT" - echo "busybox=${REGISTRY}/${IMAGE_NAME}:${DATETIME}-${BRANCH}.busybox,${REGISTRY}/${IMAGE_NAME}:${BRANCH}.busybox" >> "$GITHUB_OUTPUT" - echo "full=${REGISTRY}/${IMAGE_NAME}:${DATETIME}-${BRANCH},${REGISTRY}/${IMAGE_NAME}:${BRANCH}" >> "$GITHUB_OUTPUT" - fi + - name: Platform name + id: platform + run: echo "pair=$(echo '${{ matrix.platform }}' | tr / -)" >> "$GITHUB_OUTPUT" - name: Set up QEMU + if: matrix.platform == 'linux/arm/v7' uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 + - name: Login to GHCR + uses: docker/login-action@v4 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v7 + with: + context: . + file: Dockerfile.${{ matrix.image }} + platforms: ${{ matrix.platform }} + provenance: false + sbom: false + outputs: type=image,name=${{ env.GHCR_IMAGE }},push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + env: + DIGEST: ${{ steps.build.outputs.digest }} + run: | + mkdir -p /tmp/digests + touch "/tmp/digests/${DIGEST#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v7 + with: + name: digests-${{ matrix.image }}-${{ steps.platform.outputs.pair }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + publish: + name: Publish ${{ matrix.image }} + runs-on: ubuntu-latest + needs: build + strategy: + fail-fast: false + matrix: + include: + - image: full + suffix: '' + floating: latest + - image: busybox + suffix: .busybox + floating: busybox + - image: minimal + suffix: .minimal + floating: minimal + steps: + - name: Download digests + uses: actions/download-artifact@v7 + with: + path: /tmp/digests + pattern: digests-${{ matrix.image }}-* + merge-multiple: true + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v4 + - name: Install cosign uses: sigstore/cosign-installer@v3 + with: + cosign-release: v2.4.3 - name: Login to Docker Hub - if: github.event_name != 'pull_request' uses: docker/login-action@v4 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push minimal - uses: docker/build-push-action@v7 + - name: Login to GHCR + uses: docker/login-action@v4 with: - context: . - file: Dockerfile.minimal - platforms: linux/amd64,linux/arm64,linux/arm/v7,ppc64le - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.tags.outputs.minimal }} + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push busybox - uses: docker/build-push-action@v7 - with: - context: . - file: Dockerfile.busybox - platforms: linux/amd64,linux/arm64,linux/arm/v7,ppc64le - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.tags.outputs.busybox }} + - name: Determine tags + id: tags + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + SUFFIX: ${{ matrix.suffix }} + FLOATING: ${{ matrix.floating }} + run: | + if [[ "$RELEASE_TAG" != "" ]]; then + RELEASE="${RELEASE_TAG#v}" + echo "versioned=${RELEASE}${SUFFIX}" >> "$GITHUB_OUTPUT" + echo "moving=${FLOATING}" >> "$GITHUB_OUTPUT" + else + DATETIME=$(date +%d%m%y%H%M%S) + BRANCH=$(echo "${GITHUB_REF#refs/heads/}" | tr "/" "-") + echo "versioned=${DATETIME}-${BRANCH}${SUFFIX}" >> "$GITHUB_OUTPUT" + echo "moving=${BRANCH}${SUFFIX}" >> "$GITHUB_OUTPUT" + fi - - name: Build and push full - uses: docker/build-push-action@v7 + - name: Create manifest list and push + working-directory: /tmp/digests + env: + VERSIONED: ${{ steps.tags.outputs.versioned }} + MOVING: ${{ steps.tags.outputs.moving }} + run: | + docker buildx imagetools create \ + -t "${DOCKERHUB_IMAGE}:${VERSIONED}" \ + -t "${DOCKERHUB_IMAGE}:${MOVING}" \ + -t "${GHCR_IMAGE}:${VERSIONED}" \ + -t "${GHCR_IMAGE}:${MOVING}" \ + $(printf "${GHCR_IMAGE}@sha256:%s " *) + + - name: Get pushed digest + id: digest + env: + VERSIONED: ${{ steps.tags.outputs.versioned }} + run: | + DIGEST=$(docker buildx imagetools inspect "${DOCKERHUB_IMAGE}:${VERSIONED}" \ + --format '{{json .Manifest}}' | jq -r .digest) + echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT" + + - name: Attest Docker Hub image + uses: actions/attest-build-provenance@v2 with: - context: . - file: Dockerfile.full - platforms: linux/amd64,linux/arm64,linux/arm/v7,ppc64le - push: ${{ github.event_name != 'pull_request' }} - tags: ${{ steps.tags.outputs.full }} + subject-name: ${{ env.DOCKERHUB_IMAGE }} + subject-digest: ${{ steps.digest.outputs.digest }} + push-to-registry: false + + - name: Attest GHCR image + uses: actions/attest-build-provenance@v2 + with: + subject-name: ${{ env.GHCR_IMAGE }} + subject-digest: ${{ steps.digest.outputs.digest }} + push-to-registry: false - name: Sign images - if: github.event_name != 'pull_request' env: - TAGS_MINIMAL: ${{ steps.tags.outputs.minimal }} - TAGS_BUSYBOX: ${{ steps.tags.outputs.busybox }} - TAGS_FULL: ${{ steps.tags.outputs.full }} + DIGEST: ${{ steps.digest.outputs.digest }} run: | - IFS=',' read -ra TAGS <<< "$TAGS_MINIMAL,$TAGS_BUSYBOX,$TAGS_FULL" - for tag in "${TAGS[@]}"; do - cosign sign --yes "$tag" - done + cosign sign --yes "${DOCKERHUB_IMAGE}@${DIGEST}" + cosign sign --yes "${GHCR_IMAGE}@${DIGEST}" +