diff options
| author | Linus Färnstrand <linus@mullvad.net> | 2022-11-30 14:46:09 +0100 |
|---|---|---|
| committer | Linus Färnstrand <linus@mullvad.net> | 2022-11-30 14:46:09 +0100 |
| commit | 62371bb6c45f8606595b67c6953a1c2ed8b89d34 (patch) | |
| tree | 8a2d85bea4bd1e6e172b88ed8364b2852b924d5c | |
| parent | 4c83a396fda3c70bfa872e4b0d88dac1297d21dc (diff) | |
| parent | bb179fae8829547e8b077a844a99cb45ed9410ed (diff) | |
| download | mullvadvpn-62371bb6c45f8606595b67c6953a1c2ed8b89d34.tar.xz mullvadvpn-62371bb6c45f8606595b67c6953a1c2ed8b89d34.zip | |
Merge branch 'add-container-sigstore'
| -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 | ||||
| -rw-r--r-- | building/mullvad-app-container-signing.asc | 21 |
5 files changed, 188 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 "***********************" diff --git a/building/mullvad-app-container-signing.asc b/building/mullvad-app-container-signing.asc new file mode 100644 index 0000000000..69d250b0b3 --- /dev/null +++ b/building/mullvad-app-container-signing.asc @@ -0,0 +1,21 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEY2pmvhYJKwYBBAHaRw8BAQdAOOJocdP7MXSesmmMuA7JXxo0ZVV7aiT1ZFxo +oGKmAcS0NU11bGx2YWQgVlBOIGFwcCAoQ29udGFpbmVyIHNpZ25pbmcpIDxhcHBA +bXVsbHZhZC5uZXQ+iIoEExYKADICGwMCF4AWIQQeVRaH1n9f2CC+8sTXwX+HoNPS +FQUCY4X7/AILCQIVCgUWAgMBAAIeBQAKCRDXwX+HoNPSFRSUAPwIcRuvdzVqufOj +UsbK+qEz9WTqR2QMSGU2LPtkFJyygAEAzhaAFgklONznxpPx0BWI7/hCkYmg2siI +/KfdA3wkkgWJAjMEEAEIAB0WIQShGYcC/D4KCamuW3XVodTyZt6N3wUCY4X68wAK +CRDVodTyZt6N39SuD/9JkAAT06imUiB+bKs6BbdRFPnztAxcQPJztbAmIdch2Nly +6gQyxnrbf0voTBcFs0idRyzYcCgliq+7SqHBgOHtGnOgNH1IDNkEnU2lF4kioGnY +w+6hETBb5KH/ywze+p1fEe8+Id9l5mfnZJwvciSpMKc3CQKMSGzPgPSdTjgN4TtF +U6ckwd3QAVMl1lOp6enznnLdtjnWlQU/qFI97YY7r7IdkOhVeI5oENMBPJWjlEZr +9wHBedm0ZatplW16myikJZJ/VCGU0HLRNz+Ki6FEvV4VcxLKSlCkBwdk3mjpb++r +CKqgyEZOH47icK3blAbZvYZVHSl4P5BMW67YLUAp627PpWllyJDKt5Tt+gbGJ6Q3 +Jwaw6/eDObEVArV/bnNnk3T07IcDotlcm0HUIUJeVKLuBQOsyo6zyRW6iCzwbgHN +Bt8N99DQE2d9nySkzGDGZOmHjRLMF/q7NPbzdI0bwoE3D8S8w1VSKVqmGbkfR8W8 +a7ZibDR3W7x6ofi9owFd9XEM/SQGDU99FxvLLHUy//2Ox/iPxdUWygzWchneDaFL +MnGJ5GEgsfz7zdzBjXcYxU3jEdEHzlkiRcdqM8mjTM+1xmOT3ed30sf04uHHYzu6 +Gm/EaXoAwZGtr/HZ8teR6yEr+R57JrICOVOLHYbjSprJmOWBcskMefpiwK9EUA== +=0W2k +-----END PGP PUBLIC KEY BLOCK----- |
