Files
Docssh4/README.md
2025-12-10 23:27:05 -03:00

307 lines
8.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SSH Tunnel Server & Admin API
This project is a single Go binary that:
- Runs an SSH tunnel server (password or public-key auth)
- Tracks per-user limits, expiry and active connections
- Persists users in PostgreSQL
- Exposes a secure HTTP admin API
- Serves a modern web panel (SPA) that calls the admin API
The admin API is protected by a static bearer token (`ADMIN_TOKEN`) and is intended to be used only by the bundled web panel or trusted automation.
---
## Process overview
1. **SSH server**
- Listens on `config.json``listen` (default `:2222`)
- Supports:
- password auth (per-user)
- optional per-user public key auth
- Enforces:
- per-user `max_connections`
- per-connection bandwidth limits (up / down)
- optional expiry (`expires_at`)
2. **Admin HTTP server**
- Listens on `ADMIN_HTTP_ADDR` (default `127.0.0.1:8080`)
- Serves:
- static panel (from `./admin/index.html`)
- JSON API under `/api/*`
- Requires `Authorization: Bearer <ADMIN_TOKEN>` on all `/api/*` endpoints.
3. **PostgreSQL**
- Connection string from `PG_DSN`
- Users are stored in table `ssh_users`
- On startup:
- If `PG_DSN` is set, users are loaded from DB and override `config.json` users.
- On any create/update/delete via API:
- DB is updated
- In-memory users are reloaded
- All active connections for that user are disconnected so new config applies immediately.
---
## Environment variables
| Variable | Required | Default | Description |
|-------------------|----------|------------------|-----------------------------------------------------------------------------|
| `PG_DSN` | Optional | | PostgreSQL DSN. If empty, runs with config-only users (no DB persistence). |
| `ADMIN_TOKEN` | Required | | Static bearer token for admin API & panel login. |
| `ADMIN_HTTP_ADDR` | Optional | `127.0.0.1:8080` | Address for the admin HTTP server (panel + API). |
Example:
```bash
export PG_DSN='postgres://sshpanel:password@localhost:5432/sshpanel?sslmode=disable'
export ADMIN_TOKEN='super-long-random-token'
export ADMIN_HTTP_ADDR='0.0.0.0:8080'
./sshpanel -config config.json
```
---
## Data model (simplified)
The admin API works on a user model with these key fields:
```jsonc
{
"username": "test",
"password": "secret", // write-only; never returned by API
"max_connections": 1, // 0 = unlimited
"expires_at": "2025-12-12T22:37:00Z", // RFC3339 or empty string for "never"
"limit_mbps_up": 10, // per-connection upstream limit (Mbps)
"limit_mbps_down": 10 // per-connection downstream limit (Mbps)
}
```
Additional runtime field:
- `active_conns` current number of live SSH connections for that user (read-only, calculated in memory).
---
## Authentication
All admin API endpoints require:
```http
Authorization: Bearer <ADMIN_TOKEN>
```
If the header is missing or incorrect:
- `401 Unauthorized` if token doesnt match
- `403 Forbidden` if `ADMIN_TOKEN` is not configured on the server
The static panel (`/`) does **not** expose data until a token is provided in the login screen; it then stores it in `localStorage` and sends it as `Authorization: Bearer ...` on each request.
---
## API routes
Base URL: `http://<ADMIN_HTTP_ADDR>`
### 1. `GET /api/users`
List all users with current status.
**Auth:** required (Bearer token)
**Request:** no body
**Response:**
- `200 OK` with JSON array of users
- `401/403` on auth failure
Schema:
```jsonc
[
{
"username": "test",
"active_conns": 1,
"max_connections": 1,
"expires_at": "2025-12-12T22:37:00Z", // or null if no expiry
"limit_mbps_up": 10,
"limit_mbps_down": 10
}
]
```
**Example (curl):**
```bash
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://127.0.0.1:8080/api/users
```
---
### 2. `POST /api/users/create`
Create a new user or update an existing user (upsert).
**Auth:** required
**Request body:** JSON, fields:
```jsonc
{
"username": "test", // required
"password": "secret", // required for new user; optional on update
"max_connections": 1, // required; 0 = unlimited
"expires_at": "2025-12-12T22:37:00Z", // optional; "" = no expiry
"limit_mbps_up": 10, // required; 0 = use default or unlimited
"limit_mbps_down": 10 // required; 0 = use default or unlimited
}
```
Behavior:
- If `username` **does not exist** in DB:
- `password` **must** be non-empty.
- Creates a new user row in `ssh_users`.
- If `username` **already exists**:
- If `password` is **non-empty**, it replaces the old password.
- If `password` is **missing or empty**, the existing password is kept.
- Other fields are updated.
Side effect:
- After a successful upsert, **all active SSH connections for that user are closed**.
New connections must use the updated credentials/limits.
**Responses:**
- `201 Created` on success (empty body)
- `400 Bad Request` on invalid body or missing required fields (`username`, or `password` when creating a new user)
- `401/403` on auth failure
- `500 Internal Server Error` on DB issues
**Example create a new user:**
```bash
curl -X POST http://127.0.0.1:8080/api/users/create \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "alice",
"password": "alicepass",
"max_connections": 2,
"expires_at": "2025-12-31T23:59:00Z",
"limit_mbps_up": 10,
"limit_mbps_down": 10
}'
```
**Example update only limits (keep password):**
```bash
curl -X POST http://127.0.0.1:8080/api/users/create \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "alice",
"max_connections": 3,
"expires_at": "2025-12-31T23:59:00Z",
"limit_mbps_up": 20,
"limit_mbps_down": 20
}'
# password omitted => password is kept as-is
# any active sessions for alice are disconnected
```
**Example change password and limits:**
```bash
curl -X POST http://127.0.0.1:8080/api/users/create \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "alice",
"password": "newpass",
"max_connections": 1,
"expires_at": "",
"limit_mbps_up": 5,
"limit_mbps_down": 5
}'
```
---
### 3. `DELETE /api/users/delete`
Delete a user and disconnect all their active sessions.
**Auth:** required
**Request:**
- Method: `DELETE`
- Query parameter: `username` (required)
Example URL:
```text
/api/users/delete?username=alice
```
Behavior:
- User row is deleted from `ssh_users`.
- In-memory state is reloaded.
- All active SSH connections for that user are closed.
**Responses:**
- `204 No Content` on success
- `400 Bad Request` if `username` is missing
- `401/403` on auth failure
- `500 Internal Server Error` on DB issues
**Example (curl):**
```bash
curl -X DELETE "http://127.0.0.1:8080/api/users/delete?username=alice" \
-H "Authorization: Bearer $ADMIN_TOKEN"
```
---
### 4. `GET /` (static panel)
Serves the SPA admin panel (`admin/index.html`).
- No auth at HTTP level, but:
- The panel shows a **login screen** asking for `ADMIN_TOKEN`.
- The token is stored in `localStorage` and used as `Authorization: Bearer ...` for all `/api/*` calls.
- If the token is wrong, the UI shows an error and stays on the login view.
You typically access it in a browser at:
```text
http://127.0.0.1:8080/
```
(or whatever you put in `ADMIN_HTTP_ADDR`).
---
## Notes and recommendations
- Expose `ADMIN_HTTP_ADDR` publicly **only** if you are confident the `ADMIN_TOKEN` has sufficient entropy (long, random) and you trust the network.
- For extra safety:
- Bind `ADMIN_HTTP_ADDR` to `127.0.0.1:8080` and access through an SSH tunnel or reverse proxy with TLS.
- Rotate `ADMIN_TOKEN` regularly; restart the binary after changing it.
- Any change (create, update, delete) to a user via the API immediately disconnects their current SSH sessions, so clients must reconnect.
---