Eseguire il servizio del proprio pacchetto in una jail: account dedicato, senza root, solo la rete consentita
Lo script di servizio di un pacchetto QPKG viene eseguito come root. Con il blocco Start in a jail del generatore di servizi, i programmi avviati usano un account dedicato in una sandbox: nessuna capability né modo di acquisirla, un filtro sulle chiamate di sistema pericolose e una rete propria. L’esempio serve una pagina che mostra ciò che la sandbox permette di fare.
- Profilocosa viene eseguito e cosa vede
- Blocchiaccount, cartelle, jail
- Compilarebvs-jail e bwrap sono inclusi
- Firmarecaso 1, poi installare
- Verificarela pagina descrive il suo ambiente
Le immagini provengono da Bucket & Code Server IDE 1.0.0-49. L’esempio è stato compilato, firmato, installato e verificato su un NAS di prova con QTS 6.1. L’account si chiama ola.
01Cosa offre la jail
- I servizi usano l’account indicato nel profilo, mai root, senza capability e con
NoNewPrivsattivo. Un filtro seccomp rifiuta mount, nuovi namespace, moduli del kernel,bpf,io_uring,ptracee le altre chiamate usate dagli exploit del kernel. - Vedono l’app in sola lettura, le proprie cartelle dati in lettura e scrittura,
/tmp,/proce/devprivati e la glibc del NAS. Nessun’altra condivisione, nessun/etc/config, nessun altro pacchetto. - La loro rete ha un loopback dedicato. Ogni porta
inboundviene inoltrata dal NAS a una porta interna. Le connessioni in uscita passano per un proxy HTTP che rifiuta indirizzi privati, di loopback e del NAS. - Un limite di memoria, una quota relativa di CPU e un limite di processi si applicano all’intera jail.
Il pacchetto include la jail stessa: bin/bvs-jail e un bin/bwrap statico (bubblewrap), preparati durante la compilazione per ogni architettura.
02Prima di iniziare
- Servono un progetto QPKG creato con New project, QDK nella pagina Toolchains e l’ambiente di compilazione Ubuntu 24.04. Il proprio App Center ne descrive la configurazione; questo esempio usa il modello di servizio Empty.
- Un certificato di firma del codice emesso da QNAP a proprio nome, sotto forma di file. App Center installa solo pacchetti firmati.
03Il profilo
Il profilo è un file JSON del pacchetto. Qui si trova in shared/etc/jail/jailbox.json nel progetto e viene installato come etc/jail/jailbox.json nella cartella di installazione del pacchetto.
# 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@e@QPKG_DATA@rappresentano le cartelle di installazione e dei dati, note solo sul NAS. Ogni avvio ne risolve i percorsi in una copia modificabile solo da root, che viene usata dalla jail. Le chiavi sconosciute vengono rifiutate.appviene montato in sola lettura. Qui è l’intera cartella di installazione, così i servizi possono usare il BusyBox del pacchetto; un’app reale indica il proprio albero di cartelle, per esempio@QPKG_ROOT@/app.- I
servicespartono in ordine. Unoneshottermina prima di proseguire;wait_portattende che il servizio sia in ascolto prima di avviare il successivo. Almeno un servizio rimane in esecuzione. inboundinoltra la porta 19975 del NAS alla porta interna 8080.egressfornisce ai servizi un proxy su127.0.0.1:3128per le porte 80 e 443 degli indirizzi pubblici; un elencoallowammette le destinazioni indicate qualunque sia il loro indirizzo.limits.memory_mbvale 2048 se non viene impostato;stop_secondsindica quanto la jail attende l’arresto dei servizi.
Il primo servizio esegue questo script, anch’esso incluso nel pacchetto come shared/share/jailbox/checks.sh. Scrive ciò che la sandbox consente nella cartella servita dal secondo servizio.
#!/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"
04I blocchi
Nella pagina del progetto, scheda Service and package, sezione Blocks: usare Add a block sotto At start cinque volte, in questo ordine.
- Data folder on the volume, nome
jailbox: la cartella$QPKG_DATA. - Create an account, nome
jailbox: l’account indicato nel profilo. - Create a folder,
$QPKG_DATA/www, proprietariojailbox:jailbox, modalità750: l’unica cartella in cui i servizi scrivono. - Start in a jail, ID
jailbox, profilo$QPKG_ROOT/etc/jail/jailbox.json. I secondi di attesa all’arresto devono essere almeno il valorestop_secondsdel profilo più 15; il valore predefinito 90 è sufficiente.
- Wait for a port,
19975: l’avvio termina quando la porta inoltrata risponde.
Sotto At a status check, Is it running? con ID jailbox e Is the port open? con 19975 fanno rispondere il comando status dello script di servizio per la jail. Save mostra le differenze di ogni file e Commit le registra nel repository del progetto.
05Compilare, firmare, installare
Build nella pagina del progetto esegue qbuild di QDK. Il suo log elenca i componenti della jail inclusi nel pacchetto.
Senza certificato il pacchetto viene compilato senza firma e la pagina del progetto segnala che il deployment di prova e il repository richiedono un pacchetto firmato.
- Mettere i file del certificato
certificate,private_keyeca_certsaccanto aqpkg.cfgnella cartella del progetto e ricompilare: il pacchetto sarà firmato. Il.gitignoredel modello esclude questi file da git. In CI provengono dai segreti del repository, come mostra Il proprio App Center. - Installare il pacchetto firmato: Test deploy nella pagina del progetto (admin), Install Manually in App Center oppure il repository App Center del proprio NAS.
06Verificare l’isolamento
Aprire http://<NAS address>:19975/: la pagina mostra “Served from inside the jail.” attraverso la porta inoltrata. Il file checks.txt accanto alla pagina contiene i risultati del primo servizio:
# 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
- La cartella del volume contiene solo quelle del pacchetto: la cartella di installazione sotto
.qpkge quella dei dati. Nessuna condivisione, nessun altro pacchetto. - Da una sessione SSH come admin,
grep -E '^(Uid|CapEff|NoNewPrivs|Seccomp):' /proc/<pid>/statusdel servizio mostra gli stessi valori. Anche i due processi bubblewrap che sorvegliano la jail dall’esterno non hanno capability e hannoNoNewPrivsattivo, senza il filtro. - Il log della jail è
log/jailbox.lognella cartella dati: avvio di ogni servizio, relativa porta e arresto. Segnala quando la cartella dati appartiene a root; i servizi scrivono solo nella cartella assegnata all’account dal blocco Create a folder.
07Arresto, rimozione e dati conservati
/etc/init.d/jailbox.sh stop, o l’arresto da App Center, termina i servizi in ordine inverso e la jail registraended (0). Non rimangono processi dell’account, cgroup o porte in ascolto.- La rimozione del pacchetto arresta la jail ed elimina la cartella di installazione e lo script di servizio. L’account e la cartella dati rimangono perché nessun blocco li rimuove: Your own shell lines sotto At removal può rimuovere anche questi.
- La cartella di esecuzione della jail,
/var/run/bvs-jail/jailbox, rimane finché l’avvio successivo non la riscrive. Si trova nel file system in memoria di QTS e scompare al prossimo riavvio.