Skip to content

ivere27/nitella

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nitella

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).

Architecture

┌──────────────────┐                    ┌──────────────────┐
│   nitella CLI    │                    │   Hub Server     │
│   (or Mobile)    │◄──────────────────►│   (Blind Relay)  │
│                  │     E2E Encrypted  │                  │
└──────────────────┘                    └────────┬─────────┘
        │                                        │
        │ P2P (WebRTC)                          │ mTLS
        │ bypasses Hub                          │
        │ when possible                         ▼
        │                               ┌──────────────────┐
        └──────────────────────────────►│   nitellad       │
                                        │   (Proxy Node)   │
                                        └──────────────────┘

Key Principles

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

Security

  • 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.

Modules

Nitellad - TCP Reverse Proxy

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.

GeoIP

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.

Mock Server

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.

Quick Start

Nitellad (Reverse Proxy)

# 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 --mtls

Standalone Nitellad Proxy

Standalone 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/24

CIDR 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: 20

Proxy persistence is disabled by default. For clean Go/Rust behavior comparisons, omit --db-path so no old proxies or rules are restored.

Nitella CLI

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.8

CLI Commands:

  • status [proxy_id] - Show proxy status
  • list - List all proxies
  • proxy create <addr> <backend> - Create a new proxy
  • proxy delete <proxy_id> - Delete a proxy
  • rule list <proxy_id> - List rules for a proxy
  • rule add <proxy_id> <allow|block> <ip> - Add a rule
  • rule remove <proxy_id> <rule_id> - Remove a rule
  • conn [proxy_id] - List active connections (all proxies if no id)
  • conn close <proxy_id> <conn_id> - Close a connection
  • conn 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 status
  • geoip config local <city_db> [isp_db] - Configure local MaxMind DB
  • geoip config remote <provider> - Configure remote API provider
  • lookup <ip> - GeoIP lookup for an IP
  • stream - Stream connection events (Ctrl+C to stop)
  • approve [cache|once] <id> [duration] - Approve a pending connection request
  • deny [cache|once] <id> [duration] [reason] - Deny a pending connection request
  • pending - List pending approval requests
  • block <ip> [duration_seconds] - Quick block an IP globally
  • allow <ip> [duration_seconds] - Quick allow an IP globally
  • global-rules list - List active global rules
  • global-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/nitella

Passphrase 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 commands
  • Up/Down - Navigate command history
  • Ctrl+A/E - Go to start/end of line
  • Ctrl+W - Delete word backward
  • Alt+Backspace - Delete word backward
  • Ctrl+Left/Right - Jump to previous/next word
  • Alt+Left/Right - Jump to previous/next word
  • Ctrl+L - Clear screen
  • Ctrl+C - Cancel current command (double-press to exit)

Hub Mode (Remote Management)

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 certificate

See docs/HUB.md for detailed Hub architecture and setup.

P2P Mode

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:3478

P2P connections are authenticated via certificate exchange and DTLS encrypted.

Proxy Templates

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.

Approval System

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 connection

See docs/APPROVAL_SYSTEM.md for detailed documentation.

GeoIP

# 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

Mock

# 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=ssh

Installation

git clone https://github.com/ivere27/nitella.git
cd nitella
make build

Requirements

  • Go 1.22+
  • protoc (Protocol Buffers compiler)
  • protoc-gen-go, protoc-gen-go-grpc

Build synurang FFI plugin (optional)

For FFI GeoIP support:

make build_plugin

Project Structure

nitella/
├── 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

Development

# Build all modules
make build

# Run all tests
make test

# Generate protobuf files
make proto

# Format code
make fmt

# Clean build artifacts
make clean

Module-specific commands

# 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

Configuration Example

# 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"

Docker

Quick Start

# 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=ssh

Standalone Daemon Images

The 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.mmdb

To 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.db

Use 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.mmdb

Use 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.

Known Issue: Firewall Blocking Docker Traffic

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/16

This rule persists across reboots.

Third-Party Data

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.

License

Apache License 2.0 - see LICENSE for details.

About

A Zero-Trust Layer 4 reverse proxy with E2E encryption and real-time 2FA approvals.

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors