
nftables 규칙을 테스트하기 위한 결정론적 네트워크 샌드박스입니다. 임시 Linux 네트워크 네임스페이스(netns)와 Scapy를 사용하여 방화벽 로직을 안전하게 검증합니다.
NSE를 사용하는 이유 • 기능 • 요구 사항 • 설치 • 빠른 시작 • 작동 방식 • 프로젝트 구조
실제 운영 중인 Linux 시스템에서 방화벽 규칙 세트를 테스트하는 것은 상당한 위험을 수반합니다. 잘못된 규칙은 SSH 관리 세션을 끊어버리거나, 테스트 중 평문 트래픽을 유출하거나, 호스트에 고아 방화벽 테이블을 남길 수 있습니다.
Network Sandbox Engine (NSE)은 안전하고 재현 가능한 테스트 하네스를 제공합니다. 일시적인 Linux 네트워크 네임스페이스를 생성하고, 가상 이더넷 페어를 연결하고, nftables 규칙 세트를 컴파일하고, Scapy를 사용하여 합성 Layer 2 및 Layer 3 패킷을 주입합니다. 모든 평가는 샌드박스 네임스페이스 내부에서 이루어지며, 호스트 방화벽 상태는 절대 변경되지 않습니다.
핵심 아키텍처 특성:
nse_<uuid>)에만 로드되며, 정리 단계에서 완전히 제거됩니다.nse/는 98% 테스트 커버리지에서 약 1150개의 문장으로 구성됩니다.NSE는 네트워크 네임스페이스를 생성하고, nftables 규칙 세트를 로드하며, 커널 트레이스 이벤트를 읽기 때문에 root로 실행됩니다. 소켓, 포트, 또는 어떤 종류의 RPC 엔드포인트도 열지 않습니다. 즉, 사용자가 호출하는 라이브러리이자 CLI이며, 실행 기간 동안에만 권한을 보유합니다.
버전 2.1.0에서는 이전 릴리스에 포함되었던 FastAPI/Svelte 웹 인터페이스가 제거되었습니다. 해당 인터페이스는 2.0.0부터 root 권한으로 프로세스 내에서 실행되었으며, 이는 테스트 도구로서는 너무 큰 공격 표면이었습니다. 필요한 경우 코드는 git 히스토리의 v2.0.0 태그에 남아 있습니다.
방화벽 테스트는 부정적 단언(negative assertion)입니다. 즉, *"이 패킷은 통과하지 못했다"*라는 것이며, 부정적 단언은 측정 도구가 작동한다는 것이 확인되지 않으면 아무 가치가 없습니다. 커널에 전혀 연결되지 않은 트레이스 모니터와 모든 것을 차단한 방화벽은 바이트 단위로 동일한 출력을 생성합니다.
따라서 NSE는 측정했음을 입증할 수 없는 판정은 보고하지 않습니다:
카나리 패킷은 트레이스 id로 결과에서 제외되므로, 판정 스트림에 절대 나타나지 않습니다.
이 스위트는 이를 단언하는 대신 실제로 성립함을 증명합니다. make test-blind는 파서가 아무것도 이해하지 못하도록 강제하며, 러너가 0이 아닌 값으로 종료되지 않으면 빌드가 실패합니다. 이 작업은 모든 푸시에서 CI로 실행됩니다.
TestRequest, TraceEvent)을 반환하는 직접 Python API(run_test_pipeline).nse_<id>).nse_router_<id>)와 서버(nse_server_<id>) 체인.nse-runner). 잘못된 판정 및 관찰하지 못한 판정 모두에서 0이 아닌 값으로 종료됩니다.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 객체를 반환합니다.전체 기술 사양은 기술 아키텍처 가이드를 참조하세요.
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
하나의 태그. git push origin vX.Y.Z는 빌드하고, Sigstore로 서명하고, GitHub Release를 게시하고, TestPyPI에 업로드하고, TestPyPI에서 설치하여 스모크 테스트한 후에야 PyPI에 업로드합니다. make release-dry로 리허설할 수 있습니다.
docs/RELEASING.md를 참조하세요.
전체 대화형 웹 문서는 다음에서 확인할 수 있습니다:
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에 따라 라이선스가 부여됩니다.
| 보장 | 메커니즘 |
|---|
| 모니터가 첫 번째 테스트 패킷 이전에 연결되었음 | 준비 카나리(readiness canary)를 주입하고, 커널 트레이스가 관찰될 때까지 재주입합니다. 관찰되지 않으면 실행하지 않습니다. |
| 모니터가 마지막 패킷 이후에도 여전히 연결되어 있었음 | 주입 후 활성 카나리(liveness canary)가 실행됩니다. 이를 놓치면 판정 스트림이 잘린 것으로 선언됩니다. |
| 파서가 커널이 말한 내용을 이해했음 | 어떤 패턴과도 일치하지 않는 트레이스 라인은 집계되며, 0보다 큰 집계 값은 디버그 로그가 아닌 오류로 처리됩니다. |
| 모니터가 조용히 죽지 않았음 | 읽기 루프는 왜 종료되었는지(정상 중지, 예기치 않은 EOF, 타임아웃 또는 충돌)를 기록하며, 정상 중지만 허용됩니다. |
| 누락된 판정은 통과가 아님 | CLI 러너는 관찰된 판정 수가 예상 수와 다를 경우(양방향 모두) 실패합니다. |