Skip to content

mew-sh/pamtunnel

Repository files navigation

PAMTunnel — Telegram-controlled PAM Tunnel in Rust

A minimal Privileged Access Management (PAM) tunnel system consisting of two Rust binaries:

Binary Host Role
vps-agent Public VPS (Linux) Telegram bot + tunnel orchestrator
gateway-agent Local network gateway machine (Linux) Reverse-tunnel client

Architecture

Telegram user
     │  /rdp 192.168.1.23
     ▼
┌─────────────┐   control channel (persistent TCP)   ┌──────────────────┐
│  vps-agent  │ ──────────────────────────────────── │  gateway-agent   │
│             │ ◄── ControlReply::TunnelReady ─────── │                  │
│  public     │                                       │  LAN             │
│  port XXXXX │   data channel (single-use TCP)       │                  │
│  (user RDP) │                                       │ 192.168.1.23:3389│
│             │ ◄── DataHandshake{token} ─────────── │                  │
│  data port  │                                       └──────────────────┘
│  YYYYY      │
└─────────────┘
       ▲
       │  RDP/SSH client connects to VPS_HOST:XXXXX
       │  (from .rdp file or ssh command sent by bot)

Session lifecycle

  1. User sends /rdp 192.168.1.23 to the bot.
  2. VPS binds two random ports from the configured range:
    • public_port – the end-user's RDP/SSH client will connect here.
    • data_port – the gateway will connect here as the server-side of the tunnel.
  3. VPS sends OpenTunnel { target_host, target_port, token, data_port } to the gateway over the persistent control channel.
  4. Gateway connects to VPS:data_port, sends DataHandshake { token } (one-time auth), then connects to 192.168.1.23:3389 locally.
  5. VPS authenticates the token, then waits for the user's RDP client on public_port.
  6. Once both sides are connected, VPS proxies the two TCP streams bidirectionally.
  7. When the session ends (disconnect or 120 s timeout), both ports are closed and the session is removed.

Each session uses a fresh random token and random ports — truly single-use.


Quick start — pre-built binaries

Download the latest release for your architecture from the Releases page:

File Target
pamtunnel-*-x86_64-unknown-linux-gnu.tar.gz amd64 VPS / server (glibc)
pamtunnel-*-x86_64-unknown-linux-musl.tar.gz amd64 static (containers, minimal images)
pamtunnel-*-aarch64-unknown-linux-gnu.tar.gz arm64 (Oracle ARM, Raspberry Pi 4 64-bit)
pamtunnel-*-aarch64-unknown-linux-musl.tar.gz arm64 static
pamtunnel-*-armv7-unknown-linux-gnueabihf.tar.gz armv7 (Raspberry Pi 2/3 32-bit)

Each archive contains both vps-agent and gateway-agent plus the .env.example files.


Building from source

cargo build --release

Produces:

  • target/release/vps-agent
  • target/release/gateway-agent

Deployment

VPS (runs vps-agent)

Option A — bare binary

# Copy binary
scp target/release/vps-agent user@vps:/usr/local/bin/

# Configure
cp vps-agent/vps-agent.env.example /etc/pamtunnel/vps-agent.env
$EDITOR /etc/pamtunnel/vps-agent.env

# Run
export $(cat /etc/pamtunnel/vps-agent.env | grep -v '^#' | tr -d '\r' | xargs)
RUST_LOG=info /usr/local/bin/vps-agent

Option B — Docker Compose

cp vps-agent/vps-agent.env.example vps-agent.env
$EDITOR vps-agent.env

docker compose up -d          # uses docker-compose.yml
docker compose logs -f

Firewall rules required on the VPS:

ufw allow 9000/tcp            # control port (gateway dials in)
ufw allow 20000:30000/tcp     # ephemeral user-facing port range

Gateway machine (runs gateway-agent)

Option A — bare binary

# Copy binary
scp target/release/gateway-agent user@gateway:/usr/local/bin/

# Configure
cp gateway-agent/gateway-agent.env.example /etc/pamtunnel/gateway-agent.env
$EDITOR /etc/pamtunnel/gateway-agent.env   # set VPS_HOST

# Run
export $(cat /etc/pamtunnel/gateway-agent.env | grep -v '^#' | tr -d '\r' | xargs)
RUST_LOG=info /usr/local/bin/gateway-agent

Option B — Docker Compose

cp gateway-agent/gateway-agent.env.example gateway-agent.env
$EDITOR gateway-agent.env     # set VPS_HOST

