
Un sandbox di rete deterministico per testare le regole di nftables. Utilizza namespace di rete Linux effimeri (netns) e Scapy per validare in modo sicuro la logica del firewall.
Perché NSE? • Funzionalità • Requisiti • Installazione • Avvio rapido • Come funziona • Struttura del progetto
Testare ruleset di firewall su un sistema Linux attivo comporta rischi significativi: regole malformate possono interrompere le sessioni SSH di gestione, far trapelare traffico in chiaro durante i test, o lasciare tabelle firewall orfane attive sull'host.
Network Sandbox Engine (NSE) fornisce un harness di test sicuro e riproducibile. Costruisce network namespace Linux effimeri, collega coppie di virtual ethernet, compila ruleset nftables e inietta pacchetti sintetici di Livello 2 e Livello 3 usando Scapy. Tutta la valutazione avviene all'interno del namespace sandbox: lo stato del firewall dell'host non viene mai alterato.
Proprietà architetturali chiave:
nse_<uuid>) e vengono completamente rimossi durante il teardown.nse/ è di ~1150 istruzioni con il 98% di copertura dei test.NSE crea network namespace, carica ruleset nftables e legge eventi di traccia del kernel, quindi viene eseguito come root. Non apre socket, porte o endpoint RPC di alcun tipo — è una libreria e una CLI che invochi tu, e detiene privilegi solo per la durata di un'esecuzione.
La versione 2.1.0 ha rimosso l'interfaccia web FastAPI/Svelte che le release
precedenti includevano. Quell'interfaccia veniva eseguita in-process come root
dalla 2.0.0 in poi, il che rappresentava una grande superficie d'attacco per uno
strumento di testing; il codice rimane nella cronologia git al tag v2.0.0 se
ti serve.
Un test di firewall è un'asserzione negativa — "questo pacchetto non è passato" — e un'asserzione negativa non vale nulla se non è noto che lo strumento funzioni. Un monitor di traccia che non si è mai agganciato al kernel e un firewall che ha bloccato tutto producono un output identico byte per byte.
NSE quindi rifiuta di riportare un verdetto che non può dimostrare di aver misurato:
| Garanzia | Meccanismo |
|---|---|
| Il monitor era agganciato prima del primo pacchetto di test | Un canary di prontezza viene iniettato e re-iniettato finché la sua traccia nel kernel non viene osservata. Nessuna osservazione, nessuna esecuzione. |
| Il monitor era ancora agganciato dopo l'ultimo | Un canary di vitalità viene eseguito dopo l'iniezione. Se viene mancato, il flusso dei verdetti viene dichiarato troncato. |
| Il parser ha capito ciò che il kernel ha detto | Le righe di traccia che non corrispondono ad alcun pattern vengono contate, e qualsiasi conteggio maggiore di zero è un errore anziché un log di debug. |
| Il monitor non è morto silenziosamente | Il ciclo di lettura registra perché è terminato — stop pulito, EOF inatteso, timeout o crash — e solo uno stop pulito è accettabile. |
| Un verdetto mancante non è un superamento | Il runner CLI fallisce quando il numero di verdetti osservati differisce da quello atteso, in entrambe le direzioni. |
I pacchetti canary sono esclusi dai risultati tramite trace id, quindi non compaiono mai nel tuo flusso di verdetti.
La suite dimostra che questo vale, invece di affermarlo: make test-blind forza
il parser a non capire nulla, e la build fallisce a meno che il runner non esca
con codice diverso da zero. Quel job viene eseguito in CI a ogni push.
run_test_pipeline) che restituisce modelli Pydantic strutturati (TestRequest, TraceEvent).nse_<id>) collegato direttamente all'host.nse_router_<id>) e Server (nse_server_<id>) per test di forwarding e NAT.nse-runner). Esce con codice diverso da zero in caso di verdetto errato e di verdetto che non è riuscito a osservare.mypy --strict), applicazione dei confini architetturali (import-linter), formattazione ruff e un coverage ratchet (make test-cov, soglia minima 98%).nft)ip)ip netns e operazioni di traccia del kernel)Su sistemi Debian o Ubuntu:
sudo apt update && sudo apt install -y nftables iproute2 conntrack
Installa il motore principale con supporto CLI:
pip install "network-sandbox-engine[cli]"
Per lo sviluppo locale:
git clone https://github.com/onyks-os/NetworkSandboxEngine.git
cd NetworkSandboxEngine
make setup
import asyncio
from nse.core.netns_controller import NetnsController
from nse.core.pipeline import run_test_pipeline
from nse.models.test_request import TestRequest, PacketSpec
rules = """
table ip filter {
chain input {
type filter hook input priority 0; policy drop;
tcp dport 80 accept
}
}
"""
request = TestRequest(
rules=rules,
packets=[
PacketSpec(protocol="tcp", src_ip="10.0.0.1", dst_ip="10.0.0.2", dst_port=80),
PacketSpec(protocol="tcp", src_ip="10.0.0.1", dst_ip="10.0.0.2", dst_port=22),
],
)
async def main():
controller = NetnsController()
events = await run_test_pipeline(request=request, controller=controller)
for evt in events:
if evt.verdict:
print(f"[{evt.chain}] Verdict: {evt.verdict}")
asyncio.run(main())
Crea un file di test firewall_test.yaml:
tests:
- name: "Allow HTTP Port 80, Drop SSH Port 22"
topology: simple
rules: |
table ip filter {
chain input {
type filter hook input priority 0; policy drop;
tcp dport 80 accept
}
}
packets:
- protocol: tcp
src_ip: 10.0.0.1
dst_ip: 10.0.0.2
dst_port: 80
expected_verdict: ACCEPT
- protocol: tcp
src_ip: 10.0.0.1
dst_ip: 10.0.0.2
dst_port: 22
expected_verdict: DROP
expected_verdict è per pacchetto. Le chiavi sconosciute vengono rifiutate
anziché impostate a un valore predefinito, quindi un refuso fa fallire la suite
invece di diventare silenziosamente un'aspettativa che non hai mai scritto.
Esegui la suite con privilegi di root:
sudo nse-runner --file firewall_test.yaml
Codici di uscita: 0 tutti i pacchetti corrispondono; 1 un verdetto era errato oppure il motore non è riuscito a osservarne uno. Gli errori dell'oracle sono riportati separatamente dai fallimenti del firewall, perché significano che la misurazione si è rotta, non il ruleset.
podman build -t nse .
podman run --rm --cap-add=NET_ADMIN --cap-add=NET_RAW \
-v "$PWD/firewall_test.yaml:/suite.yaml:ro" nse --file /suite.yaml
Utile per fissare la versione di nftables rispetto alla quale vengono testate le tue regole.
NSE orchestra i sottosistemi di rete del kernel Linux e le interfacce di traccia attraverso una pipeline di esecuzione strutturata a più fasi:
graph TD
subgraph Step1["1. Test Specification"]
Req["<b>TestRequest</b><br/>ruleset + packets + topology"]
end
subgraph Step2["2. Ephemeral Netns Sandbox"]
direction TB
Netns["<b>Netns Setup</b><br/>nse_<id> & veth links"]
RuleEng["<b>Rule Engine</b><br/>validate & load nftables"]
Inject["<b>Scapy Injector</b><br/>L2/L3 packet injection"]
NFT["<b>Kernel nftables</b><br/>meta nftrace set 1"]
Netns --> RuleEng
RuleEng --> Inject
Inject --> NFT
end
subgraph Step3["3. Trace Evaluation & Oracle"]
direction TB
Harvester["<b>Trace Harvester</b><br/>nft monitor trace stream"]
Oracle["<b>Deterministic Oracle</b><br/>TraceEvents & verdicts"]
Harvester --> Oracle
end
Step1 --> Step2
Step2 --> Step3RuleEngine.validate() esegue una prova a secco del ruleset usando nft --check -f.NetnsController crea il network namespace isolato e configura le interfacce virtual ethernet (veth).meta nftrace set 1).ScapyInjector inietta frame sintetici attraverso il link veth.TraceHarvester cattura gli eventi di nft monitor trace e restituisce oggetti TraceEvent strutturati.Per le specifiche tecniche complete, vedi la Guida all'architettura tecnica.
NetworkSandboxEngine/
├── nse/ # Core PyPI package (network-sandbox-engine)
│ ├── core/ # Kernel primitives, pipeline, and naming rules
│ ├── models/ # Pydantic models (TestRequest, PacketSpec, TraceEvent)
│ └── cli/ # Headless YAML runner entrypoint
├── docs/ # Architecture specs and MkDocs web documentation
├── tests/ # Unit, golden file, and privileged e2e tests
│ └── fixtures/nft_trace/ # Golden `nft monitor trace` corpus
├── pyproject.toml # Build backend configuration
└── Makefile # Local automation and CI workflow
Un tag. git push origin vX.Y.Z compila, firma con Sigstore, pubblica la
GitHub Release, carica su TestPyPI, installa da TestPyPI ed esegue uno smoke test,
e solo allora carica su PyPI. Prova con make release-dry.
Vedi docs/RELEASING.md.
La documentazione web interattiva completa è disponibile su:
https://onyks-os.github.io/nse/
Compila la documentazione localmente:
make docs
Servi la documentazione con hot-reload su http://127.0.0.1:8000:
make docs-serve
Esegui linting statico e test unitari:
make verify
Esegui la verifica CI locale completa (include linting, test unitari, build del frontend, build della documentazione, smoke test PyPI e test di integrazione privilegiati):
make ci-local
Questo progetto è distribuito sotto la Licenza MIT.