From 2e83da7476b3ffa2e1d47054a1fafe91d8477000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20Mass=C3=A9?= Date: Tue, 10 Jun 2025 22:06:37 +0200 Subject: [PATCH] article: Running qemu-user-static with Podman --- .../qemu-user-static-with-podman/index.md | 145 +++++++++++++++++ .../qemu-user-static-with-podman/index.md | 146 ++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 content/english/blog/qemu-user-static-with-podman/index.md create mode 100644 content/french/blog/qemu-user-static-with-podman/index.md diff --git a/content/english/blog/qemu-user-static-with-podman/index.md b/content/english/blog/qemu-user-static-with-podman/index.md new file mode 100644 index 0000000..d3f9aec --- /dev/null +++ b/content/english/blog/qemu-user-static-with-podman/index.md @@ -0,0 +1,145 @@ +--- +title: "Running qemu-user-static with Podman" +date: 2025-06-10T00:00:00+02:00 +opensource: +- Podman +- Qemu +topics: +- Containers +--- + +Recently, I had to run ARM64 containers on a customer's x86 laptop. +Easy, you might say: just install qemu-user-static on the laptop! +And you also have a ready-made container on Docker Hub, just in case! + +That's it? +Not so sure... + + + +## Current situation + +While it is easy to install **qemu-user-static** on **Fedora**, this is not the case for **CentOS Stream** or **Red Hat Enterprise Linux**. +You can always retrieve the package from a Fedora repository and install it on these Fedora downstream distributions. +This works, but it remains an orphaned package that will never be updated. + +What about the image [docker.io/multiarch/qemu-user-static](https://hub.docker.com/r/multiarch/qemu-user-static) that can be found on Docker Hub? +It hasn't been updated in two years, and the latest version of qemu available is 7.2. +Considering that version 10 of qemu was released recently, this is a bit messy... + +## TL;DR + +Two one-liners, one for building the image and one for executing it. +Build the container image **localhost/qemu-user-static**: + +```sh +sudo podman build -f https://red.ht/qemu-user-static -t localhost/qemu-user-static /tmp +``` + +Run **qemu-user-static**: + +```sh +sudo podman run --rm --privileged --security-opt label=filetype:container_file_t --security-opt label=level:s0 --security-opt label=type:spc_t localhost/qemu-user-static +``` + +That's it! +You can now run a container image that is in a different hardware architecture than your machine. + +Example: + +``` +$ arch +x86_64 + +$ podman run -it --rm --platform linux/arm64/v8 docker.io/library/alpine +/ # arch +aarch64 +``` + +## Image building + +I chose to base my image on the official Fedora images (version 42 at the time of writing this article). +If you have already worked with the **docker.io/multiarch/qemu-user-static** image, you will see that I have modernized it a bit: + +- No more scripts from the **qemu** repo, the **systemd-binfmt** component provides everything you need. +- No more options to pass during execution, the script unregisters all binaries registered with the **binfmt** subsystem and registers all its `qemu-*-static` binaries instead. + +The result is stripped down: + +```dockerfile +FROM quay.io/fedora/fedora:42 + +RUN dnf install -y qemu-user-static \ + && dnf clean all + +ADD container-entrypoint / + +ENTRYPOINT ["/container-entrypoint"] +CMD [] +``` + +The **container-entrypoint** script is also reduced to the bare minimum: + +```sh +#!/bin/sh + +set -Eeuo pipefail + +if [ ! -d /proc/sys/fs/binfmt_misc ]; then + echo "No binfmt support in the kernel." + echo " Try: '/sbin/modprobe binfmt_misc' from the host" + exit 1 +fi + +if [ ! -f /proc/sys/fs/binfmt_misc/register ]; then + echo "Mounting /proc/sys/fs/binfmt_misc..." + mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc +fi + +echo "Cleaning up..." +find /proc/sys/fs/binfmt_misc -type f -name 'qemu-*' -exec sh -c 'echo -1 > {}' \; + +echo "Registering..." +exec /usr/lib/systemd/systemd-binfmt +``` + +## Execution + +Once the image has been built, it must be executed so that the `qemu-*-static` binaries are registered with the **binfmt** system. +But there is a small subtlety: on a Linux distribution such as Fedora, CentOS Stream, or RHEL, the SELinux system keeps a close eye on things! +And if something tries to break through the containerization engine's security, SELinux detects it and the action is prohibited. + +And that's exactly what happens when you naively run the previously built image: the `qemu-*-static` binaries have a SELinux label that prevents them from being executed by the containerization engine when it is about to launch PID 1 of the container to be emulated. + +The solution? Launch the container with SELinux options that give the container the correct SELinux labels so that the action is allowed. +This is the role of the `--security-opt` options: + +- `label=filetype:container_file_t`: the files in the container image are labeled with the SELinux type **container_file_t**. +- `label=label=level:s0`: the files in the container image are labeled with the SELinux level **s0**. + +As you may have recognized, these two options ensure that the files in our image **localhost/qemu-user-static** have the SELinux label for shared volumes. +These files can therefore be shared between containers. + +The options `--security-opt label=type:spc_t` and `--privileged` allow the container to run as root, mount the pseudo file system **binfmt_misc**, and register the `qemu-*-static` binaries. + +The complete command line is: + +```sh +sudo podman run --rm --privileged --security-opt label=filetype:container_file_t --security-opt label=level:s0 --security-opt label=type:spc_t localhost/qemu-user-static +``` + +If you forget these security options, when you try to run a container in a different architecture, podman will terminate without error but without executing anything... + +With a bit of luck, you will then see the following error message in your logs: + +``` +[10051.131634] audit: type=1400 audit(1749488918.807:3124): avc:  denied  { read execute } for  pid=32544 comm="dumb-init" path="/usr/bin/qemu-x86_64-static" dev="overlay" ino=434591 scontext=system_u:system_r:container_t:s0:c53,c117 tcontext=system_u:object_r:container_file_t:s0:c1022,c1023 tclass=file permissive=0 +[10051.131656] audit: type=1701 audit(1749488918.807:3125): auid=10018 uid=1000 gid=1000 ses=1 subj=system_u:system_r:container_t:s0:c53,c117 pid=32544 comm="dumb-init" exe="/usr/bin/qemu-x86_64-static" sig=11 res=1 +``` + +Although it may seem cryptic at first glance, the message says it all: you are trying to execute (`{ read execute }`) a binary (`/usr/bin/qemu-x86_64-static`) that has the label `system_u:object_r:container_file_t:s0:c1022,c1023` from a process that has the label `system_u:system_r:container_t:s0:c53,c117` (note the difference at the end of the label!) and this access is denied (`avc: denied`). + +## Conclusion + +This issue has been a thorn in my side for the past few weeks, and I'm glad I finally found a solution! +I hope you find it as useful as I did. diff --git a/content/french/blog/qemu-user-static-with-podman/index.md b/content/french/blog/qemu-user-static-with-podman/index.md new file mode 100644 index 0000000..6244e72 --- /dev/null +++ b/content/french/blog/qemu-user-static-with-podman/index.md @@ -0,0 +1,146 @@ +--- +title: "Faire fonctionner qemu-user-static avec Podman" +date: 2025-06-10T00:00:00+02:00 +opensource: +- Podman +- Qemu +topics: +- Containers +--- + +Récemment, j'ai eu à faire tourner des conteneurs ARM64 sur le laptop x86 d'un client. +Facile me direz-vous : tu n'as qu'à installer qemu-user-static sur le laptop ! +Et t'as aussi un conteneur tout prêt sur Docker Hub, au cas où ! + +La messe est dite ? +Pas si sûr... + + + +## État des lieux + +En effet, s'il est facile d'installer **qemu-user-static** sur **Fedora**, ce n'est pas le cas de **CentOS Stream** ou **Red Hat Enterprise Linux**. +On peut toujours récupérer le paquet d'un repository Fedora et l'installer sur ces downstream de Fedora. +Ça marche mais ça reste un paquet orphelin qui ne sera jamais mis à jour... + +Et l'image [docker.io/multiarch/qemu-user-static](https://hub.docker.com/r/multiarch/qemu-user-static) que l'on peut trouver sur le Docker Hub ? +Elle n'a pas été mise à jour depuis 2 ans et la dernière version disponible de qemu est la 7.2. +Quand on sait que la version 10 de qemu a été publiée récemment, ça fait désordre... + +## TL;DR + +Deux one-liners, un pour la construction de l'image et un pour son exécution. + +Construire l'image de conteneur **localhost/qemu-user-static** : + +```sh +sudo podman build -f https://red.ht/qemu-user-static -t localhost/qemu-user-static /tmp +``` + +Exécuter **qemu-user-static** : + +```sh +sudo podman run --rm --privileged --security-opt label=filetype:container_file_t --security-opt label=level:s0 --security-opt label=type:spc_t localhost/qemu-user-static +``` + +Ça y est ! +Vous pouvez maintenant exécuter une image de conteneur qui est dans une architecture matérielle différente de celle de votre machine. + +Exemple : + +``` +$ arch +x86_64 + +$ podman run -it --rm --platform linux/arm64/v8 docker.io/library/alpine +/ # arch +aarch64 +``` + +## Construction de l'image + +J'ai choisi de baser mon image sur les images officielles Fedora (version 42 lors de l'écriture de cet article). +Si vous avez déjà travaillé avec l'image **docker.io/multiarch/qemu-user-static**, vous verrez que j'ai un peu modernisé tout ça : + +- Plus de script en provenance du repo **qemu**, le composant **systemd-binfmt** fournit tout le nécessaire. +- Plus d'option à passer lors de l'exécution, le script désenregistre tous les binaires enregistrés auprès de sous-système **binfmt** et enregistre tous ses binaires `qemu-*-static` à la place. + +Le résultat est dépouillé : + +```dockerfile +FROM quay.io/fedora/fedora:42 + +RUN dnf install -y qemu-user-static \ + && dnf clean all + +ADD container-entrypoint / + +ENTRYPOINT ["/container-entrypoint"] +CMD [] +``` + +Le script **container-entrypoint** est lui aussi réduit à son strict nécessaire : + +```sh +#!/bin/sh + +set -Eeuo pipefail + +if [ ! -d /proc/sys/fs/binfmt_misc ]; then + echo "No binfmt support in the kernel." + echo " Try: '/sbin/modprobe binfmt_misc' from the host" + exit 1 +fi + +if [ ! -f /proc/sys/fs/binfmt_misc/register ]; then + echo "Mounting /proc/sys/fs/binfmt_misc..." + mount binfmt_misc -t binfmt_misc /proc/sys/fs/binfmt_misc +fi + +echo "Cleaning up..." +find /proc/sys/fs/binfmt_misc -type f -name 'qemu-*' -exec sh -c 'echo -1 > {}' \; + +echo "Registering..." +exec /usr/lib/systemd/systemd-binfmt +``` + +## Exécution + +Une fois l'image construite, il faut l'exécuter pour que les binaires `qemu-*-static` soient enregistrés auprès du système **binfmt**. +Mais il y a une petite subtilité : sur une distribution Linux type Fedora, CentOS Stream ou RHEL, le système SELinux veille au grain ! +Et si quelque chose tente de percer l'étanchéité du moteur de conteneurisation, SELinux le détecte et l'action est interdite. + +Et c'est exactement ce qui se produit quand on exécute naïvement l'image construite précédemment : les binaires `qemu-*-static` ont une étiquette SELinux qui interdit leur exécution par le moteur de conteneurisation au moment où il s'apprête à lancer le PID 1 du conteneur à émuler. + +La solution ? Lancer le conteneur avec les options SELinux qui donne au conteneur les bonnes étiquettes SELinux afin que l'action soit autorisée. +C'est le rôle des options `--security-opt` : + +- `label=filetype:container_file_t`: les fichiers de l'image de conteneur sont étiquetés avec le type SELinux **container_file_t**. +- `label=label=level:s0`: les fichiers de l'image de conteneur sont étiquetés avec le niveau SELinux **s0**. + +Ces deux options, vous l'avez peut-être reconnu, font que les fichiers de notre image **localhost/qemu-user-static** ont l'étiquette SELinux des volumes partagés. +Le partage de ces fichiers est donc autorisé entre les conteneurs. + +Les options `--security-opt label=type:spc_t` et `--privileged` permettent au conteneur de s'exécuter en root, de pouvoir procéder au montage du pseudo système de fichier **binfmt_misc** et d'enregistrer les binaires `qemu-*-static`. + +La ligne de commande complète est : + +```sh +sudo podman run --rm --privileged --security-opt label=filetype:container_file_t --security-opt label=level:s0 --security-opt label=type:spc_t localhost/qemu-user-static +``` + +Si vous oubliez ces options de sécurité, lorsque vous voudrez exécuter un conteneur dans une architecture différente, podman se terminera sans erreur mais sans rien exécuter... + +Avec un peu de chance, vous repèrerez alors dans vos logs le message d'erreur suivant : + +``` +[10051.131634] audit: type=1400 audit(1749488918.807:3124): avc:  denied  { read execute } for  pid=32544 comm="dumb-init" path="/usr/bin/qemu-x86_64-static" dev="overlay" ino=434591 scontext=system_u:system_r:container_t:s0:c53,c117 tcontext=system_u:object_r:container_file_t:s0:c1022,c1023 tclass=file permissive=0 +[10051.131656] audit: type=1701 audit(1749488918.807:3125): auid=10018 uid=1000 gid=1000 ses=1 subj=system_u:system_r:container_t:s0:c53,c117 pid=32544 comm="dumb-init" exe="/usr/bin/qemu-x86_64-static" sig=11 res=1 +``` + +Même si ça peut sembler cryptique au premier abord, le message dit l'essentiel : tu essayes d'éxécuter (`{ read execute }`) un binaire (`/usr/bin/qemu-x86_64-static`) qui a l'étiquette `system_u:object_r:container_file_t:s0:c1022,c1023` depuis un processus qui a l'étiquette `system_u:system_r:container_t:s0:c53,c117` (notez la différence sur la fin de l'étiquette !) et cet accès est refusé (`avc: denied`). + +## Conclusion + +Ce sujet a été le caillou dans ma chaussure durant ces dernières semaines, je suis content d'être parvenu à trouver une solution ! +Et j'espère que la dite solution vous sera autant utile à vous qu'elle l'a été pour moi.