|
| 1 | +# Architecture |
| 2 | + |
| 3 | +Chisel is a TCP/UDP tunneling tool transported over HTTP and secured via SSH. A single Go binary acts as both client and server. It is a fork of [jpillora/chisel](https://github.com/jpillora/chisel), maintained by OutSystems with custom build/release pipelines. |
| 4 | + |
| 5 | +## High-Level Overview |
| 6 | + |
| 7 | +```mermaid |
| 8 | +graph LR |
| 9 | + subgraph Client Side |
| 10 | + App["Application"] |
| 11 | + CP["Chisel Client<br/>(Proxy Listener)"] |
| 12 | + end |
| 13 | +
|
| 14 | + subgraph Transport |
| 15 | + WS["WebSocket over HTTP/S"] |
| 16 | + SSH["SSH Session<br/>(encrypted)"] |
| 17 | + end |
| 18 | +
|
| 19 | + subgraph Server Side |
| 20 | + SP["Chisel Server<br/>(HTTP + WS Handler)"] |
| 21 | + Target["Target Service"] |
| 22 | + end |
| 23 | +
|
| 24 | + App -->|"TCP/UDP"| CP |
| 25 | + CP -->|"SSH channel<br/>inside WebSocket"| WS |
| 26 | + WS --- SSH |
| 27 | + SSH -->|"SSH channel<br/>inside WebSocket"| SP |
| 28 | + SP -->|"TCP/UDP"| Target |
| 29 | +``` |
| 30 | + |
| 31 | +**Forward tunnel:** Client listens locally, traffic flows through the server to the target. |
| 32 | + |
| 33 | +**Reverse tunnel:** Server listens, traffic flows back through the client to a target behind the client. |
| 34 | + |
| 35 | +## Project Structure |
| 36 | + |
| 37 | +``` |
| 38 | +chisel/ |
| 39 | +├── main.go # CLI entrypoint — parses flags, delegates to client or server |
| 40 | +├── client/ # Client package |
| 41 | +│ ├── client.go # Client struct, config, initialization, TLS, SSH setup |
| 42 | +│ └── client_connect.go# Connection loop with backoff and reconnection |
| 43 | +├── server/ # Server package |
| 44 | +│ ├── server.go # Server struct, config, SSH key management, auth |
| 45 | +│ ├── server_handler.go# HTTP/WebSocket handler, SSH handshake, tunnel setup |
| 46 | +│ └── server_listen.go # TCP listener with TLS (manual certs or LetsEncrypt) |
| 47 | +├── share/ # Shared library packages |
| 48 | +│ ├── version.go # Protocol version ("chisel-v3") and build version |
| 49 | +│ ├── compat.go # Backwards-compatible type aliases |
| 50 | +│ ├── ccrypto/ # Key generation, fingerprinting (ECDSA/SHA256) |
| 51 | +│ ├── cio/ # Bidirectional pipe, logger, stdio |
| 52 | +│ ├── cnet/ # WebSocket-to-net.Conn adapter, HTTP server with graceful shutdown |
| 53 | +│ ├── cos/ # OS signals (SIGUSR2 stats, SIGHUP reconnect), context helpers |
| 54 | +│ ├── settings/ # Config encoding, remote parsing, user/auth management, env helpers |
| 55 | +│ └── tunnel/ # Core tunnel engine — proxy, SSH channel handling, keepalive, UDP |
| 56 | +├── test/ |
| 57 | +│ ├── e2e/ # End-to-end tests (auth, TLS, SOCKS, UDP, proxy) |
| 58 | +│ └── bench/ # Benchmarking tool |
| 59 | +├── Dockerfile # Alpine-based container image |
| 60 | +├── goreleaser.yml # GoReleaser config — builds Linux binary, pushes to GHCR |
| 61 | +└── Makefile # Build targets for cross-compilation, lint, test, release |
| 62 | +``` |
| 63 | + |
| 64 | +## Component Responsibilities |
| 65 | + |
| 66 | +### `main.go` -- CLI |
| 67 | + |
| 68 | +Parses the top-level command (`server` or `client`), then delegates to dedicated flag parsers. Handles environment variable fallbacks for host, port, auth, and key configuration. |
| 69 | + |
| 70 | +### `client/` -- Tunnel Client |
| 71 | + |
| 72 | +- Establishes a WebSocket connection to the server (optionally through an HTTP CONNECT or SOCKS5 proxy). |
| 73 | +- Upgrades the WebSocket to an SSH connection and performs host key fingerprint verification. |
| 74 | +- Sends its tunnel configuration (list of remotes) to the server for validation. |
| 75 | +- Runs a **connection loop** with exponential backoff for automatic reconnection. |
| 76 | +- Creates local **Proxy** listeners for forward tunnels. For reverse tunnels, the server side creates the listeners. |
| 77 | + |
| 78 | +### `server/` -- Tunnel Server |
| 79 | + |
| 80 | +- Listens on HTTP(S) with optional TLS (manual key/cert or automatic LetsEncrypt via `autocert`). |
| 81 | +- Upgrades incoming WebSocket connections (matching the `chisel-v3` protocol) to SSH server sessions. |
| 82 | +- Authenticates users via SSH password auth, checked against an auth file (`users.json`) that hot-reloads on changes via `fsnotify`. |
| 83 | +- Validates client-requested remotes against user permissions (regex-based address matching). |
| 84 | +- Falls back to a reverse proxy for non-WebSocket HTTP requests, or serves `/health` and `/version` endpoints. |
| 85 | + |
| 86 | +### `share/tunnel/` -- Tunnel Engine |
| 87 | + |
| 88 | +The core data-plane, shared by both client and server: |
| 89 | + |
| 90 | +- **`Tunnel`** -- Binds an SSH connection and manages its lifecycle (keepalive pings, channel handling). |
| 91 | +- **`Proxy`** -- Listens on a local address (TCP, UDP, or stdio) and pipes traffic through SSH channels. |
| 92 | +- **Outbound handler** -- Receives SSH channel open requests and connects to the target host (TCP dial, UDP dial, or SOCKS5 proxy). |
| 93 | +- **UDP multiplexing** -- Encodes/decodes UDP packets with source address metadata over a single SSH channel using `gob` encoding. |
| 94 | + |
| 95 | +### `share/settings/` -- Configuration |
| 96 | + |
| 97 | +- **`Remote`** -- Parses the `local:remote` format (e.g., `3000:google.com:80`, `R:2222:localhost:22`, `socks`, `stdio:host:port`). |
| 98 | +- **`Config`** -- JSON-serialized handshake payload exchanged between client and server over SSH. |
| 99 | +- **`UserIndex`** -- Loads and watches the auth file, manages user permissions with regex-based address ACLs. |
| 100 | +- **`Env`** -- Reads `CHISEL_*` environment variables for tuning (timeouts, buffer sizes, UDP settings). |
| 101 | + |
| 102 | +### `share/ccrypto/` -- Cryptography |
| 103 | + |
| 104 | +Generates ECDSA P256 keys (deterministic from seed or random), converts between PEM and chisel key formats, and computes SHA256 fingerprints for host key verification. |
| 105 | + |
| 106 | +### `share/cnet/` -- Network Adapters |
| 107 | + |
| 108 | +- **`wsConn`** -- Wraps `gorilla/websocket.Conn` as a standard `net.Conn` with buffered reads. |
| 109 | +- **`HTTPServer`** -- Extends `net/http.Server` with context-aware graceful shutdown. |
| 110 | + |
| 111 | +## Connection Lifecycle |
| 112 | + |
| 113 | +```mermaid |
| 114 | +sequenceDiagram |
| 115 | + participant C as Client |
| 116 | + participant WS as WebSocket |
| 117 | + participant S as Server |
| 118 | + participant T as Target |
| 119 | +
|
| 120 | + C->>S: HTTP Upgrade (Sec-WebSocket-Protocol: chisel-v3) |
| 121 | + S->>C: 101 Switching Protocols |
| 122 | +
|
| 123 | + Note over C,S: SSH handshake over WebSocket |
| 124 | + C->>S: SSH ClientConn (with user/pass if auth enabled) |
| 125 | + S->>C: SSH ServerConn (host key for fingerprint verification) |
| 126 | +
|
| 127 | + C->>S: SSH Request "config" (JSON: version + remotes) |
| 128 | + S->>S: Validate remotes against user permissions |
| 129 | + S->>C: Reply (ok / denied) |
| 130 | +
|
| 131 | + Note over C,S: Tunnel active — keepalive pings every 25s |
| 132 | +
|
| 133 | + rect rgb(230, 240, 255) |
| 134 | + Note over C,T: Forward tunnel data flow |
| 135 | + C->>S: SSH Channel Open "chisel" (target host:port) |
| 136 | + S->>T: TCP/UDP Dial |
| 137 | + C-->>T: Bidirectional data pipe |
| 138 | + end |
| 139 | +
|
| 140 | + rect rgb(255, 240, 230) |
| 141 | + Note over C,T: Reverse tunnel data flow |
| 142 | + S->>S: Listen on server-side port |
| 143 | + S->>C: SSH Channel Open "chisel" (target host:port) |
| 144 | + C->>T: TCP/UDP Dial |
| 145 | + T-->>S: Bidirectional data pipe |
| 146 | + end |
| 147 | +``` |
| 148 | + |
| 149 | +## Protocol Layers |
| 150 | + |
| 151 | +```mermaid |
| 152 | +graph TB |
| 153 | + subgraph "Protocol Stack" |
| 154 | + L1["Application Data (TCP/UDP payload)"] |
| 155 | + L2["SSH Channel (encrypted, multiplexed)"] |
| 156 | + L3["SSH Connection (auth, keepalive)"] |
| 157 | + L4["WebSocket (framing)"] |
| 158 | + L5["HTTP/S (transport, TLS optional)"] |
| 159 | + L6["TCP"] |
| 160 | + end |
| 161 | +
|
| 162 | + L1 --> L2 --> L3 --> L4 --> L5 --> L6 |
| 163 | +``` |
| 164 | + |
| 165 | +This layering means chisel traffic looks like regular HTTP/WebSocket traffic to firewalls and proxies, while the SSH layer provides encryption and authentication. |
| 166 | + |
| 167 | +## External Dependencies |
| 168 | + |
| 169 | +| Dependency | Purpose | |
| 170 | +|---|---| |
| 171 | +| `gorilla/websocket` | WebSocket transport layer | |
| 172 | +| `golang.org/x/crypto/ssh` | SSH protocol implementation (encryption, auth, channels) | |
| 173 | +| `golang.org/x/crypto/acme/autocert` | Automatic TLS certificates via LetsEncrypt | |
| 174 | +| `armon/go-socks5` | Built-in SOCKS5 proxy server | |
| 175 | +| `jpillora/backoff` | Exponential backoff for client reconnection | |
| 176 | +| `fsnotify/fsnotify` | Hot-reload of the users auth file | |
| 177 | +| `golang.org/x/net/proxy` | SOCKS5 outbound proxy dialer (client side) | |
| 178 | +| `golang.org/x/sync/errgroup` | Concurrent goroutine lifecycle management | |
| 179 | + |
| 180 | +## Key Design Decisions |
| 181 | + |
| 182 | +1. **SSH over WebSocket over HTTP.** This combination lets chisel traverse corporate proxies and firewalls that only allow HTTP traffic, while still providing authenticated, encrypted tunnels. |
| 183 | + |
| 184 | +2. **Single binary, dual mode.** The same executable runs as either client or server. The `share/` packages contain all the tunnel logic used by both sides. |
| 185 | + |
| 186 | +3. **Multiplexed SSH channels.** Multiple tunnel remotes share a single WebSocket/SSH connection. Each remote gets its own SSH channel, avoiding the overhead of multiple TCP connections. |
| 187 | + |
| 188 | +4. **UDP over SSH.** UDP packets are serialized with `gob` encoding (including source address metadata) and multiplexed through SSH channels, enabling UDP tunneling over the inherently stream-oriented SSH protocol. |
| 189 | + |
| 190 | +5. **Automatic reconnection.** The client uses exponential backoff with a configurable max retry count and interval. SIGHUP can short-circuit the backoff timer for immediate reconnection. |
| 191 | + |
| 192 | +6. **Hot-reloadable auth.** The server watches its `users.json` file for changes and reloads permissions without restart, using `fsnotify`. |
| 193 | + |
| 194 | +7. **Graceful context propagation.** Both client and server use `context.Context` throughout, enabling clean shutdown on OS signals (SIGINT/SIGTERM) via `cos.InterruptContext()`. |
| 195 | + |
| 196 | +## Build and Release |
| 197 | + |
| 198 | +The project uses **GoReleaser** to build Linux binaries and push Docker images to `ghcr.io/outsystems/chisel`. The build version is injected via `-ldflags` at compile time into `share.BuildVersion`. The Makefile provides cross-compilation targets for FreeBSD, Linux, Windows, and macOS. |
0 commit comments