diff --git a/.github/workflows/main.yml b/.github/workflows/docker.yml similarity index 72% rename from .github/workflows/main.yml rename to .github/workflows/docker.yml index 148d4e3..53b3324 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/docker.yml @@ -21,9 +21,9 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - - run: ./build-with-docker.sh + - run: ./build-with.sh docker - - run: sh -exc 'if [ $(sha256sum dist/qubes-firewall.xen | cut -d " " -f 1) = $(grep "SHA2 last known" build-with-docker.sh | rev | cut -d ":" -f 1 | rev | cut -d "\"" -f 1 | tr -d " ") ]; then echo "SHA256 MATCHES"; else exit 42; fi' + - run: sh -exc 'if [ $(sha256sum dist/qubes-firewall.xen | cut -d " " -f 1) = $(grep "SHA2 last known" build-with.sh | rev | cut -d ":" -f 1 | rev | cut -d "\"" -f 1 | tr -d " ") ]; then echo "SHA256 MATCHES"; else exit 42; fi' - name: Upload Artifact uses: actions/upload-artifact@v3 diff --git a/.github/workflows/podman.yml b/.github/workflows/podman.yml new file mode 100644 index 0000000..fba19eb --- /dev/null +++ b/.github/workflows/podman.yml @@ -0,0 +1,32 @@ +name: Main workflow + +on: + pull_request: + push: + schedule: + # Prime the caches every Monday + - cron: 0 1 * * MON + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - run: ./build-with.sh podman + + - run: sh -exc 'if [ $(sha256sum dist/qubes-firewall.xen | cut -d " " -f 1) = $(grep "SHA2 last known" build-with.sh | rev | cut -d ":" -f 1 | rev | cut -d "\"" -f 1 | tr -d " ") ]; then echo "SHA256 MATCHES"; else exit 42; fi' + + - name: Upload Artifact + uses: actions/upload-artifact@v3 + with: + name: mirage-firewall.tar.bz2 + path: mirage-firewall.tar.bz2 diff --git a/Dockerfile b/Dockerfile index 0c3c0c8..f959047 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,35 @@ # Pin the base image to a specific hash for maximum reproducibility. # It will probably still work on newer images, though, unless an update # changes some compiler optimisations (unlikely). -# bookworm-slim -FROM debian@sha256:07c6cb2ae86479dcc1942a89b0a1f4049b6e9415f7de327ff641aed58b8e3100 +# bookworm-slim taken from https://hub.docker.com/_/debian/tags?page=1&name=bookworm-slim +FROM debian@sha256:ea5ad531efe1ac11ff69395d032909baf423b8b88e9aade07e11b40b2e5a1338 +# install remove default packages repository +RUN rm /etc/apt/sources.list.d/debian.sources # and set the package source to a specific release too -RUN printf "deb [check-valid-until=no] http://snapshot.notset.fr/archive/debian/20230418T024659Z bookworm main" > /etc/apt/sources.list +# taken from https://snapshot.debian.org/archive/debian +RUN printf "deb [check-valid-until=no] http://snapshot.debian.org/archive/debian/20231107T084929Z bookworm main\n" > /etc/apt/sources.list +# taken from https://snapshot.debian.org/archive/debian-security/ +RUN printf "deb [check-valid-until=no] http://snapshot.debian.org/archive/debian-security/20231108T004541Z bookworm-security main\n" >> /etc/apt/sources.list RUN apt update && apt install --no-install-recommends --no-install-suggests -y wget ca-certificates git patch unzip bzip2 make gcc g++ libc-dev RUN wget -O /usr/bin/opam https://github.com/ocaml/opam/releases/download/2.1.5/opam-2.1.5-i686-linux && chmod 755 /usr/bin/opam +# taken from https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh +RUN test `sha512sum /usr/bin/opam | cut -d' ' -f1` = \ +"38802b3079eeceb27aab3465bfd0f9f05a710dccf9487eb35fa2c02fbaf9a0659e1447aa19dd36df9cd01f760229de28c523c08c1c86a3aa3f5e25dbe7b551dd" || exit ENV OPAMROOT=/tmp ENV OPAMCONFIRMLEVEL=unsafe-yes # Pin last known-good version for reproducible builds. # Remove this line (and the base image pin above) if you want to test with the # latest versions. -RUN opam init --disable-sandboxing -a --bare https://github.com/ocaml/opam-repository.git#28b35f67988702df5018fbf30d1c725734425670 +# taken from https://github.com/ocaml/opam-repository +RUN opam init --disable-sandboxing -a --bare https://github.com/ocaml/opam-repository.git#d1a8bf040fbb2c81ddb2612f1a49a471a06083dc RUN opam switch create myswitch 4.14.1 RUN opam exec -- opam install -y mirage opam-monorepo ocaml-solo5 RUN mkdir /tmp/orb-build ADD config.ml /tmp/orb-build/config.ml WORKDIR /tmp/orb-build -CMD opam exec -- sh -exc 'mirage configure -t xen --allocation-policy=best-fit && make depend && make tar' +CMD opam exec -- sh -exc 'mirage configure -t xen --extra-repos=\ +opam-overlays:https://github.com/dune-universe/opam-overlays.git#91a371754a2c9f4febbb6c7bb039649ad49a3c13,\ +mirage-overlays:https://github.com/dune-universe/mirage-opam-overlays.git#05f1c1823d891ce4d8adab91f5db3ac51d86dc0b \ +--allocation-policy=best-fit && make depend && make tar' diff --git a/Makefile.user b/Makefile.user index c8a1d5d..00890f6 100644 --- a/Makefile.user +++ b/Makefile.user @@ -6,7 +6,7 @@ tar: build cp dist/qubes-firewall.xen _build/mirage-firewall/vmlinuz touch _build/mirage-firewall/modules.img cat /dev/null | gzip -n > _build/mirage-firewall/initramfs - tar cjf mirage-firewall.tar.bz2 -C _build --mtime=./build-with-docker.sh mirage-firewall + tar cjf mirage-firewall.tar.bz2 -C _build --mtime=./build-with.sh mirage-firewall sha256sum mirage-firewall.tar.bz2 > mirage-firewall.sha256 fetchmotron: qubes_firewall.xen diff --git a/README.md b/README.md index 0dc963d..2a37c53 100644 --- a/README.md +++ b/README.md @@ -13,42 +13,51 @@ See the [Deploy](#deploy) section below for installation instructions. ## Build from source -Note: The most reliable way to build is using Docker. -Fedora 35 works well for this and Debian 11 also works, but you'll need to follow the instructions at [docker.com][debian-docker] to get Docker +Note: The most reliable way to build is using Docker or Podman. +Fedora 38 works well for this, Debian 12 also works, but you'll need to follow the instructions at [docker.com][debian-docker] to get Docker (don't use Debian's version). -Create a new Fedora-35 AppVM (or reuse an existing one). In the Qube's Settings (Basic / Disk storage), increase the private storage max size from the default 2048 MiB to 4096 MiB. Open a terminal. +Create a new Fedora-38 AppVM (or reuse an existing one). In the Qube's Settings (Basic / Disk storage), increase the private storage max size from the default 2048 MiB to 8192 MiB. Open a terminal. -Clone this Git repository and run the `build-with-docker.sh` script: +Clone this Git repository and run the `build-with.sh` script with either `docker` or `podman` as argument (Note: The `chcon` call is mandatory on Fedora with new SELinux policies which do not allow to standardly keep the docker images in homedir): mkdir /home/user/docker sudo ln -s /home/user/docker /var/lib/docker + sudo chcon -Rt container_file_t /home/user/docker sudo dnf install docker sudo systemctl start docker git clone https://github.com/mirage/qubes-mirage-firewall.git cd qubes-mirage-firewall - sudo ./build-with-docker.sh + sudo ./build-with.sh docker -This took about 10 minutes on my laptop (it will be much quicker if you run it again). -The symlink step at the start isn't needed if your build VM is standalone. -It gives Docker more disk space and avoids losing the Docker image cache when you reboot the Qube. +Or + + sudo systemctl start podman + git clone https://github.com/mirage/qubes-mirage-firewall.git + cd qubes-mirage-firewall + ./build-with.sh podman + +This took about 15 minutes on my laptop (it will be much quicker if you run it again). +The symlink step at the start isn't needed if your build VM is standalone. It gives Docker more disk space and avoids losing the Docker image cache when you reboot the Qube. +It's not needed with Podman as the containers lives in your home directory by default. Note: the object files are stored in the `_build` directory to speed up incremental builds. If you change the dependencies, you will need to delete this directory before rebuilding. -It's OK to install the Docker package in a template VM if you want it to remain +It's OK to install the Docker or Podman package in a template VM if you want it to remain after a reboot, but the build of the firewall itself should be done in a regular AppVM. -You can also build without Docker, as for any normal Mirage unikernel; +You can also build without that script, as for any normal Mirage unikernel; see [the Mirage installation instructions](https://mirage.io/wiki/install) for details. -The Docker build fixes the versions of the libraries it uses, ensuring that you will get -exactly the same binary that is in the release. If you build without Docker, it will build +The build script fixes the versions of the libraries it uses, ensuring that you will get +exactly the same binary that is in the release. If you build without it, it will build against the latest versions instead (and the hash will therefore probably not match). However, it should still work fine. ## Deploy +### Manual deployment If you want to deploy manually, unpack `mirage-firewall.tar.bz2` in domU. The tarball contains `vmlinuz`, which is the unikernel itself, plus a dummy initramfs file that Qubes requires: @@ -84,6 +93,9 @@ qvm-features mirage-firewall qubes-firewall 1 qvm-features mirage-firewall no-default-kernelopts 1 ``` +### Deployment using saltstack +If you're familiar how to run salt states in Qubes, you can also use the script `SaltScriptToDownloadAndInstallMirageFirewallInQubes.sls` to automatically deploy the latest version of mirage firewall in your Qubes OS. An introduction can be found [here](https://forum.qubes-os.org/t/qubes-salt-beginners-guide/20126) and [here](https://www.qubes-os.org/doc/salt/). Following the instructions from the former link, you can run the script in dom0 with the command `sudo qubesctl --show-output state.apply SaltScriptToDownloadAndInstallMirageFirewallInQubes saltenv=user`. The script checks the checksum from the integration server and compares with the latest version provided in the github releases. It might be necessary to adjust the VM templates in the script which are used for downloading of the mirage unikernel, if your default templates do not have the tools `curl` and `tar` installed by default. Also don't forget to change the VMs in which the uni kernel should be used or adjust the "Qubes Global Settings". + ## Upgrading To upgrade from an earlier release, just overwrite `/var/lib/qubes/vm-kernels/mirage-firewall/vmlinuz` with the new version and restart the firewall VM. @@ -148,7 +160,7 @@ The boot process: For development, use the [test-mirage][] scripts to deploy the unikernel (`qubes-firewall.xen`) from your development AppVM. This takes a little more setting up the first time, but will be much quicker after that. e.g. - $ test-mirage dist/qubes-firewall.xen mirage-firewall + [user@dev ~]$ test-mirage dist/qubes-firewall.xen mirage-firewall Waiting for 'Ready'... OK Uploading 'dist/qubes-firewall.xen' (7454880 bytes) to "mirage-test" Waiting for 'Booting'... OK diff --git a/SaltScriptToDownloadAndInstallMirageFirewallInQubes.sls b/SaltScriptToDownloadAndInstallMirageFirewallInQubes.sls new file mode 100644 index 0000000..dc83f20 --- /dev/null +++ b/SaltScriptToDownloadAndInstallMirageFirewallInQubes.sls @@ -0,0 +1,103 @@ +# How to install the superlight mirage-firewall for Qubes OS by using saltstack +# Tested on Qubes v4.1 and mirage v0.8.5 +# After the install, you have to switch your AppVMs to use the mirage firewall vm created by this script e.g. by using "Qubes Global Settings" +# inspired by: https://github.com/one7two99/my-qubes/tree/master/mirage-firewall + +# default template + dispvm template are used. Possible optimization is to use min-dvms +{% set DownloadVMTemplate = salt['cmd.shell']("qubes-prefs default_template") %} +{% set DispVM = salt['cmd.shell']("qubes-prefs default_dispvm") %} + +{% set DownloadVM = "DownloadVmMirage" %} +{% set MirageFW = "sys-mirage-fw" %} +{% set GithubUrl = "https://github.com/mirage/qubes-mirage-firewall" %} +{% set Filename = "mirage-firewall.tar.bz2" %} +{% set MirageInstallDir = "/var/lib/qubes/vm-kernels/mirage-firewall" %} + +#download and install the latest version +{% set Release = salt['cmd.shell']("qvm-run --dispvm " ~ DispVM ~ " --pass-io \"curl --silent --location -o /dev/null -w %{url_effective} " ~ GithubUrl ~ "/releases/latest | rev | cut -d \"/\" -f 1 | rev\"") %} + +{% if Release != salt['cmd.shell']("[ ! -f " ~ MirageInstallDir ~ "/version.txt" ~ " ] && touch " ~ MirageInstallDir ~ "/version.txt" ~ ";cat " ~ MirageInstallDir ~ "/version.txt") %} + +create-downloader-VM: + qvm.vm: + - name: {{ DownloadVM }} + - present: + - template: {{ DownloadVMTemplate }} + - label: red + - prefs: + - template: {{ DownloadVMTemplate }} + - include-in-backups: false + +{% set DownloadBinary = GithubUrl ~ "/releases/download/" ~ Release ~ "/" ~ Filename %} + +download-and-unpack-in-DownloadVM4mirage: + cmd.run: + - names: + - qvm-run --pass-io {{ DownloadVM }} {{ "curl -L -O " ~ DownloadBinary }} + - qvm-run --pass-io {{ DownloadVM }} {{ "tar -xvjf " ~ Filename }} + - require: + - create-downloader-VM + + +check-checksum-in-DownloadVM: + cmd.run: + - names: + - qvm-run --pass-io {{ DownloadVM }} {{ "\"echo \\\"Checksum of last build on github:\\\";curl -s https://raw.githubusercontent.com/mirage/qubes-mirage-firewall/main/build-with.sh | grep \\\"SHA2 last known:\\\" | cut -d\' \' -f5 | tr -d \\\\\\\"\"" }} + - qvm-run --pass-io {{ DownloadVM }} {{ "\"echo \\\"Checksum of downloaded local file:\\\";sha256sum ~/mirage-firewall/vmlinuz | cut -d\' \' -f1\"" }} + - qvm-run --pass-io {{ DownloadVM }} {{ "\"diff <(curl -s https://raw.githubusercontent.com/mirage/qubes-mirage-firewall/main/build-with.sh | grep \\\"SHA2 last known:\\\" | cut -d\' \' -f5 | tr -d \\\\\\\") <(sha256sum ~/mirage-firewall/vmlinuz | cut -d\' \' -f1) && echo \\\"Checksums DO match.\\\" || (echo \\\"Checksums do NOT match.\\\";exit 101)\"" }} #~/mirage-firewall/modules.img + - require: + - download-and-unpack-in-DownloadVM4mirage + +copy-mirage-kernel-to-dom0: + cmd.run: + - name: mkdir -p {{ MirageInstallDir }}; qvm-run --pass-io --no-gui {{ DownloadVM }} "cat ~/mirage-firewall/vmlinuz" > {{ MirageInstallDir ~ "/vmlinuz" }} + - require: + - download-and-unpack-in-DownloadVM4mirage + - check-checksum-in-DownloadVM + +create-initramfs: + cmd.run: + - names: + - gzip -n9 < /dev/null > {{ MirageInstallDir ~ "/initramfs" }} + - echo {{ Release }} > {{ MirageInstallDir ~ "/version.txt" }} + - require: + - copy-mirage-kernel-to-dom0 + +create-sys-mirage-fw: + qvm.vm: + - name: {{ MirageFW }} + - present: + - class: StandaloneVM + - label: black + - prefs: + - kernel: mirage-firewall + - kernelopts: + - include-in-backups: False + - memory: 32 + - maxmem: 32 + - netvm: sys-net + - provides-network: True + - vcpus: 1 + - virt-mode: pvh + - features: + - enable: + - qubes-firewall + - no-default-kernelopts + - require: + - copy-mirage-kernel-to-dom0 + + +cleanup-in-DownloadVM: + cmd.run: + - names: + - qvm-run -a --pass-io --no-gui {{ DownloadVM }} "{{ "rm " ~ Filename ~ "; rm -R ~/mirage-firewall" }}" + - require: + - create-initramfs + +remove-DownloadVM4mirage: + qvm.absent: + - name: {{ DownloadVM }} + - require: + - cleanup-in-DownloadVM + +{% endif %} diff --git a/build-with-docker.sh b/build-with-docker.sh deleted file mode 100755 index e5a9a17..0000000 --- a/build-with-docker.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh -set -eu -echo Building Docker image with dependencies.. -docker build -t qubes-mirage-firewall . -echo Building Firewall... -docker run --rm -i -v `pwd`:/tmp/orb-build qubes-mirage-firewall -echo "SHA2 of build: $(sha256sum ./dist/qubes-firewall.xen)" -echo "SHA2 last known: 8ae5314edf5b863b788c4b873e27bc4b206a2ff7ef1051c4c62ae41584ed3e14" -echo "(hashes should match for released versions)" diff --git a/build-with.sh b/build-with.sh new file mode 100755 index 0000000..712b012 --- /dev/null +++ b/build-with.sh @@ -0,0 +1,24 @@ +#!/bin/sh +set -eu + +if [[ $# -ne 1 ]] ; then + echo "Usage: build-with.sh { docker | podman }" + exit 1 +fi + +builder=$1 +case $builder in + docker|podman) + ;; + *) + echo "You should use either docker or podman for building" + exit 2 +esac + +echo Building $builder image with dependencies.. +$builder build -t qubes-mirage-firewall . +echo Building Firewall... +$builder run --rm -i -v `pwd`:/tmp/orb-build:Z qubes-mirage-firewall +echo "SHA2 of build: $(sha256sum ./dist/qubes-firewall.xen)" +echo "SHA2 last known: 2c3f68f49afdeaeedd2c03f8ef6d30d6bb4d6306bda0a1ff40f95f440a90034c" +echo "(hashes should match for released versions)"