fix: WinBox tunnel bind address, port range, and proxy support
- Bind tunnel listeners to 0.0.0.0 instead of 127.0.0.1 so tunnels are reachable through reverse proxies and container networks - Reduce port range to 49000-49004 (5 concurrent tunnels) - Derive WinBox URI host from request Host header instead of hardcoding 127.0.0.1, enabling use behind reverse proxies - Add README security warning about default encryption keys Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
16
README.md
16
README.md
@@ -66,8 +66,22 @@ Web UI
|
||||
# Clone and configure
|
||||
git clone https://github.com/staack/the-other-dude.git && cd the-other-dude
|
||||
cp .env.example .env
|
||||
# Edit .env -- set CREDENTIAL_ENCRYPTION_KEY and JWT_SECRET_KEY at minimum
|
||||
```
|
||||
|
||||
**Edit `.env` before starting** -- at minimum, generate unique values for:
|
||||
|
||||
```bash
|
||||
# Generate a JWT signing key
|
||||
JWT_SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(64))")
|
||||
|
||||
# Generate a Fernet encryption key (used to encrypt device credentials at rest)
|
||||
CREDENTIAL_ENCRYPTION_KEY=$(python3 -c "import secrets, base64; print(base64.b64encode(secrets.token_bytes(32)).decode())")
|
||||
```
|
||||
|
||||
> **Warning**
|
||||
> The `.env.example` ships with **hard-coded dev defaults** for both keys. These are fine for local development but **must be replaced before exposing the instance to any network**. Anyone with the default `JWT_SECRET_KEY` can forge authentication tokens, and the default `CREDENTIAL_ENCRYPTION_KEY` leaves all stored device credentials readable.
|
||||
|
||||
```bash
|
||||
# Build images sequentially (avoids OOM on low-RAM machines)
|
||||
docker compose --profile full build api
|
||||
docker compose --profile full build poller
|
||||
|
||||
@@ -159,11 +159,17 @@ async def open_winbox_session(
|
||||
if not isinstance(port, int) or not (49000 <= port <= 49100):
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Invalid port allocation from tunnel service")
|
||||
|
||||
# Derive the tunnel host from the request so remote clients get the server's
|
||||
# address rather than 127.0.0.1 (which would point to the user's own machine).
|
||||
tunnel_host = (request.headers.get("x-forwarded-host") or request.headers.get("host") or "127.0.0.1")
|
||||
# Strip port from host header (e.g. "10.101.0.175:8001" → "10.101.0.175")
|
||||
tunnel_host = tunnel_host.split(":")[0]
|
||||
|
||||
return WinboxSessionResponse(
|
||||
tunnel_id=tunnel_id,
|
||||
host="127.0.0.1",
|
||||
host=tunnel_host,
|
||||
port=port,
|
||||
winbox_uri=f"winbox://127.0.0.1:{port}",
|
||||
winbox_uri=f"winbox://{tunnel_host}:{port}",
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ services:
|
||||
POLL_INTERVAL_SECONDS: 60
|
||||
WIREGUARD_GATEWAY: wireguard
|
||||
TUNNEL_PORT_MIN: 49000
|
||||
TUNNEL_PORT_MAX: 49100
|
||||
TUNNEL_PORT_MAX: 49004
|
||||
TUNNEL_IDLE_TIMEOUT: 300
|
||||
SSH_RELAY_PORT: 8080
|
||||
SSH_IDLE_TIMEOUT: 900
|
||||
@@ -90,7 +90,7 @@ services:
|
||||
SSH_MAX_PER_USER: 10
|
||||
SSH_MAX_PER_DEVICE: 20
|
||||
ports:
|
||||
- "127.0.0.1:49000-49100:49000-49100"
|
||||
- "49000-49004:49000-49004"
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 8192
|
||||
|
||||
@@ -65,7 +65,7 @@ func (m *Manager) OpenTunnel(deviceID, tenantID, userID, remoteAddr string) (*Op
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
|
||||
if err != nil {
|
||||
m.portPool.Release(port)
|
||||
return nil, fmt.Errorf("failed to listen on port %d: %w", port, err)
|
||||
|
||||
@@ -54,7 +54,7 @@ func (pp *PortPool) Release(port int) {
|
||||
}
|
||||
|
||||
func canBind(port int) bool {
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
ln, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func TestPortPool_ConcurrentAccess(t *testing.T) {
|
||||
|
||||
func TestPortPool_BindVerification(t *testing.T) {
|
||||
// Occupy a port, then verify Allocate skips it
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:49050")
|
||||
ln, err := net.Listen("tcp", "0.0.0.0:49050")
|
||||
require.NoError(t, err)
|
||||
defer ln.Close()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user