diff options
| author | Mullvad build server <app@mullvad.net> | 2022-11-01 16:14:23 +0100 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2022-11-30 14:45:46 +0100 |
| commit | bb179fae8829547e8b077a844a99cb45ed9410ed (patch) | |
| tree | 8a2d85bea4bd1e6e172b88ed8364b2852b924d5c | |
| parent | 5cb79e2a278c9e7ee94e27f2fa63e1cd83e42f82 (diff) | |
| download | mullvadvpn-bb179fae8829547e8b077a844a99cb45ed9410ed.tar.xz mullvadvpn-bb179fae8829547e8b077a844a99cb45ed9410ed.zip | |
Add initial build-and-publish.sh script and readme for build containers
| -rw-r--r-- | .github/workflows/verify-locked-down-signatures.yml | 4 | ||||
| -rw-r--r-- | building/Dockerfile (renamed from Dockerfile) | 0 | ||||
| -rw-r--r-- | building/README.md | 68 | ||||
| -rwxr-xr-x | building/build-and-publish.sh | 95 |
4 files changed, 167 insertions, 0 deletions
diff --git a/.github/workflows/verify-locked-down-signatures.yml b/.github/workflows/verify-locked-down-signatures.yml index 9ca5088e19..0912fa6104 100644 --- a/.github/workflows/verify-locked-down-signatures.yml +++ b/.github/workflows/verify-locked-down-signatures.yml @@ -11,6 +11,10 @@ on: - ios/MullvadVPN.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved - android/gradle/verification-metadata.xml - android/gradle/wrapper/gradle-wrapper.properties + - building/build-and-publish.sh + - building/mullvad-app-container-signing.asc + - building/linux-container-image-tag.txt + - building/android-container-image-tag.txt workflow_dispatch: jobs: verify-signatures: diff --git a/Dockerfile b/building/Dockerfile index e036834cc1..e036834cc1 100644 --- a/Dockerfile +++ b/building/Dockerfile diff --git a/building/README.md b/building/README.md new file mode 100644 index 0000000000..c85776d368 --- /dev/null +++ b/building/README.md @@ -0,0 +1,68 @@ +# Mullvad VPN app build containers + +Substitute `${repo}` with the actual absolute path to this repository + +## Building and publishing a container image + +These instructions describe how to set up the trusted machine that builds, signs and publishes +the container images to ghcr.io. + +If you `sudo` into a `build` account that do the builds, you need to set the permissions on the tty, +so podman can ask for the passphrase for the gpg key: +``` +realuser@server $ sudo chown build:build $(tty) +realuser@server $ sudo -u build -i +``` + + +Configure podman to store signatures when building and pushing images. `~/.config/containers/registries.d/mullvad.yaml`: +```yml +docker: + ghcr.io/mullvad: + sigstore-staging: file://${repo}/building/sigstore +``` + +Build and publish the container image. Tag it with the github hash of the current commit +``` +git checkout -b update-build-container + +./build-and-publish.sh (linux|android) + +git push # And create a PR +``` + + +## Pulling, verifying and using build images + +These instructions describe how anyone can pull the images and verify them with GPG before using them. + +Copy the Mullvad app container signing GPG key to somewhere outside the repository (so a `git pull` can't overwrite it with a malicious key): +``` +cp ${repo}/building/mullvad-app-container-signing.asc /path/to/mullvad-app-container-signing.asc +``` + +Configure a strict policy for podman when pulling from `ghcr.io/mullvad`. `~/.config/containers/policy.json`: +```json +{ + "default": [{ "type": "insecureAcceptAnything" }], + "transports": { + "docker": { + "ghcr.io/mullvad": [ + { + "type": "signedBy", + "keyType": "GPGKeys", + "keyPath": "/path/to/mullvad-app-container-signing.asc" + } + ] + } + } +} +``` + +Configure podman to fetch image signatures from the in-repo sigstore directory. `~/.config/containers/registries.d/mullvad.yaml`: +```yml +docker: + ghcr.io/mullvad: + sigstore: file://${repo}/building/sigstore +``` + diff --git a/building/build-and-publish.sh b/building/build-and-publish.sh new file mode 100755 index 0000000000..777c644d51 --- /dev/null +++ b/building/build-and-publish.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +# This script will build, sign and publish the Mullvad VPN app build image(s) +# for either linux or android, depending on the first argument. +# Please see `README.md` for setup instructions *before* running this script + +set -eu + +CONTAINER_SIGNING_KEY_FINGERPRINT=1E551687D67F5FD820BEF2C4D7C17F87A0D3D215 +REGISTRY_HOST="ghcr.io" +REGISTRY_ORG="mullvad" + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +REPO_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" +cd "$REPO_DIR" + +source "$REPO_DIR/scripts/utils/log" + +tag="$(git rev-parse --short HEAD)" + +case ${1-:""} in + linux) + container_name="mullvadvpn-app-build" + containerfile_path="$SCRIPT_DIR/Dockerfile" + container_context_dir="$REPO_DIR" + container_image_tag_path="$SCRIPT_DIR/linux-container-image-tag.txt" + ;; + android) + container_name="mullvadvpn-app-build-android" + containerfile_path="$REPO_DIR/android/docker/Dockerfile" + container_context_dir="$REPO_DIR/android/docker/" + container_image_tag_path="$SCRIPT_DIR/android-container-image-tag.txt" + ;; + *) + log_error "Invalid platform. Specify 'linux' or 'android' as first argument" + exit 1 +esac +full_container_name="$REGISTRY_HOST/$REGISTRY_ORG/$container_name" + +log_header "Building $full_container_name tagged as '$tag' and 'latest'" +podman build -f "$containerfile_path" "$container_context_dir" --no-cache \ + -t "$full_container_name:$tag" \ + -t "$full_container_name:latest" + +# Temporary directory to store image digest and signature in. +# This is a hack since two consecutive `podman push` seems +# to overwrite the signatures. We want to keep the signature +# for both the 'latest' and '$tag' tags. +tmp_signature_dir=$(mktemp -d) + +function delete_tmp_signature_dir { + rm -rf "$tmp_signature_dir" +} +trap 'delete_tmp_signature_dir' EXIT + +log_header "Pushing $full_container_name:latest" +podman push "$full_container_name:latest" \ + --sign-by $CONTAINER_SIGNING_KEY_FINGERPRINT \ + --digestfile "$tmp_signature_dir/digest_latest" + +digest=$(cat "$tmp_signature_dir/digest_latest") +log_success "Pushed image with digest $digest" +# Backup the signature so we can restore it after the second podman push later +signature_dir="$SCRIPT_DIR/sigstore/$REGISTRY_ORG/$container_name@${digest/:/=}" +if [[ -f "$signature_dir/signature-2" ]]; then + log_error "Did not expect $signature_dir/signature-2 to exist" + exit 1 +fi +cp "$signature_dir/signature-1" "$tmp_signature_dir/signature-2" + +log_header "Pushing $full_container_name:$tag" +podman push "$full_container_name:$tag" \ + --sign-by $CONTAINER_SIGNING_KEY_FINGERPRINT \ + --digestfile "$tmp_signature_dir/digest_$tag" + +if ! cmp -s "$tmp_signature_dir/digest_latest" "$tmp_signature_dir/digest_$tag"; then + log_error "Digests differ between 'latest' and '$tag' pushes" + exit 1 +fi + +cp "$tmp_signature_dir/signature-2" "$signature_dir/" + +log_info "Storing container tag to $container_image_tag_path" +echo "$tag" > "$container_image_tag_path" + +log_header "Commiting signatures and new tag name to git" +git add "$container_image_tag_path" "$signature_dir" +GPG_TTY=$(tty) git commit -S -m "Updating build container for $1 to $tag" + +log_success "***********************" +log_success "" +log_success "Done building and pushing $full_container_name with tags '$tag' and 'latest'" +log_success "Make sure to push the changes to git" +log_success "" +log_success "***********************" |
