Skip to content

Commit dffb322

Browse files
committed
Add project documentation (CLAUDE.md, ARCHITECTURE.md, CONTRIBUTING.md)
Initialize comprehensive project documentation for the chisel fork: - ARCHITECTURE.md with system diagrams and design decisions - CONTRIBUTING.md with build, test, and PR guidelines - CLAUDE.md with AI agent guidance and quick-reference
1 parent 6d0ec74 commit dffb322

3 files changed

Lines changed: 417 additions & 0 deletions

File tree

ARCHITECTURE.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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.

CLAUDE.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# CLAUDE.md
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. This is an OutSystems fork of [jpillora/chisel](https://github.com/jpillora/chisel).
4+
5+
## Key Commands
6+
7+
```sh
8+
make all # Build for current platform (uses goreleaser)
9+
make test # Unit tests with race detection and coverage (excludes test/)
10+
make lint # go fmt + go vet (excludes test/)
11+
go test ./... # All tests including e2e
12+
go test ./test/e2e/ # Only e2e tests
13+
make release # Lint + test + goreleaser release
14+
```
15+
16+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for full build, test, and release details.
17+
18+
## Directory Overview
19+
20+
| Directory | Contents |
21+
|---|---|
22+
| `main.go` | CLI entrypoint -- parses `server`/`client` subcommand, delegates |
23+
| `client/` | Client: WebSocket connection, SSH setup, reconnection with backoff |
24+
| `server/` | Server: HTTP/TLS listener, WebSocket upgrade, SSH auth, user permissions |
25+
| `share/` | Shared libraries used by both client and server |
26+
| `share/tunnel/` | Core tunnel engine -- proxy, SSH channels, keepalive, UDP mux |
27+
| `share/settings/` | Config parsing, remote format, user/auth, `CHISEL_*` env vars |
28+
| `share/ccrypto/` | ECDSA key generation, SSH fingerprinting |
29+
| `share/cnet/` | WebSocket-to-net.Conn adapter, HTTP server with graceful shutdown |
30+
| `share/cio/` | Bidirectional pipe, logging, stdio |
31+
| `share/cos/` | OS signals (SIGUSR2 stats, SIGHUP reconnect), context helpers |
32+
| `test/e2e/` | End-to-end tests (auth, TLS, SOCKS, UDP, proxy) |
33+
| `test/bench/` | Benchmarking tool |
34+
| `example/` | Example configs (users.json, Fly.io deployment) |
35+
36+
See [ARCHITECTURE.md](./ARCHITECTURE.md) for component responsibilities and data flow diagrams.
37+
38+
## Important Patterns
39+
40+
### Module path and import aliases
41+
42+
The Go module path is `github.com/jpillora/chisel` (upstream path, kept for compatibility). Standard import aliases used throughout:
43+
44+
```go
45+
chclient "github.com/jpillora/chisel/client"
46+
chserver "github.com/jpillora/chisel/server"
47+
chshare "github.com/jpillora/chisel/share"
48+
```
49+
50+
### Backwards compatibility layer
51+
52+
`share/compat.go` re-exports types and functions from sub-packages under the `chshare` package. This exists for backwards compatibility with code that imports `chshare` directly. When adding new public APIs to `share/` sub-packages, do NOT add new aliases to `compat.go` unless maintaining an existing external API contract.
53+
54+
### Build version injection
55+
56+
`share.BuildVersion` is set at compile time via `-ldflags`. Default is `"0.0.0-src"`. The protocol version (`"chisel-v3"`) is in `share/version.go` -- changing it breaks client/server compatibility.
57+
58+
### Environment variables
59+
60+
All chisel-specific env vars use the `CHISEL_` prefix, accessed via `settings.Env("SUFFIX")` (which reads `CHISEL_SUFFIX`). Helper functions `EnvInt`, `EnvDuration`, `EnvBool` are in `share/settings/env.go`.
61+
62+
### Test structure
63+
64+
- Unit tests live alongside source files (e.g., `client/client_test.go`)
65+
- E2e tests are in `test/e2e/` using `package e2e_test`
66+
- E2e tests share a `testLayout` setup helper defined in `test/e2e/setup_test.go`
67+
- `make test` excludes the `test/` directory; use `go test ./...` to include e2e
68+
69+
### CGO and cross-compilation
70+
71+
Most build targets use `CGO_ENABLED=0` except Linux and Windows which use `CGO_ENABLED=1`. All builds use `-trimpath` for reproducibility.
72+
73+
### Context and shutdown
74+
75+
Both client and server use `cos.InterruptContext()` for graceful shutdown on SIGINT/SIGTERM. Always propagate `context.Context` through new code paths.
76+
77+
### go.mod replace directive
78+
79+
`go.mod` contains `replace github.com/jpillora/chisel => ../chisel`. This is a local development override -- do not remove it, but be aware it means `go mod tidy` expects a sibling directory.
80+
81+
## Domain Glossary
82+
83+
| Term | Meaning |
84+
|---|---|
85+
| **Remote** | A tunnel endpoint specification in the format `local:remote` (e.g., `3000:google.com:80`). Parsed by `settings.DecodeRemote`. |
86+
| **Forward tunnel** | Client listens locally, traffic flows through server to the target. |
87+
| **Reverse tunnel** | Server listens, traffic flows back through client. Remotes prefixed with `R:`. |
88+
| **Fingerprint** | SHA256 hash of the server's ECDSA public key, base64-encoded (44 chars). Used for host-key verification. |
89+
| **SOCKS remote** | A special remote using `socks` as the target, routing through the built-in SOCKS5 proxy. |
90+
| **stdio remote** | A special remote using `stdio` as the local host, connecting stdin/stdout to the tunnel (useful with SSH ProxyCommand). |
91+
| **Config handshake** | JSON payload (`settings.Config`) sent from client to server over SSH after connection, containing version and requested remotes. |
92+
| **UserIndex** | Server-side auth structure loaded from `users.json`. Maps `user:pass` to regex-based address ACLs. Hot-reloads via `fsnotify`. |
93+
| **Keepalive** | SSH-level ping sent every 25s (default) to prevent proxy idle timeouts. |
94+
| **Proxy (tunnel)** | A `tunnel.Proxy` instance that listens on a local address and pipes traffic through an SSH channel. |

0 commit comments

Comments
 (0)