Merge branch 'feature/remote-access'

This commit is contained in:
Jason Staack
2026-03-12 19:04:14 -05:00
6 changed files with 28 additions and 8 deletions

View File

@@ -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

View File

@@ -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}",
)

View File

@@ -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

View File

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

View File

@@ -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
}

View File

@@ -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()