diff --git a/.github/workflows/build-push-image.yml b/.github/workflows/build-push-image.yml new file mode 100644 index 0000000..90fbfd8 --- /dev/null +++ b/.github/workflows/build-push-image.yml @@ -0,0 +1,245 @@ +name: Build and push multi-arch container image + +on: + workflow_call: + inputs: + registry: + required: true + type: string + registry_user: + required: true + type: string + image_name: + required: true + type: string + reproduce: + required: true + type: boolean + secrets: + registry_token: + required: true + + +env: + BUILDKIT_IMAGE: "docker.io/moby/buildkit:v19.0@sha256:14aa1b4dd92ea0a4cd03a54d0c6079046ea98cd0c0ae6176bdd7036ba370cbbe" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install dev. dependencies + run: |- + sudo apt-get update + sudo apt-get install -y git python3-poetry --no-install-recommends + poetry install --only package + + - name: Verify that the Dockerfile matches the commited template and params + run: |- + cp Dockerfile Dockerfile.orig + make Dockerfile + diff Dockerfile.orig Dockerfile + + prepare: + runs-on: ubuntu-latest + outputs: + debian_archive_date: ${{ steps.params.outputs.debian_archive_date }} + source_date_epoch: ${{ steps.params.outputs.source_date_epoch }} + image: ${{ steps.params.outputs.full_image_name }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Compute image parameters + id: params + run: | + DEBIAN_ARCHIVE_DATE=$(date -u +'%Y%m%d') + SOURCE_DATE_EPOCH=$(date -u -d ${DEBIAN_ARCHIVE_DATE} +"%s") + TAG=${DEBIAN_ARCHIVE_DATE}-$(git describe --long --first-parent | tail -c +2) + FULL_IMAGE_NAME=${{ inputs.registry }}/${{ inputs.image_name }}:${TAG} + + echo "debian_archive_date=${DEBIAN_ARCHIVE_DATE}" >> $GITHUB_OUTPUT + echo "source_date_epoch=${SOURCE_DATE_EPOCH}" >> $GITHUB_OUTPUT + echo "tag=${DEBIAN_ARCHIVE_DATE}-${TAG}" >> $GITHUB_OUTPUT + echo "full_image_name=${FULL_IMAGE_NAME}" >> $GITHUB_OUTPUT + + build: + name: Build ${{ matrix.platform.name }} image + runs-on: ubuntu-24.04${{ matrix.platform.suffix }} + needs: + - prepare + strategy: + fail-fast: false + matrix: + platform: + - suffix: "" + name: "linux/amd64" + - suffix: "-arm" + name: "linux/arm64" + steps: + - uses: actions/checkout@v4 + + - name: Prepare + run: | + platform=${{ matrix.platform.name }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ inputs.registry_user }} + password: ${{ secrets.registry_token }} + + # Instructions for reproducibly building a container image are taken from: + # https://github.com/freedomofpress/repro-build?tab=readme-ov-file#build-and-push-a-container-image-on-github-actions + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: image=${{ env.BUILDKIT_IMAGE }} + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + context: ./dangerzone/ + file: Dockerfile + build-args: | + DEBIAN_ARCHIVE_DATE=${{ needs.prepare.outputs.debian_archive_date }} + SOURCE_DATE_EPOCH=${{ needs.prepare.outputs.source_date_epoch }} + provenance: false + outputs: type=image,"name=${{ inputs.registry }}/${{ needs.image_name }}",push-by-digest=true,push=true,rewrite-timestamp=true,name-canonical=true + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + echo "Image digest is: ${digest}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: + - prepare + - build + outputs: + digest_root: ${{ steps.image.outputs.digest_root }} + digest_amd64: ${{ steps.image.outputs.digest_amd64 }} + digest_arm64: ${{ steps.image.outputs.digest_arm64 }} + steps: + - uses: actions/checkout@v4 + + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ inputs.registry_user }} + password: ${{ secrets.registry_token }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: image=${{ env.BUILDKIT_IMAGE }} + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + DIGESTS=$(printf '${{ needs.prepare.outputs.image }}@sha256:%s ' *) + docker buildx imagetools create -t ${{ needs.prepare.outputs.image }} ${DIGESTS} + + - name: Inspect image + id: image + run: | + # Inspect the image + docker buildx imagetools inspect ${{ needs.prepare.outputs.image }} + docker buildx imagetools inspect ${{ needs.prepare.outputs.image }} --format "{{json .Manifest}}" > manifest + + # Calculate and print the digests + digest_root=$(jq -r .digest manifest) + digest_amd64=$(jq -r .manifests[0].digest manifest) + digest_arm64=$(jq -r .manifests[1].digest manifest) + + echo "The image digests are:" + echo " Root: $digest_root" + echo " linux/amd64: $digest_amd64" + echo " linux/arm64: $digest_arm64" + + # NOTE: Set the digests as an output because the `env` context is not + # available to the inputs of a reusable workflow call. + echo "digest_root=$digest_root" >> "$GITHUB_OUTPUT" + echo "digest_amd64=$digest_amd64" >> "$GITHUB_OUTPUT" + echo "digest_arm64=$digest_arm64" >> "$GITHUB_OUTPUT" + + # This step calls the container workflow to generate provenance and push it to + # the container registry. + provenance: + needs: + - prepare + - merge + strategy: + matrix: + digest: + - root + - amd64 + - arm64 + permissions: + actions: read # for detecting the Github Actions environment. + id-token: write # for creating OIDC tokens for signing. + packages: write # for uploading attestations. + uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0 + with: + digest: ${{ needs.merge.outputs[format('digest_{0}', matrix.digest)] }} + image: ${{ needs.prepare.outputs.image }} + registry-username: ${{ inputs.registry_user }} + secrets: + registry-password: ${{ secrets.registry_token }} + + # This step ensures that the image is reproducible + check-reproducibility: + if: ${{ inputs.reproduce }} + needs: + - prepare + - merge + runs-on: ubuntu-24.04${{ matrix.platform.suffix }} + strategy: + fail-fast: false + matrix: + platform: + - suffix: "" + name: "amd64" + - suffix: "-arm" + name: "arm64" + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Reproduce the same container image + run: | + ./dev_scripts/reproduce-image.py \ + --runtime \ + docker \ + --debian-archive-date \ + ${{ needs.prepare.outputs.debian_archive_date }} \ + --platform \ + linux/${{ matrix.platform.name }} \ + ${{ needs.merge.outputs[format('digest_{0}', matrix.platform.name)] }} diff --git a/.github/workflows/release-container-image.yml b/.github/workflows/release-container-image.yml index df23e89..37e298e 100644 --- a/.github/workflows/release-container-image.yml +++ b/.github/workflows/release-container-image.yml @@ -9,229 +9,14 @@ on: schedule: - cron: "0 0 * * *" # Run every day at 00:00 UTC. -env: - REGISTRY: ghcr.io/${{ github.repository_owner }} - REGISTRY_USER: ${{ github.actor }} - REGISTRY_PASSWORD: ${{ github.token }} - IMAGE_NAME: dangerzone/dangerzone - BUILDKIT_IMAGE: "docker.io/moby/buildkit:v19.0@sha256:14aa1b4dd92ea0a4cd03a54d0c6079046ea98cd0c0ae6176bdd7036ba370cbbe" jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install dev. dependencies - run: |- - sudo apt-get update - sudo apt-get install -y git python3-poetry --no-install-recommends - poetry install --only package - - - name: Verify that the Dockerfile matches the commited template and params - run: |- - cp Dockerfile Dockerfile.orig - make Dockerfile - diff Dockerfile.orig Dockerfile - - prepare: - runs-on: ubuntu-latest - outputs: - debian_archive_date: ${{ steps.params.outputs.debian_archive_date }} - source_date_epoch: ${{ steps.params.outputs.source_date_epoch }} - image: ${{ steps.params.outputs.full_image_name }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Compute image parameters - id: params - run: | - DEBIAN_ARCHIVE_DATE=$(date -u +'%Y%m%d') - SOURCE_DATE_EPOCH=$(date -u -d ${DEBIAN_ARCHIVE_DATE} +"%s") - TAG=${DEBIAN_ARCHIVE_DATE}-$(git describe --long --first-parent | tail -c +2) - FULL_IMAGE_NAME=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${TAG} - - echo "debian_archive_date=${DEBIAN_ARCHIVE_DATE}" >> $GITHUB_OUTPUT - echo "source_date_epoch=${SOURCE_DATE_EPOCH}" >> $GITHUB_OUTPUT - echo "tag=${DEBIAN_ARCHIVE_DATE}-${TAG}" >> $GITHUB_OUTPUT - echo "full_image_name=${FULL_IMAGE_NAME}" >> $GITHUB_OUTPUT - - build: - name: Build ${{ matrix.platform.name }} image - runs-on: ubuntu-24.04${{ matrix.platform.suffix }} - needs: - - prepare - strategy: - fail-fast: false - matrix: - platform: - - suffix: "" - name: "linux/amd64" - - suffix: "-arm" - name: "linux/arm64" - steps: - - uses: actions/checkout@v4 - - - name: Prepare - run: | - platform=${{ matrix.platform.name }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - # Instructions for reproducibly building a container image are taken from: - # https://github.com/freedomofpress/repro-build?tab=readme-ov-file#build-and-push-a-container-image-on-github-actions - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver-opts: image=${{ env.BUILDKIT_IMAGE }} - - - name: Build and push by digest - id: build - uses: docker/build-push-action@v6 - with: - context: ./dangerzone/ - file: Dockerfile - build-args: | - DEBIAN_ARCHIVE_DATE=${{ needs.prepare.outputs.debian_archive_date }} - SOURCE_DATE_EPOCH=${{ needs.prepare.outputs.source_date_epoch }} - provenance: false - outputs: type=image,"name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}",push-by-digest=true,push=true,rewrite-timestamp=true,name-canonical=true - cache-from: type=gha - cache-to: type=gha,mode=max - - - name: Export digest - run: | - mkdir -p ${{ runner.temp }}/digests - digest="${{ steps.build.outputs.digest }}" - touch "${{ runner.temp }}/digests/${digest#sha256:}" - echo "Image digest is: ${digest}" - - - name: Upload digest - uses: actions/upload-artifact@v4 - with: - name: digests-${{ env.PLATFORM_PAIR }} - path: ${{ runner.temp }}/digests/* - if-no-files-found: error - retention-days: 1 - - merge: - runs-on: ubuntu-latest - needs: - - prepare - - build - outputs: - digest_root: ${{ steps.image.outputs.digest_root }} - digest_amd64: ${{ steps.image.outputs.digest_amd64 }} - digest_arm64: ${{ steps.image.outputs.digest_arm64 }} - steps: - - uses: actions/checkout@v4 - - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: ${{ runner.temp }}/digests - pattern: digests-* - merge-multiple: true - - - name: Login to GHCR - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - driver-opts: image=${{ env.BUILDKIT_IMAGE }} - - - name: Create manifest list and push - working-directory: ${{ runner.temp }}/digests - run: | - DIGESTS=$(printf '${{ needs.prepare.outputs.image }}@sha256:%s ' *) - docker buildx imagetools create -t ${{ needs.prepare.outputs.image }} ${DIGESTS} - - - name: Inspect image - id: image - run: | - # Inspect the image - docker buildx imagetools inspect ${{ needs.prepare.outputs.image }} - docker buildx imagetools inspect ${{ needs.prepare.outputs.image }} --format "{{json .Manifest}}" > manifest - - # Calculate and print the digests - digest_root=$(jq -r .digest manifest) - digest_amd64=$(jq -r .manifests[0].digest manifest) - digest_arm64=$(jq -r .manifests[1].digest manifest) - - echo "The image digests are:" - echo " Root: $digest_root" - echo " linux/amd64: $digest_amd64" - echo " linux/arm64: $digest_arm64" - - # NOTE: Set the digests as an output because the `env` context is not - # available to the inputs of a reusable workflow call. - echo "digest_root=$digest_root" >> "$GITHUB_OUTPUT" - echo "digest_amd64=$digest_amd64" >> "$GITHUB_OUTPUT" - echo "digest_arm64=$digest_arm64" >> "$GITHUB_OUTPUT" - - # This step calls the container workflow to generate provenance and push it to - # the container registry. - provenance: - needs: - - prepare - - merge - strategy: - matrix: - digest: - - root - - amd64 - - arm64 - permissions: - actions: read # for detecting the Github Actions environment. - id-token: write # for creating OIDC tokens for signing. - packages: write # for uploading attestations. - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.0.0 + build-push-image: + uses: ./.github/workflows/build-push-image.yml with: - digest: ${{ needs.merge.outputs[format('digest_{0}', matrix.digest)] }} - image: ${{ needs.prepare.outputs.image }} - registry-username: ${{ github.actor }} + registry: ghcr.io/${{ github.repository_owner }} + registry_user: ${{ github.actor }} + image_name: dangerzone/dangerzone + reproduce: true secrets: - registry-password: ${{ secrets.GITHUB_TOKEN }} - - # This step ensures that the image is reproducible - check-reproducibility: - needs: - - prepare - - merge - runs-on: ubuntu-24.04${{ matrix.platform.suffix }} - strategy: - fail-fast: false - matrix: - platform: - - suffix: "" - name: "amd64" - - suffix: "-arm" - name: "arm64" - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Reproduce the same container image - run: | - ./dev_scripts/reproduce-image.py \ - --runtime \ - docker \ - --debian-archive-date \ - ${{ needs.prepare.outputs.debian_archive_date }} \ - --platform \ - linux/${{ matrix.platform.name }} \ - ${{ needs.merge.outputs[format('digest_{0}', matrix.platform.name)] }} + registry_token : ${{ github.token }}