diff --git a/.github/workflows/sconify.yml b/.github/workflows/sconify.yml new file mode 100644 index 0000000..22245b0 --- /dev/null +++ b/.github/workflows/sconify.yml @@ -0,0 +1,224 @@ +name: Build, Test and Push Docker Image + +on: + workflow_call: + inputs: + docker-registry: + description: "Docker registry of docker image to sconify" + default: "docker.io" + type: string + docker-username: + description: "Docker registry username" + type: string + required: true + image-name: + description: "Name of docker image to sconify" + type: string + required: true + image-tag: + description: "Tag of docker image to sconify" + type: string + required: true + scontain-username: + description: "Scontain registry username" + type: string + required: true + sconify-version: + description: "Version of the sconify image to use" + type: string + required: true + binary: + description: "Path of the binary to use" + type: string + required: true + command: + description: "Command to execute (default: ENTRYPOINT + CMD of native image)" + type: string + binary-fs: + description: "Embed the file system into the binary via Scone binary file system (default: false)" + type: boolean + default: false + fs-dir: + description: "Path of directories to add to the binary file system (use multiline to add multiple directories)" + type: string + fs-file: + description: "Path of files to add to the binary file system (use multiline to add multiple files)" + type: string + host-path: + description: "Host path, served directly from the host file system (use multiline to add multiple path)" + type: string + heap: + description: "Enclave heap size (default 1G)" + type: string + default: "1G" + mprotect: + description: "Scone mprotect mode (0:disable; 1:enable; default 0)" + type: number + default: 0 + dlopen: + description: "Scone dlopen mode (0:disable; 1:enable; default 0)" + type: number + default: 0 + sconify-debug: + description: "Create Scone debug image (default true)" + type: boolean + default: true + sconify-prod: + description: "Create Scone production image (default true)" + type: boolean + default: true + runner: + description: "Runner to use (overrides `runs-on`) âš ī¸ the specified runner must feature Ubuntu OS and docker CE" + type: string + default: "ubuntu-latest" + secrets: + docker-password: + description: "Docker Registry Password or Token" + required: true + scontain-password: + description: "Scontain Registry Password or Token" + required: true + scone-signing-key: + description: "Signing Key for Scone Production (not required with `sconify-prod: false`)" + required: false + outputs: + debug-image: + description: "Debug Sconified Image" + value: ${{ jobs.build.outputs.debug-image }} + debug-mrenclave: + description: "Debug Sconified Image MrEnclave Fingerprint" + value: ${{ jobs.build.outputs.debug-mrenclave }} + debug-checksum: + description: "Debug Sconified Image Checksum" + value: ${{ jobs.build.outputs.debug-checksum }} + prod-image: + description: "Prod Sconified Image" + value: ${{ jobs.build.outputs.prod-image }} + prod-mrenclave: + description: "Prod Sconified Image MrEnclave Fingerprint" + value: ${{ jobs.build.outputs.prod-mrenclave }} + prod-checksum: + description: "Prod Sconified Image Checksum" + value: ${{ jobs.build.outputs.prod-checksum }} + +jobs: + build: + runs-on: ${{ inputs.runner }} + outputs: + debug-image: ${{ steps.push-debug.outputs.image }} + debug-mrenclave: ${{ steps.push-debug.outputs.mrenclave }} + debug-checksum: ${{ steps.push-debug.outputs.checksum }} + prod-image: ${{ steps.push-prod.outputs.image }} + prod-mrenclave: ${{ steps.push-prod.outputs.mrenclave }} + prod-checksum: ${{ steps.push-prod.outputs.checksum }} + steps: + - name: Create Temporary Directory + run: mkdir -p ${{github.workspace}}/tmp + + - name: Prepare Sconify Command + id: prepare-command + run: | + FROM_IMAGE=${{ inputs.docker-registry }}/${{ inputs.image-name }}:${{ inputs.image-tag }} + DEBUG_IMAGE=$FROM_IMAGE-scone-debug-${{ inputs.sconify-version }} + echo "debug-image=$DEBUG_IMAGE" + echo "debug-image=$DEBUG_IMAGE" >> "$GITHUB_OUTPUT" + PROD_IMAGE=$FROM_IMAGE-scone-prod-${{ inputs.sconify-version }} + echo "prod-image=$PROD_IMAGE" + echo "prod-image=$PROD_IMAGE" >> "$GITHUB_OUTPUT" + SCONIFY_CMD="sconify_iexec" + # REQUIRED: + # --from + SCONIFY_CMD+=" --from=$FROM_IMAGE" + # --to will be added later on + # --binary + SCONIFY_CMD+=" --binary=${{ inputs.binary }}" + # OPTIONAL: + # --command option + [[ -n '${{ inputs.command }}' ]] && SCONIFY_CMD+=" --command=${{ inputs.command }}" + # --host-path variadic option + while IFS= read -r line; do [[ -n "$line" ]] && SCONIFY_CMD+=" --host-path=$line" ; done <<< '${{ inputs.host-path }}' + # BINARY FILE SYSTEM (binary fs): + # --binary-fs option + if ${{ inputs.binary-fs }}; then SCONIFY_CMD+=" --binary-fs"; fi + # --fs-dir variadic option + while IFS= read -r line; do [[ -n "$line" ]] && SCONIFY_CMD+=" --fs-dir=$line" ; done <<< '${{ inputs.fs-dir }}' + # --fs-file variadic option + while IFS= read -r line; do [[ -n "$line" ]] && SCONIFY_CMD+=" --file=$line" ; done <<< '${{ inputs.fs-file }}' + # SCONE ENV VARS: + # --heap option + [[ -n '${{ inputs.heap }}' ]] && SCONIFY_CMD+=" --heap=${{ inputs.heap }}" + # --dlopen option + [[ -n '${{ inputs.dlopen }}' ]] && SCONIFY_CMD+=" --dlopen=${{ inputs.dlopen }}" + # --mprotect option + [[ -n '${{ inputs.mprotect }}' ]] && SCONIFY_CMD+=" --mprotect=${{ inputs.mprotect }}" + # DEBUG + # --verbose --no-color options + SCONIFY_CMD+=" --verbose --no-color" + echo "sconify-base-command=$SCONIFY_CMD" + echo "sconify-base-command=$SCONIFY_CMD" >> "$GITHUB_OUTPUT" + + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + registry: ${{ inputs.docker-registry }} + username: ${{ inputs.docker-username }} + password: ${{ secrets.docker-password }} + + - name: Login to Scontain Docker Registry + uses: docker/login-action@v3 + with: + registry: "registry.scontain.com" + username: ${{ inputs.scontain-username }} + password: ${{ secrets.scontain-password }} + + - name: Pull Image to Sconify + run: docker pull ${{ inputs.docker-registry }}/${{ inputs.image-name }}:${{ inputs.image-tag }} + + - name: Pull Sconify Image + run: docker pull registry.scontain.com/scone-production/iexec-sconify-image:${{ inputs.sconify-version }} + + - name: Sconify Image Debug + if: ${{ inputs.sconify-debug }} + run: | + docker run \ + --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + registry.scontain.com/scone-production/iexec-sconify-image:${{ inputs.sconify-version }} \ + ${{ steps.prepare-command.outputs.sconify-base-command }} \ + --to=${{ steps.prepare-command.outputs.debug-image }} + + - name: Push Debug Image + if: ${{ inputs.sconify-debug }} + id: push-debug + run: | + docker push ${{ steps.prepare-command.outputs.debug-image }} + echo "image=${{ steps.prepare-command.outputs.debug-image }}" >> "$GITHUB_OUTPUT" + echo "checksum=0x$(docker image inspect ${{ steps.prepare-command.outputs.debug-image }} | jq .[0].RepoDigests[0] | sed 's/"//g' | awk -F '@sha256:' '{print $2}')" >> "$GITHUB_OUTPUT" + echo "mrenclave=$(docker run --rm -e SCONE_HASH=1 ${{ steps.prepare-command.outputs.debug-image }})" >> "$GITHUB_OUTPUT" + + - name: Sconify Image Prod + if: ${{ inputs.sconify-prod }} + run: | + mkdir -p ${{github.workspace}}/tmp/sig + echo "${{ secrets.scone-signing-key }}" > ${{github.workspace}}/tmp/sig/enclave-key.pem + docker run \ + --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v ${{github.workspace}}/tmp/sig/enclave-key.pem:/sig/enclave-key.pem \ + registry.scontain.com/scone-production/iexec-sconify-image:${{ inputs.sconify-version }} \ + ${{ steps.prepare-command.outputs.sconify-base-command }} \ + --to=${{ steps.prepare-command.outputs.prod-image }} \ + --scone-signer=/sig/enclave-key.pem + + - name: Push Prod Image + if: ${{ inputs.sconify-prod }} + id: push-prod + run: | + docker push ${{ steps.prepare-command.outputs.prod-image }} + echo "image=${{ steps.prepare-command.outputs.prod-image }}" >> "$GITHUB_OUTPUT" + echo "checksum=0x$(docker image inspect ${{ steps.prepare-command.outputs.prod-image }} | jq .[0].RepoDigests[0] | sed 's/"//g' | awk -F '@sha256:' '{print $2}')" >> "$GITHUB_OUTPUT" + echo "mrenclave=$(docker run --rm -e SCONE_HASH=1 ${{ steps.prepare-command.outputs.prod-image }})" >> "$GITHUB_OUTPUT" + + - name: Clean Temporary Directory + if: always() + run: rm -rf ${{github.workspace}}/tmp diff --git a/sconify/README.md b/sconify/README.md new file mode 100644 index 0000000..be5324b --- /dev/null +++ b/sconify/README.md @@ -0,0 +1,157 @@ +# Sconify - Reusable Workflow Documentation 🚀 + +## Overview 🌟 + +This reusable GitHub Actions workflow automates the process of sconifying a Docker image. It is configurable via inputs for the Sconification options and secrets for docker registries credentials and production enclave signing key. + +The workflow performs the following actions: + +- **Create Temporary Directory** +- **Prepare Sconify Command** and args +- **Login to Docker Registry** +- **Login to Scontain Docker Registry** +- **Pull Image to Sconify** from Docker Registry +- **Pull Sconify Image** from Scontain Docker Registry +- [unless input `sconify-debug: false`] + - **Sconify Image Debug** + - **Push Debug Image** to Docker Registry and prepare outputs (`debug-image`,`debug-mrenclave`,`debug-checksum`) +- [unless input `sconify-prod: false`] + - **Sconify Image Prod** using scone-signing-key stored in the Temporary Directory + - **Push Prod Image** to Docker Registry and prepare outputs (`prod-image`,`prod-mrenclave`,`prod-checksum`) +- **Clean Temporary Directory** always + +## Workflow Inputs đŸ› ī¸ + +| **Input** | **Description** | **Required** | **Default** | +| --------------------- | -------------------------------------------------------------------------------------------------------- | ------------ | -------------------------------- | +| **docker-registry** | Docker registry of docker image to sconify | No | docker.io | +| **docker-username** | Docker registry username | Yes | - | +| **image-name** | Name of docker image to sconify | Yes | - | +| **image-tag** | Tag of docker image to sconify | Yes | - | +| **scontain-username** | Scontain registry username | Yes | - | +| **sconify-version** | Version of the sconify image to use | Yes | - | +| **binary** | [SCONE] Path of the binary to use | Yes | - | +| **command** | [SCONE] Command to execute | No | ENTRYPOINT + CMD of native image | +| **binary-fs** | [SCONE] Embed the file system into the binary via Scone binary file system | No | false | +| **fs-dir** | [SCONE] Path of directories to add to the binary file system (use multiline to add multiple directories) | No | - | +| **fs-file** | [SCONE] Path of files to add to the binary file system (use multiline to add multiple files) | No | - | +| **host-path** | [SCONE] Host path, served directly from the host file system (use multiline to add multiple path) | No | - | +| **heap** | [SCONE] Enclave heap size | No | 1G | +| **dlopen** | [SCONE] Scone dlopen mode (0:disable; 1:enable) | No | 0 | +| **mprotect** | [SCONE] Scone mprotect mode (0:disable; 1:enable) | No | 0 | + +| **sconify-debug** | Create Scone debug image | No | true | +| **sconify-prod** | Create Scone production image | No | true | +| **runner** | Runner to use (overrides `runs-on`) âš ī¸ the specified runner must feature Ubuntu OS and docker CE | No | ubuntu-latest | + +> â„šī¸ for more details about [SCONE] options see [Scone's documentation](https://sconedocs.github.io/ee_sconify_image/#all-supported-options) + +### Secrets 🔐 + +| **Secret** | **Description** | **Required** | +| --------------------- | ----------------------------------------------- | --------------------------------------- | +| **docker-password** | Docker Registry Password or Token | Yes | +| **scontain-password** | Scontain Registry Password or Token | Yes | +| **scone-signing-key** | Signing Key for Scone Production (PEM RSA-3072) | Yes unless `inputs.sconify-prod: false` | + +### Outputs 📤 + +| **Output** | **Description** | +| ------------------- | ---------------------------------------------------------------------------------- | +| **debug-image** | Debug Sconified Image (unless `inputs.sconify-debug: false`) | +| **debug-mrenclave** | Debug Sconified Image MrEnclave Fingerprint (unless `inputs.sconify-debug: false`) | +| **debug-checksum** | Debug Sconified Image Checksum (unless `inputs.sconify-debug: false`) | +| **prod-image** | Prod Sconified Image (unless `inputs.sconify-prod: false`) | +| **prod-mrenclave** | Prod Sconified Image MrEnclave Fingerprint (unless `inputs.sconify-prod: false`) | +| **prod-checksum** | Prod Sconified Image Checksum (unless `inputs.sconify-prod: false`) | + +## How to Use This Reusable Workflow 🔄 + +1. **Save the Workflow File** + This workflow is already saved as `.github/workflows/sconify.yml` in the repository. 💾 + +2. **Call the Reusable Workflow** + In another workflow file (e.g., triggered manually or by a release), invoke this reusable workflow like so: + +```yaml +name: Sconify iApp + +on: + workflow_dispatch: + inputs: + image-name: + required: true + type: string + image-tag: + required: true + type: string + sconify-debug: + type: boolean + default: true + sconify-prod: + type: boolean + default: true + +jobs: + sconify: + uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/sconify.yml@feat/sconify + with: + # runner: your-runner-here âš ī¸ control the runner used in the workflow to match your requirements + image-name: ${{ inputs.image-name }} + image-tag: ${{ inputs.image-tag }} + sconify-debug: ${{ inputs.sconify-debug }} + sconify-prod: ${{ inputs.sconify-prod }} + docker-registry: docker.io + sconify-version: 5.9.0-v15 + binary: /usr/local/bin/node + command: node /app/src/app.js + host-path: | + /etc/hosts + /etc/resolv.conf + binary-fs: true + fs-dir: /app + heap: 1G + dlopen: 1 + mprotect: 1 + docker-username: ${{ vars.DOCKER_USERNAME }} + scontain-username: ${{ vars.SCONTAIN_USERNAME }} + secrets: + docker-password: ${{ secrets.DOCKER_TOKEN }} + scontain-password: ${{ secrets.SCONTAIN_TOKEN }} + scone-signing-key: ${{ secrets.SCONE_SIGNING_KEY }} + + use-sconify-output: + # usually you want to deploy the sconified image as an iExec app using the sconify job outputs + runs-on: ubuntu-latest + needs: sconify + steps: + - run: | + echo "DEBUG IMAGE INFO: image=${{ needs.sconify.outputs.debug-image }} | checksum=${{ needs.sconify.outputs.debug-checksum }} | mrenclave=${{ needs.sconify.outputs.debug-mrenclave }}" + echo "PROD IMAGE INFO: image=${{ needs.sconify.outputs.prod-image }} | checksum=${{ needs.sconify.outputs.prod-checksum }} | mrenclave=${{ needs.sconify.outputs.prod-mrenclave }}" +``` + +3. **Configure Variables** + Ensure that the following variables are added to your repository's settings: + + - `DOCKER_USERNAME`: Your Docker Registry username + - `SCONTAIN_USERNAME`: Your Scontain username + + NB: Beware if you choose to use secrets to store registries usernames; + registries usernames can appear in sconified image names outputted as `outputs.debug-image` and `outputs.prod-image`, in such a case GitHub Actions blanks the outputs with this waring: + + > Skip output 'prod-image' since it may contain secret. + + > Skip output 'debug-image' since it may contain secret. + +4. **Configure Secrets** + Ensure that the following secrets are added to your repository's settings: + - `DOCKER_PASSWORD`: Your Docker Registry password or access token + - `SCONTAIN_PASSWORD`: Your Scontain password or access token + - `SCONE_SIGNING_KEY`: The key to use for signing Scone Prod applications + +## Prerequisites 📋 + +1. **Read/Write access to the image to sconify** + +2. **Read access to Scontain's `iexec-sconify-image` image**: + - You must have a Scontain account with access to the `scone-production/iexec-sconify-image` image.