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 sign: required: true type: boolean key_name: required: false type: string default: "dangerzone-tests" key_cache: required: false type: string default: "v1-keypair-${{ github.ref_name }}" # unique for the branch / PR secrets: registry_token: required: true 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 }} tag: ${{ steps.params.outputs.tag }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Compute image parameters id: params run: | source Dockerfile.env 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=${TAG}" >> $GITHUB_OUTPUT echo "full_image_name=${FULL_IMAGE_NAME}" >> $GITHUB_OUTPUT echo "buildkit_image=${BUILDKIT_IMAGE}" >> $GITHUB_OUTPUT build: name: Build ${{ matrix.platform.name }} image runs-on: ${{ matrix.platform.runs-on }} needs: - prepare outputs: debian_archive_date: ${{ needs.prepare.outputs.debian_archive_date }} source_date_epoch: ${{ needs.prepare.outputs.source_date_epoch }} image: ${{ needs.prepare.outputs.image }} tag: ${{ needs.prepare.outputs.tag }} strategy: fail-fast: false matrix: platform: - runs-on: "ubuntu-24.04" name: "linux/amd64" - runs-on: "ubuntu-24.04-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=${{ needs.prepare.outputs.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 }}/${{ inputs.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: - build outputs: debian_archive_date: ${{ needs.build.outputs.debian_archive_date }} source_date_epoch: ${{ needs.build.outputs.source_date_epoch }} image: ${{ needs.build.outputs.image }} tag: ${{ needs.build.outputs.tag }} 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.build.outputs.image }}@sha256:%s ' *) docker buildx imagetools create -t ${{ needs.build.outputs.image }} ${DIGESTS} - name: Inspect image id: image run: | # Inspect the image docker buildx imagetools inspect ${{ needs.build.outputs.image }} docker buildx imagetools inspect ${{ needs.build.outputs.image }} --format "{{json .Manifest}}" > manifest # Calculate and print the digests digest_root=$(jq -r .digest manifest) digest_amd64=$(jq -r '.manifests[] | select(.platform.architecture=="amd64") | .digest' manifest) digest_arm64=$(jq -r '.manifests[] | select(.platform.architecture=="arm64") | .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: - merge strategy: matrix: manifest_type: - 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.1.0 with: digest: ${{ needs.merge.outputs[format('digest_{0}', matrix.manifest_type)] }} image: ${{ needs.merge.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: - merge runs-on: ${{ matrix.platform.runs-on }} strategy: fail-fast: false matrix: platform: - runs-on: "ubuntu-24.04" name: "amd64" - runs-on: "ubuntu-24.04-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.merge.outputs.debian_archive_date }} \ --platform \ linux/${{ matrix.platform.name }} \ ${{ needs.merge.outputs[format('digest_{0}', matrix.platform.name)] }} sign: if: ${{ inputs.sign }} runs-on: "ubuntu-latest" env: COSIGN_PASSWORD: "password" COSIGN_YES: true needs: - merge # outputs: add signature location ? steps: - name: Install Cosign uses: sigstore/cosign-installer@d7d6bc7722e3daa8354c50bcb52f4837da5e9b6a with: cosign-release: 'v2.5.0' - name: Check install run: cosign version - name: Generate keypair run: |- cosign generate-key-pair --output-key-prefix="${{ inputs.key_name }}" - name: Cache keypair uses: actions/cache@v4 with: path: "${{ inputs.key_name }}.*" key: ${{ inputs.key_cache }} enableCrossOsArchive: true - name: Login to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ inputs.registry_user }} password: ${{ secrets.registry_token }} - name: Sign container run: |- export IMAGE_URI="${{ inputs.registry }}/${{ inputs.image_name }}:${{ needs.merge.outputs.tag }}@${{ needs.merge.outputs.digest_root }}" cosign sign -d --yes --key=${{ inputs.key_name }}.key "$IMAGE_URI" shell: bash