docker compose -f docker-compose.gateway.yml up -d
docker compose -f docker-compose.gateway.yml logs -f

The gateway only needs outbound TCP to VPS_HOST:CONTROL_PORT and the ephemeral data port range. No inbound ports need to be opened on the LAN machine.


Docker — building images locally

Both Dockerfiles produce statically-linked binaries (musl) via cargo-zigbuild and package them in a scratch image (~5 MB).

# Single architecture
docker build --platform linux/amd64 -f Dockerfile.vps-agent     -t pamtunnel/vps-agent:latest .
docker build --platform linux/arm64 -f Dockerfile.gateway-agent -t pamtunnel/gateway-agent:latest .

# Multi-arch manifest (requires docker buildx)
docker buildx create --use --name pamtunnel-builder
docker buildx build --platform linux/amd64,linux/arm64 \
    -f Dockerfile.vps-agent     -t youruser/vps-agent:latest     --push .
docker buildx build --platform linux/amd64,linux/arm64 \
    -f Dockerfile.gateway-agent -t youruser/gateway-agent:latest --push .

Telegram commands

Command Description
/rdp 192.168.1.23 Tunnel RDP (port 3389) from the LAN machine. Bot replies with a .rdp file.
/rdp HOME-PC Same, using a hostname resolvable on the gateway's LAN.
/ssh 192.168.1.22 Tunnel SSH (port 22). Bot replies with the ssh command.
/help Show command list.

Configuration reference

vps-agent.env

Variable Required Default Description
TELEGRAM_TOKEN Bot token from @BotFather
ALLOWED_USERS Comma-separated Telegram user IDs
VPS_HOST Public IP / hostname of the VPS (embedded in generated files)
CONTROL_PORT 9000 Port the gateway control listener binds on
PORT_RANGE_START 20000 Start of ephemeral port range
PORT_RANGE_END 30000 End of ephemeral port range
RUST_LOG info Log level (trace, debug, info, warn, error)

gateway-agent.env

Variable Required Default Description
VPS_HOST Public IP / hostname of the VPS
CONTROL_PORT 9000 Must match CONTROL_PORT on the VPS
KEEPALIVE_SECS 30 Keepalive ping interval
RUST_LOG info Log level

Security notes

  • Only Telegram user IDs listed in ALLOWED_USERS can issue commands.
  • Target host is validated (alphanumeric + - + ., RFC-1123) before use — no shell injection possible.
  • Every session uses a cryptographically random 128-bit one-time token (CSPRNG via rand).
  • Each session uses two freshly-bound random ports — never reused.
  • Sessions time out after 120 seconds if neither the gateway nor the user connects.
  • The gateway agent only dials outbound; no ports need to be open on the LAN machine.
  • Control messages are length-prefixed with a 16 MiB size guard to prevent memory exhaustion.
  • Binaries use rustls exclusively — no OpenSSL dependency.

Project structure

pamtunnel/
├── Cargo.toml                      # workspace
├── Dockerfile.vps-agent            # multi-arch Docker image (vps-agent)
├── Dockerfile.gateway-agent        # multi-arch Docker image (gateway-agent)
├── docker-compose.yml              # VPS deployment
├── docker-compose.gateway.yml      # Gateway deployment
├── .github/
│   └── workflows/
│       └── release.yml             # CI: test → cross-compile → GitHub Release
├── shared/                         # protocol types + framing helpers
│   ├── Cargo.toml
│   └── src/lib.rs
├── vps-agent/                      # Telegram bot + tunnel orchestrator
│   ├── Cargo.toml
│   ├── vps-agent.env.example
│   └── src/
│       ├── main.rs                 # entrypoint, bot dispatcher
│       ├── config.rs               # env-var config
│       ├── session.rs              # session store (DashMap)
│       ├── control.rs              # gateway control channel listener
│       ├── tunnel.rs               # per-session TCP proxy
│       ├── bot_handler.rs          # /rdp and /ssh command logic
│       └── files.rs                # .rdp file & SSH message generation
└── gateway-agent/                  # reverse-tunnel client
    ├── Cargo.toml
    ├── gateway-agent.env.example
    └── src/
        ├── main.rs                 # entrypoint, control channel loop
        ├── config.rs               # env-var config
        └── tunnel.rs               # data tunnel (VPS data port ↔ LAN target)

About

Telegram-controlled PAM Tunnel

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages