This commit is contained in:
2025-12-10 23:27:05 -03:00
commit 8c8211b60d

306
README.md Normal file
View File

@@ -0,0 +1,306 @@
# 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.
---