A mobile-controlled Layer 4 reverse proxy with Zero-Trust architecture. The Hub acts as a "blind relay" - all commands, metrics, and configurations are end-to-end encrypted.
Note: nitellad-rs/ is an experimental/benchmark Rust implementation. The default build/test/runtime path in this repository is the Go implementation (cmd/nitellad).
┌──────────────────┐ ┌──────────────────┐
│ nitella CLI │ │ Hub Server │
│ (or Mobile) │◄──────────────────►│ (Blind Relay) │
│ │ E2E Encrypted │ │
└──────────────────┘ └────────┬─────────┘
│ │
│ P2P (WebRTC) │ mTLS
│ bypasses Hub │
│ when possible ▼
│ ┌──────────────────┐
└──────────────────────────────►│ nitellad │
│ (Proxy Node) │
└──────────────────┘
| Principle | Description |
|---|---|
| Zero Trust Hub | Hub cannot decrypt commands or metrics - only relays encrypted blobs |
| Mobile as Root CA | Your mobile device holds the Root CA private key |
| P2P First | Direct WebRTC connections when possible, Hub relay as fallback |
| Blind Routing | Hub routes via opaque tokens - cannot correlate user to nodes |
- Encryption: X25519 ECDH + AES-256-GCM for E2E encryption
- Authentication: mTLS with certificates signed by your CA
- Pairing: PAKE (Password-Authenticated Key Exchange) or QR code
- Replay Protection: Timestamps + request IDs + Ed25519 signatures
See THREAT_MODEL.md for detailed security analysis.
A security-first Layer 4 (TCP) reverse proxy with intelligent traffic routing, statistics, and mock services.
- Rule Engine: Match by IP, CIDR, GeoIP (country/city/ISP), TLS certificate attributes
- Mock Services: Honeypot mode with SSH/RDP/HTTP/MySQL tarpits
- Statistics: Connection tracking, aggregation by IP/Country/ISP
- Rate Limiting: Fail2Ban-style auto-escalation blocking
- mTLS: Certificate-based client authentication
- Process Isolation: Option to run listeners in separate OS processes
- FFI GeoIP: Zero-copy GeoIP lookups via synurang FFI
See docs/REVERSE_PROXY.md for detailed documentation.
High-performance GeoIP lookup service with multi-layer caching and multiple provider support.
- Multi-layer Caching: L1 (in-memory LRU) + L2 (SQLite persistent)
- Multiple Data Sources: Local MaxMind databases, remote HTTP providers with automatic failover
- Configurable Strategy: Define lookup order, hot-reload configuration
- gRPC API: Public lookup service + Admin management service
- FFI Support: Zero-copy Go-to-Go FFI via synurang
See docs/GEOIP.md for detailed documentation.
Honeypot/mock server that emulates various network protocols to detect scanning and waste attacker resources.
- Multiple Protocols: HTTP, SSH, MySQL, MSSQL, Redis, SMTP, Telnet, RDP
- Tarpit Mode: Waste attacker time with slow/endless responses
- Drip Mode: Send data byte-by-byte to tie up scanners
- Customizable: Custom payloads, delays, and behaviors
See docs/MOCK.md for detailed documentation.
# Build
make nitellad_build nitella_build
# Run with backend
./bin/nitellad --listen :8080 --backend localhost:3000
# Run with config file
./bin/nitellad --config proxy.yaml
# Run with Admin API (generates random token if not provided)
./bin/nitellad --listen :8080 --backend localhost:3000 --admin-port 50051
# Run with specific admin token
./bin/nitellad --listen :8080 --backend localhost:3000 \
--admin-port 50051 --admin-token your-secret-token
# Run in process mode (each proxy as separate child process)
./bin/nitellad --listen :8080 --backend localhost:3000 --process-mode
# Run with GeoIP
./bin/nitellad --config proxy.yaml --geoip-city /path/to/GeoLite2-City.mmdb
# With mTLS
./bin/nitellad --listen :8443 --backend localhost:3000 \
--tls-cert server.crt --tls-key server.key --tls-ca ca.crt --mtlsStandalone mode runs a local TCP proxy directly from nitellad command-line flags. It does not require Hub, the nitella CLI, YAML config, or proxy persistence.
Use this mode when comparing Go nitellad and Rust nitellad-rs behavior against the same backend.
# Start an example backend on localhost:3000
make example_backend
# Go daemon
make nitellad_run NITELLAD_ARGS="--listen :8080 --backend localhost:3000"
# Rust daemon
make nitellad_rs_run NITELLAD_ARGS="--listen :8080 --backend localhost:3000"Standalone CLI rule flags:
| Flag | Meaning |
|---|---|
--default-action allow |
Allow traffic when no rule matches |
--default-action block |
Block traffic when no rule matches |
--default-action require_approval |
Hold unmatched traffic for approval |
--fallback-action mock |
Send blocked or failed connections to a mock response |
--fallback-mock <preset> |
Mock/tarpit preset for fallback, such as ssh-tarpit |
--allow-ip <list> |
Allow exact source IPs, CIDRs, or standalone aliases |
--block-ip <list> |
Block exact source IPs, CIDRs, or standalone aliases |
--allow-country <list> |
Allow GeoIP country codes |
--block-country <list> |
Block GeoIP country codes |
--rate-limit-max-connections <n> |
Enable per-IP rate limiting on the default rule |
--rate-limit-interval <seconds> |
Counting window for the limit |
--rate-limit-count-only-failures |
Count only short-lived connections as failures |
--rate-limit-failure-threshold <seconds> |
Failure threshold for short-lived connections |
--rate-limit-auto-block[=false] |
Enable or disable temporary blocking after the limit is exceeded |
--rate-limit-block-duration <seconds> |
Temporary auto-block duration |
--rate-limit-block-steps <list> |
Optional escalation block durations |
Exact IP and CIDR examples:
--allow-ip 127.0.0.1,::1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12
--block-ip 203.0.113.10,198.51.100.0/24CIDR matching is source-IP based. For example, 192.168.0.0/16 matches 192.168.10.20, but not 192.169.0.1.
Standalone IP aliases are accepted only by daemon startup flags in standalone mode:
| Alias | Expands to |
|---|---|
localhost |
127.0.0.0/8,::1/128 |
private |
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7 |
local |
localhost + private + 169.254.0.0/16,fe80::/10 |
These aliases are not general rule syntax for YAML, DB, Hub, or runtime admin APIs. Those paths continue to use explicit IP/CIDR values.
KR-only proxy with local/LAN test traffic allowed:
make nitellad_rs_run NITELLAD_ARGS="\
--listen :8080 \
--backend localhost:3000 \
--default-action block \
--allow-country KR \
--allow-ip local \
--geoip-city /path/to/GeoLite2-City.mmdb \
--geoip-isp /path/to/GeoLite2-ASN.mmdb"The same NITELLAD_ARGS can be used with make nitellad_run for the Go daemon.
Fail2ban-style standalone protection:
make nitellad_rs_run NITELLAD_ARGS="\
--listen :8080 \
--backend localhost:3000 \
--rate-limit-max-connections 3 \
--rate-limit-interval 60 \
--rate-limit-count-only-failures \
--rate-limit-failure-threshold 20 \
--rate-limit-block-duration 1800 \
--fallback-action mock \
--fallback-mock ssh-tarpit"This blocks a source IP for 30 minutes after 3 connections that each close in under 20 seconds within a 60-second window. While blocked, new connections are handled by the SSH tarpit instead of being closed.
Block selected countries while allowing everyone else:
make nitellad_rs_run NITELLAD_ARGS="\
--listen :8080 \
--backend localhost:3000 \
--default-action allow \
--block-country US,JP \
--geoip-city /path/to/GeoLite2-City.mmdb \
--geoip-isp /path/to/GeoLite2-ASN.mmdb"Rule priority in standalone mode:
| Rule source | Priority |
|---|---|
--block-ip, --block-country |
110 |
--allow-ip, --allow-country |
100 |
| default rule | -1000 |
If a connection matches both an allow and block startup rule, block wins.
Local/private IPs such as 127.0.0.1, ::1, 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 do not resolve to GeoIP countries. For strict country filtering during local tests, include --allow-ip local or explicit --allow-ip entries for localhost and LAN CIDRs.
Startup source precedence:
| Mode | Startup source |
|---|---|
--listen + --backend |
CLI standalone proxy and CLI startup rules |
--config proxy.yaml |
YAML-owned proxies; CLI startup rules are not applied |
--db-path nitella.db |
Restores persisted proxies/rules and persists new runtime rules |
For YAML standalone config, put the equivalent setting on the entrypoint:
entryPoints:
web:
address: ":8080"
defaultAction: allow
fallbackAction: mock
fallbackMock: ssh-tarpit
rateLimit:
maxConnections: 3
intervalSeconds: 60
autoBlock: true
blockDurationSeconds: 1800
countOnlyFailures: true
failureDurationThreshold: 20Proxy persistence is disabled by default. For clean Go/Rust behavior comparisons, omit --db-path so no old proxies or rules are restored.
Connect to nitellad's admin API to manage proxies, rules, and connections.
# Build CLI
make nitella_build
# Connect to admin API (use token from daemon logs)
./bin/nitella --addr localhost:50051 --token your-admin-token
# Interactive shell
nitella> help
nitella> status
nitella> list
# Single command mode
./bin/nitella --addr localhost:50051 --token your-token status
./bin/nitella --addr localhost:50051 --token your-token lookup 8.8.8.8CLI Commands:
status [proxy_id]- Show proxy statuslist- List all proxiesproxy create <addr> <backend>- Create a new proxyproxy delete <proxy_id>- Delete a proxyrule list <proxy_id>- List rules for a proxyrule add <proxy_id> <allow|block> <ip>- Add a rulerule remove <proxy_id> <rule_id>- Remove a ruleconn [proxy_id]- List active connections (all proxies if no id)conn close <proxy_id> <conn_id>- Close a connectionconn closeall [proxy_id]- Close all connections (all proxies if no id)block <ip>- Quick block an IP (all proxies)allow <ip>- Quick allow an IP (all proxies)metrics- Stream real-time metrics (Ctrl+C to stop)geoip status- Show GeoIP service statusgeoip config local <city_db> [isp_db]- Configure local MaxMind DBgeoip config remote <provider>- Configure remote API providerlookup <ip>- GeoIP lookup for an IPstream- Stream connection events (Ctrl+C to stop)approve [cache|once] <id> [duration]- Approve a pending connection requestdeny [cache|once] <id> [duration] [reason]- Deny a pending connection requestpending- List pending approval requestsblock <ip> [duration_seconds]- Quick block an IP globallyallow <ip> [duration_seconds]- Quick allow an IP globallyglobal-rules list- List active global rulesglobal-rules remove <id>- Remove a global rule
Identity & Security:
The CLI creates a cryptographic identity (Ed25519 keypair with BIP-39 mnemonic) on first launch. You can protect the private key with a passphrase:
# First launch - will prompt for optional passphrase
./bin/nitella
# Or provide passphrase via flag/env (for automation)
./bin/nitella --passphrase "your-secret"
NITELLA_PASSPHRASE="your-secret" ./bin/nitellaPassphrase Strength Analysis:
When setting a passphrase, the CLI analyzes its strength and shows:
- Entropy in bits
- Estimated crack time against realistic GPU clusters
- Attack scenario (up to all hyperscalers combined with 1M H100 GPUs)
Passphrase Security Analysis:
✓ STRONG: very strong
Entropy: 133.1 bits
Crack time: heat death of universe
Scenario: all hyperscalers combined (1M H100s) @ 500000000 hashes/sec
The key is encrypted with Argon2id + AES-256-GCM. KDF parameters are stored in the encrypted file for future compatibility. The default profile is used automatically:
| Profile | Memory | Iterations | Use Case |
|---|---|---|---|
server |
32 MB | 1 | High-throughput servers |
default |
64 MB | 2 | CLI tools (OWASP recommended) |
secure |
128 MB | 3 | Password managers, wallets |
Keyboard Shortcuts:
Tab- Auto-complete commandsUp/Down- Navigate command historyCtrl+A/E- Go to start/end of lineCtrl+W- Delete word backwardAlt+Backspace- Delete word backwardCtrl+Left/Right- Jump to previous/next wordAlt+Left/Right- Jump to previous/next wordCtrl+L- Clear screenCtrl+C- Cancel current command (double-press to exit)
The CLI supports Hub mode for secure remote management of nitellad nodes via a central relay server. All commands and data are end-to-end encrypted - the Hub cannot read your traffic. Hub mode is the default - use --local for direct nitellad connection.
# Configure Hub connection
nitella config set hub hub.example.com:50052
nitella login
# Node pairing (PAKE - Hub learns nothing)
nitella pair # Generate pairing code
nitella pair-offline # QR code pairing for air-gapped setups
# List and manage remote nodes
nitella nodes
nitella node <node-id> status
# Handle approval requests (when using REQUIRE_APPROVAL action)
nitella pending # List pending requests
nitella approve cache <id> 1h # Approve for 1 hour (cached)
nitella approve once <id> # Approve this connection only
nitella deny <id> # Deny this connection
nitella deny cache <id> 1h reason # Deny and cache for 1 hour
# Identity management
nitella identity # Show identity info
nitella identity export-ca # Export Root CA certificateSee docs/HUB.md for detailed Hub architecture and setup.
When possible, the CLI connects directly to nodes via WebRTC, bypassing the Hub entirely:
# Connect to node with P2P (default behavior)
nitella node <node-id> status
# Use custom STUN server
NITELLA_STUN="stun:stun.cloudflare.com:3478" nitella node <node-id>
# Or via flag at startup
nitella --stun stun:stun.cloudflare.com:3478P2P connections are authenticated via certificate exchange and DTLS encrypted.
Version-controlled proxy configurations stored encrypted on Hub:
# Import and push to Hub
nitella proxy import config.yaml --name "Production"
nitella proxy push <proxy-id> -m "Added GeoIP rules"
# View history and diff
nitella proxy history <proxy-id>
nitella proxy diff <proxy-id> --rev1 2 --rev2 4
# Apply to node
nitella proxy apply <proxy-id> <node-id>See docs/PROXY_TEMPLATE.md for details.
Real-time connection approval for zero-trust access control. When a proxy uses require_approval action, incoming connections are held until you approve or deny them.
# proxy.yaml - require approval for all connections
proxy:
default_action: require_approval
default_backend: "10.0.0.1:3306"# Approval requests appear in real-time
nitella>
⚠ APPROVAL REQUIRED (req: abc123)
Source: 1.2.3.4 (CN, Beijing)
Dest: prod-db:5432
nitella> approve cache abc123 1h # Allow for 1 hour (cached)
nitella> approve once abc123 # Allow this connection only
nitella> deny abc123 # Deny this connectionSee docs/APPROVAL_SYSTEM.md for detailed documentation.
# Build
make geoip_build
# Run server (auto-detects geoip_provider.yaml)
make geoip_run_local
# Run CLI
make geoip_run_cli TOKEN=your-admin-token
# Docker (includes GeoLite2 databases)
make geoip_docker_run GEOIP_TOKEN=your-secret-token# Build
make mock_build
# Run SSH honeypot
make mock_run PORT=2222 PROTOCOL=ssh
# Run with tarpit mode (wastes attacker time)
./bin/mock -port 22 -protocol ssh -tarpit
# Docker
make mock_docker_run PORT=2222 PROTOCOL=sshgit clone https://github.com/ivere27/nitella.git
cd nitella
make build- Go 1.22+
- protoc (Protocol Buffers compiler)
- protoc-gen-go, protoc-gen-go-grpc
For FFI GeoIP support:
make build_pluginnitella/
├── api/ # Protobuf definitions
│ ├── common/ # Shared types (ActionType, ConditionType, etc.)
│ ├── proxy/ # Proxy control service
│ ├── hub/ # Hub relay service (node, mobile, admin)
│ ├── local/ # Mobile backend FFI service
│ ├── process/ # Child process IPC
│ └── geoip/ # GeoIP service
├── app/ # Flutter mobile app
│ ├── lib/screens/ # UI screens
│ └── lib/ # Generated proto + services
├── cmd/
│ ├── nitellad/ # Reverse proxy daemon + Hub server
│ ├── nitella/ # Proxy admin CLI
│ ├── geoip-server/ # GeoIP server binary
│ ├── geoip/ # GeoIP CLI binary
│ └── mock/ # Mock server binary
├── pkg/
│ ├── api/ # Generated protobuf code
│ ├── node/ # Proxy engine (listener, rules, stats)
│ ├── server/ # gRPC server implementations
│ ├── service/ # Mobile backend logic (FFI via Synurang)
│ ├── hub/ # Hub server implementation
│ ├── identity/ # BIP-39 identity management
│ ├── pairing/ # PAKE and QR code pairing
│ ├── p2p/ # WebRTC P2P connections
│ ├── crypto/ # E2E encryption (X25519, AES-256-GCM)
│ ├── config/ # YAML config loader
│ ├── geoip/ # GeoIP library
│ ├── mockproto/ # Mock protocol handlers
│ ├── shell/ # CLI utilities
│ └── log/ # Logging utility
├── test/
│ └── integration/ # Integration tests
├── docs/ # Documentation
├── go.mod
├── Makefile
└── README.md
# Build all modules
make build
# Run all tests
make test
# Generate protobuf files
make proto
# Format code
make fmt
# Clean build artifacts
make clean# Nitellad (Reverse Proxy)
make nitellad_build # Build daemon
make nitella_build # Build CLI
make nitellad_run # Run daemon
make nitella_run TOKEN=xxx # Run CLI
make nitellad_test # Run unit tests
make nitellad_test_integration # Run integration tests
# GeoIP
make geoip_build # Build server + CLI
make geoip_test # Run unit tests
make geoip_test_integration # Run integration tests
make geoip_run_local # Run server locally
make geoip_run_cli TOKEN=xxx # Run admin CLI
make geoip_docker_run # Run in Docker
# Mock
make mock_build # Build mock server
make mock_test # Run unit tests
make mock_test_integration # Run integration tests
make mock_run # Run (default: HTTP on 8080)
make mock_run PORT=22 PROTOCOL=ssh # Run SSH mock
make mock_docker_run # Run in Docker
# Plugin
make build_plugin # Build synurang FFI plugin# proxy.yaml
entrypoints:
web:
address: ":8443"
default_action: allow
honeypot:
address: ":22"
default_action: mock
default_mock: ssh-tarpit
tcp:
routers:
web-router:
entryPoints: ["web"]
service: backend-svc
services:
backend-svc:
address: "192.168.1.100:80"# Build and run nitellad with backend on host
make docker_nitellad_run BACKEND=host.docker.internal:3000
# With custom ports
make docker_nitellad_run BACKEND=host.docker.internal:3000 PROXY_PORT=9090
# GeoIP server
make geoip_docker_run GEOIP_TOKEN=your-secret-token
# Mock server
make mock_docker_run PORT=2222 PROTOCOL=sshThe published daemon images can run a single standalone proxy directly from CLI flags. This mode does not require Hub or a YAML config.
If the backend is running on the Docker host, use host.docker.internal and add Docker's host-gateway mapping on Linux:
# Go nitellad image
docker run --rm -it \
--add-host=host.docker.internal:host-gateway \
-p 8080:8080 \
docker.io/ivere27/nitellad:latest \
--listen :8080 \
--backend host.docker.internal:3000 \
--geoip-city /app/db/GeoLite2-City.mmdb \
--geoip-isp /app/db/GeoLite2-ASN.mmdb
# Rust nitellad-rs image
docker run --rm -it \
--add-host=host.docker.internal:host-gateway \
-p 8080:8080 \
docker.io/ivere27/nitellad-rs:latest \
--listen :8080 \
--backend host.docker.internal:3000 \
--geoip-city /app/db/GeoLite2-City.mmdb \
--geoip-isp /app/db/GeoLite2-ASN.mmdbTo keep proxy/admin data across restarts, mount /app/data and provide the database paths explicitly:
mkdir -p data
docker run --rm -it \
--add-host=host.docker.internal:host-gateway \
-p 8080:8080 \
-p 50051:50051 \
-e NITELLA_TOKEN=your-secret-token \
-v "$PWD/data:/app/data" \
docker.io/ivere27/nitellad:latest \
--listen :8080 \
--backend host.docker.internal:3000 \
--admin-port 50051 \
--db-path /app/data/nitella.db \
--stats-db /app/data/stats.db \
--geoip-city /app/db/GeoLite2-City.mmdb \
--geoip-isp /app/db/GeoLite2-ASN.mmdb \
--geoip-cache /app/data/geoip_cache.dbUse docker.io/ivere27/nitellad-rs:latest in the same command to run the Rust implementation. The nitellad-rs image uses the same /app/nitellad entrypoint, with /app/nitellad symlinked to /app/nitellad-rs.
KR-only proxy with local/LAN test traffic allowed:
docker run --rm -it \
--add-host=host.docker.internal:host-gateway \
-p 8080:8080 \
docker.io/ivere27/nitellad-rs:latest \
--listen :8080 \
--backend host.docker.internal:3000 \
--default-action block \
--allow-country KR \
--allow-ip local \
--geoip-city /app/db/GeoLite2-City.mmdb \
--geoip-isp /app/db/GeoLite2-ASN.mmdbUse docker.io/ivere27/nitellad:latest in the same command to run the Go implementation.
When command arguments are supplied after the image name, Docker replaces the image CMD; include --listen, --backend, and any database or GeoIP paths you need.
When using host.docker.internal to reach services on the host, firewalls like ufw may block traffic from the Docker network.
Symptom: Proxy starts but connections timeout when reaching the backend.
Failed to dial backend host.docker.internal:18000: dial tcp 172.17.0.1:18000: i/o timeout
Solution: Allow traffic from Docker's bridge network:
sudo ufw allow from 172.17.0.0/16This rule persists across reboots.
This project uses GeoLite2 data created by MaxMind, available from https://www.maxmind.com.
The GeoLite2 databases are licensed under CC BY-SA 4.0 and are free for commercial use with attribution.
Apache License 2.0 - see LICENSE for details.