Unofficial
Sur cette page
  1. 01Ce que fournit la jail
  2. 02Avant de commencer
  3. 03Le profil
  4. 04Les blocs
  5. 05Compiler, signer, installer
  6. 06Vérifier le confinement
  7. 07Arrêt, suppression et éléments conservés

Exécuter le service de votre paquet dans une jail : compte dédié, sans root, avec le seul réseau autorisé

Le script de service d’un paquet QPKG s’exécute en root. Avec le bloc Start in a jail du constructeur de services, les programmes lancés utilisent leur propre compte dans un bac à sable : aucune capacité, aucun moyen d’en acquérir, un filtre des appels système dangereux et un réseau propre. Cet exemple sert une page qui indique ce que le bac à sable lui permet de faire.

  1. Profilce qui tourne et ce qui est visible
  2. Blocscompte, dossiers, jail
  3. Compilerbvs-jail et bwrap sont inclus
  4. Signercas 1, puis installer
  5. Vérifierla page décrit son environnement

Les images proviennent de Bucket & Code Server IDE 1.0.0-49. L’exemple a été compilé, signé, installé et vérifié sur un NAS de test sous QTS 6.1. Le compte s’appelle ola.

01Ce que fournit la jail

  • Les services utilisent le compte indiqué dans le profil, jamais root, sans capacités et avec NoNewPrivs activé. Un filtre seccomp refuse les montages, les nouveaux espaces de noms, les modules du noyau, bpf, io_uring, ptrace et les autres appels utilisés par les exploits du noyau.
  • Ils voient l’application en lecture seule, leurs dossiers de données en lecture-écriture, des /tmp, /proc et /dev privés, ainsi que la glibc du NAS. Aucun autre partage, aucun /etc/config, aucun autre paquet.
  • Leur réseau possède sa propre boucle locale. Chaque port inbound est relayé du NAS vers un port interne. Les connexions sortantes passent par un proxy HTTP qui refuse les adresses privées, celles de boucle locale et celles du NAS.
  • Une limite de mémoire, une pondération CPU et une limite de processus s’appliquent à toute la jail.

Le paquet embarque la jail : bin/bvs-jail et un bin/bwrap statique (bubblewrap), préparés lors de la compilation pour chaque architecture.

02Avant de commencer

  • Il faut un projet QPKG créé avec New project, QDK sur la page Toolchains et l’environnement de compilation Ubuntu 24.04. Votre propre App Center explique leur configuration ; cet exemple utilise le modèle de service Empty.
  • Un certificat de signature de code délivré par QNAP, sous forme de fichiers. App Center installe uniquement les paquets signés.

03Le profil

Le profil est un fichier JSON du paquet. Ici, il se trouve dans shared/etc/jail/jailbox.json dans le projet, puis est installé sous etc/jail/jailbox.json dans le dossier d’installation du paquet.

# shared/etc/jail/jailbox.json
{
  "name": "jailbox",
  "user": "jailbox",
  "app": "@QPKG_ROOT@",
  "data": ["@QPKG_DATA@"],
  "services": [
    {"name": "checks", "cmd": ["@QPKG_ROOT@/bin/busybox", "sh", "@QPKG_ROOT@/share/jailbox/checks.sh", "@QPKG_ROOT@", "@QPKG_DATA@"], "oneshot": true},
    {"name": "web", "cmd": ["@QPKG_ROOT@/bin/busybox", "httpd", "-f", "-p", "127.0.0.1:8080", "-h", "@QPKG_DATA@/www"], "wait_port": 8080}
  ],
  "inbound": [{"listen": "0.0.0.0:19975", "port": 8080}],
  "egress": {},
  "limits": {"memory_mb": 256},
  "stop_seconds": 10
}
  • @QPKG_ROOT@ et @QPKG_DATA@ représentent les dossiers d’installation et de données, dont les chemins ne sont connus que sur le NAS. Chaque démarrage les remplace dans une copie modifiable uniquement par root, puis la jail utilise cette copie. Les clés inconnues sont refusées.
  • app est monté en lecture seule. Ici, il désigne tout le dossier d’installation pour permettre aux services d’utiliser le BusyBox du paquet ; une véritable application le fait pointer vers sa propre arborescence, par exemple @QPKG_ROOT@/app.
  • Les services démarrent dans l’ordre. Un oneshot se termine d’abord ; wait_port attend que le service écoute avant de lancer le suivant. Au moins un service reste en cours d’exécution.
  • inbound relaie le port 19975 du NAS vers le port interne 8080. egress fournit aux services un proxy sur 127.0.0.1:3128 pour les ports 80 et 443 des adresses publiques ; une liste allow autorise les destinations nommées quelle que soit leur adresse.
  • limits.memory_mb vaut 2048 par défaut ; stop_seconds indique combien de temps la jail attend l’arrêt de ses services.

Le premier service exécute ce script, également inclus dans le paquet sous shared/share/jailbox/checks.sh. Il écrit ce que permet le bac à sable dans le dossier servi par le second service.

