
nftablesルールをテストするための決定論的ネットワークサンドボックス。一時的なLinuxネットワーク名前空間(netns)とScapyを使用して、ファイアウォールロジックを安全に検証します。
Why NSE? • Features • Requirements • Installation • Quickstart • How It Works • Project Structure
稼働中の Linux システム上でファイアウォールルールセットをテストすることには重大なリスクが伴います。不正なルールは SSH 管理セッションを切断したり、テスト中に平文トラフィックを漏洩させたり、ホスト上に孤立したファイアウォールテーブルを残したりする可能性があります。
Network Sandbox Engine (NSE) は、安全で再現可能なテストハーネスを提供します。一時的な Linux ネットワーク名前空間を構築し、仮想イーサネットペアを接続し、nftables ルールセットをコンパイルし、Scapy を使用して合成レイヤー2およびレイヤー3パケットを注入します。すべての評価はサンドボックス名前空間内で行われ、ホストのファイアウォール状態は一切変更されません。
主要なアーキテクチャ特性:
nse_<uuid>) にのみロードされ、ティアダウン時に完全に削除されます。nse/ は約1150ステートメントで、テストカバレッジは98%です。NSE はネットワーク名前空間を作成し、nftables ルールセットをロードし、カーネルトレースイベントを読み取るため、root として実行されます。ソケット、ポート、RPC エンドポイントのいずれも開きません。これは呼び出して使用するライブラリおよび CLI であり、実行期間中のみ特権を保持します。
バージョン 2.1.0 では、以前のリリースに同梱されていた FastAPI/Svelte Web インターフェースが削除されました。このインターフェースは 2.0.0 以降、root としてインプロセスで実行されており、テストツールとしては大きな攻撃面となっていました。必要であれば、コードはタグ v2.0.0 の git 履歴に残っています。
ファイアウォールテストは否定的な主張 — 「このパケットは通過しなかった」 — であり、計測器が正常に動作していることが確認されない限り、否定的な主張には何の価値もありません。カーネルに一度もアタッチしなかったトレースモニターと、すべてをブロックしたファイアウォールは、バイト単位で同一の出力を生成します。
したがって NSE は、測定したことを示せない判定を報告することを拒否します:
カナリアパケットはトレース ID によって結果から除外されるため、判定ストリームに現れることはありません。
スイートはこれを主張するのではなく、成り立つことを証明します。make test-blind はパーサーに何も理解させないように強制し、ランナーが非ゼロで終了しない限りビルドは失敗します。このジョブは毎回のプッシュで CI 上で実行されます。
run_test_pipeline) が構造化された Pydantic モデル (TestRequest、TraceEvent) を返します。nse_<id>) がホストに直接接続されます。nse_router_<id>) とサーバー (nse_server_<id>) のチェーン。nse-runner)。誤った判定の場合および観測に失敗した判定の場合に非ゼロで終了します。mypy --strict)、アーキテクチャ境界の強制 (import-linter)、ruff フォーマット、カバレッジラチェット (make test-cov、下限98%)。nft)ip)ip netns およびカーネルトレース操作に必要)Debian または Ubuntu システムの場合:
sudo apt update && sudo apt install -y nftables iproute2 conntrack
CLI サポート付きのコアエンジンをインストールします:
pip install "network-sandbox-engine[cli]"
ローカル開発の場合:
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())
テストファイル 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 はパケットごとです。未知のキーはデフォルト値ではなく拒否されるため、タイプミスはスイートを失敗させます。書いていない期待値に静かに変わることはありません。
root 権限でスイートを実行します:
sudo nse-runner --file firewall_test.yaml
終了コード: 0 はすべてのパケットが一致。1 は判定が誤っていたまたはエンジンが判定を観測できなかった。オラクルエラーはファイアウォールの失敗とは別に報告されます。これはルールセットではなく測定が壊れたことを意味するためです。
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
ルールのテスト対象となる nftables のバージョンを固定するのに便利です。
NSE は Linux カーネルのネットワークサブシステムとトレースインターフェースを、構造化された多段階の実行パイプラインを通じてオーケストレーションします:
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() は nft --check -f を使用してルールセットをドライランします。NetnsController が分離されたネットワーク名前空間を作成し、仮想イーサネット (veth) インターフェースを構成します。meta nftrace set 1) 名前空間にロードされます。ScapyInjector が veth リンクを介して合成フレームを注入します。TraceHarvester が nft monitor trace イベントをキャプチャし、構造化された TraceEvent オブジェクトを返します。完全な技術仕様については、Technical Architecture Guide を参照してください。
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
1つのタグ。git push origin vX.Y.Z がビルドし、Sigstore で署名し、GitHub Release を公開し、TestPyPI にアップロードし、TestPyPI からインストールしてスモークテストし、その後でのみ PyPI にアップロードします。make release-dry でリハーサルできます。
docs/RELEASING.md を参照してください。
完全なインタラクティブ Web ドキュメントは以下で利用可能です:
https://onyks-os.github.io/nse/
ドキュメントをローカルでビルドします:
make docs
http://127.0.0.1:8000 でホットリロード付きのドキュメントを配信します:
make docs-serve
静的リンティングとユニットテストを実行します:
make verify
完全なローカル CI 検証を実行します (リンティング、ユニットテスト、フロントエンドビルド、ドキュメントビルド、PyPI スモークテスト、特権統合テストを含む):
make ci-local
このプロジェクトは MIT License の下でライセンスされています。
| 保証 | メカニズム |
|---|
| モニターが最初のテストパケットの前にアタッチされていた | レディネスカナリアが注入され、そのカーネルトレースが観測されるまで再注入されます。観測がなければ実行もありません。 |
| モニターが最後のテストパケットの後も依然としてアタッチされていた | 注入後にライブネスカナリアが実行されます。これが見逃された場合、判定ストリームは切り詰められたと宣言されます。 |
| パーサーがカーネルの発言を理解した | どのパターンにも一致しないトレース行がカウントされ、カウントがゼロを超える場合はデバッグログではなくエラーとなります。 |
| モニターが静かに死ななかった | 読み取りループは、それがなぜ終了したか — クリーンストップ、予期しない EOF、タイムアウト、クラッシュ — を記録し、クリーンストップのみが許容されます。 |
| 判定の欠落は合格ではない | CLI ランナーは、観測された判定の数が期待される数と異なる場合、どちらの方向であっても失敗します。 |