#!/bin/sh
# Runs inside the jail and writes what the sandbox allows into the page folder.
R=$1 D=$2 B=$R/bin/busybox V=$($B dirname "$D")
{
  $B grep -E '^(Uid|CapEff|NoNewPrivs|Seccomp):' /proc/self/status
  if $B touch "$R/probe" 2>/dev/null; then echo 'write the install folder: allowed'; else echo 'write the install folder: refused'; fi
  if $B ls /etc/config >/dev/null 2>&1; then echo 'read /etc/config: allowed'; else echo 'read /etc/config: refused'; fi
  echo "what $V holds here: $($B ls -A "$V" | $B tr '\n' ' ')"
  echo "network interfaces: $($B tail -n +3 /proc/net/dev | $B cut -d: -f1 | $B tr -d ' ' | $B tr '\n' ' ')"
} >"$D/www/checks.txt"
echo 'Served from inside the jail.' >"$D/www/index.html"

04Les blocs

Sur la page du projet, carte Service and package, onglet Blocks : utilisez Add a block sous At start cinq fois, dans cet ordre.

Ajouter un bloc, groupe « The program » : Create a group, Create an account, Owner and mode, Start the program, Start in a jail, Wait for a port, Wait for a socket
  1. Data folder on the volume, nom jailbox : le dossier $QPKG_DATA.
  2. Create an account, nom jailbox : le compte indiqué dans le profil.
  3. Create a folder, $QPKG_DATA/www, propriétaire jailbox:jailbox, mode 750 : le seul dossier dans lequel les services écrivent.
  4. Start in a jail, ID jailbox, profil $QPKG_ROOT/etc/jail/jailbox.json. Le délai d’arrêt doit être au moins égal au stop_seconds du profil plus 15 ; la valeur par défaut de 90 suffit ici. The Start in a jail block: ID jailbox, Profile $QPKG_ROOT/etc/jail/jailbox.json, Seconds to wait at stop 90, Restart it when it stops off, Undo it at stop on, and the lines it writes: qpkg_jail and, at stop, qpkg_daemon_stop with bin/bvs-jail
  5. Wait for a port, 19975 : le démarrage se termine lorsque le port relayé répond.

Sous At a status check, Is it running? avec l’ID jailbox et Is the port open? avec 19975 permettent au verbe status du script de service de contrôler la jail. Save affiche les différences de chaque fichier et Commit les enregistre dans le dépôt du projet.

« Commit these changes? » : service.json avec les blocs de démarrage, et shared/jailbox.sh avec les lignes qpkg_data_dir, qpkg_user, qpkg_dir, qpkg_jail et qpkg_wait_port

05Compiler, signer, installer

Build sur la page du projet lance qbuild de QDK. Son journal indique ce que le paquet embarque pour la jail.

Journal de compilation : paquet x86_64 avec bin/busybox, bin/chpst, share/busybox/, bin/bvs-jail, bin/bwrap et share/bubblewrap/ ; fichier produit : build/jailbox_0.1.0_x86_64.qpkg, sans signature de code

Sans certificat, le paquet est compilé sans signature. La page du projet indique que le déploiement de test et le dépôt exigent un paquet signé.

Pipeline du projet : Build terminé ; Test deploy exige un paquet signé ; Publish : non signé, le dépôt accepte uniquement les paquets signés
  1. Placez les fichiers du certificat, certificate, private_key et ca_certs, à côté de qpkg.cfg dans le dossier du projet, puis recompilez : le paquet sera signé. Le .gitignore du modèle exclut ces fichiers de git. En CI, ils proviennent des secrets du dépôt, comme l’explique Votre propre App Center.
  2. Installez le paquet signé : Test deploy sur la page du projet (admin), Install Manually dans App Center ou le dépôt App Center de votre NAS.

06Vérifier le confinement

Ouvrez http://<NAS address>:19975/ : la page affiche « Served from inside the jail. » via le port relayé. Le fichier checks.txt voisin contient les résultats du premier service :

# http://192.168.1.20:19975/checks.txt
Uid:	222	222	222	222
CapEff:	0000000000000000
NoNewPrivs:	1
Seccomp:	2
write the install folder: refused
read /etc/config: refused
what /share/CACHEDEV1_DATA holds here: .qpkg jailbox
network interfaces: lo
  • Le dossier du volume contient uniquement les dossiers du paquet : son dossier d’installation sous .qpkg et son dossier de données. Aucun partage, aucun autre paquet.
  • Depuis une session SSH en admin, grep -E '^(Uid|CapEff|NoNewPrivs|Seccomp):' /proc/<pid>/status appliqué au service donne le même résultat. Les deux processus bubblewrap qui surveillent la jail de l’extérieur n’ont eux aussi aucune capacité et ont NoNewPrivs activé, sans le filtre.
  • Le journal de la jail est log/jailbox.log dans le dossier de données : démarrage de chaque service, son port et son arrêt. Il signale que le dossier de données appartient à root ; les services écrivent uniquement dans le dossier attribué au compte par le bloc Create a folder.

07Arrêt, suppression et éléments conservés

  • /etc/init.d/jailbox.sh stop, ou l’arrêt depuis App Center, termine les services dans l’ordre inverse et la jail écrit ended (0) dans son journal. Aucun processus du compte, aucun cgroup ni aucun port en écoute ne reste.
  • La suppression du paquet arrête la jail et supprime le dossier d’installation ainsi que le script de service. Le compte et le dossier de données restent, car aucun bloc ne les supprime : Your own shell lines sous At removal peut aussi les supprimer.
  • Le dossier d’exécution de la jail, /var/run/bvs-jail/jailbox, reste jusqu’à ce que le prochain démarrage le réécrive. Il se trouve dans le système de fichiers en mémoire de QTS et disparaît au prochain redémarrage.