Updated 9 files to reflect: Management API (axum, port 9090) — embedded in bascule-server Dioxus dashboard components (WASM web target) 6 crates in workspace (was 4) README.md: Added Management API + Dashboard features section Added dashboard row to comparison table docs/architecture.md: Updated diagram showing dual-listener architecture Added Management API section explaining Arc<SessionStore> sharing Updated crate table (6 crates) docs/configuration.md: Added [dashboard] config section reference docs/observability.md: Added Management API monitoring section with curl examples docs/quickstart.md: Added Management API quick start section docs/comparison.md: Added dashboard and TPM attestation rows CLAUDE.md + CONTRIBUTING.md: Updated crate lists and feature flags config/bascule.example.toml: Added [dashboard] section All 17 README links verified valid. Build clean. Signed-off-by: Tyler King <tking@guildhouse.dev>
107 lines
4.7 KiB
Markdown
107 lines
4.7 KiB
Markdown
# Architecture
|
|
|
|
## Overview
|
|
|
|
Bascule is a single-binary SSH proxy with a built-in management API:
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
Operator workstation │ bascule (single binary) │
|
|
┌───────────────┐ │ │
|
|
│ bascule-shell │ SSH │ Port 2222: SSH Proxy (russh) │
|
|
│ identity + │─────▶│ ├── AuthProvider │
|
|
│ TPM attest │ │ ├── SessionHandler hooks │
|
|
└───────────────┘ │ └── SessionBackend │
|
|
│ ├── Local PTY │
|
|
Browser / curl │ ├── Remote Proxy │
|
|
┌───────────────┐ HTTP │ └── Container │
|
|
│ Dashboard │─────▶│ │
|
|
│ /api/* │ │ Port 9090: Management API (axum) │
|
|
└───────────────┘ │ ├── /api/sessions │
|
|
│ ├── /api/stats │
|
|
│ ├── /api/health │
|
|
│ └── /dashboard (WASM, planned) │
|
|
│ │
|
|
│ Arc<SessionStore> shared │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
## Session Backends
|
|
|
|
### Local PTY (default)
|
|
|
|
Spawns a process on this machine via `portable-pty`. The process gets a real PTY with terminal emulation, resize support, and environment variables.
|
|
|
|
### Remote Proxy
|
|
|
|
Opens a second SSH connection to a target host via `russh` client. Bridges I/O between the client and upstream channels. The client sees a transparent connection to the target host, but Bascule mediates authentication and applies SessionHandler hooks.
|
|
|
|
### Container
|
|
|
|
Spawns an ephemeral container per session using docker, podman, or nerdctl. The container is destroyed on disconnect. The container image defines what tools are available — if it's not in the image, the operator can't use it.
|
|
|
|
## Extension Model
|
|
|
|
### AuthProvider trait
|
|
|
|
Implement to add custom authentication:
|
|
|
|
```rust
|
|
#[async_trait]
|
|
pub trait AuthProvider: Send + Sync + 'static {
|
|
async fn check_public_key(&self, user: &str, key: &PublicKey) -> bool;
|
|
async fn check_password(&self, user: &str, password: &str) -> bool;
|
|
fn principal_for_user(&self, user: &str) -> String;
|
|
}
|
|
```
|
|
|
|
### SessionHandler trait
|
|
|
|
Implement to add policy, audit, or custom behavior:
|
|
|
|
```rust
|
|
#[async_trait]
|
|
pub trait SessionHandler: Send + Sync + 'static {
|
|
async fn on_session_start(&self, session: &SessionInfo) -> anyhow::Result<()>;
|
|
async fn build_session_env(&self, session: &SessionInfo) -> HashMap<String, String>;
|
|
async fn on_exec(&self, session: &SessionInfo, command: &str) -> anyhow::Result<()>;
|
|
async fn on_session_end(&self, session: &SessionInfo) -> anyhow::Result<()>;
|
|
fn display_name(&self, session: &SessionInfo) -> String;
|
|
}
|
|
```
|
|
|
|
All methods have default implementations (accept/passthrough). Override only what you need.
|
|
|
|
## Security Model
|
|
|
|
### Container hardening (default)
|
|
|
|
When `hardened = true`:
|
|
- `--security-opt no-new-privileges`
|
|
- `--cap-drop ALL`
|
|
- `--cap-add SETUID --cap-add SETGID` (minimal for shell)
|
|
|
|
### Ephemeral sessions
|
|
|
|
When `ephemeral = true` (default), the container is `--rm` and destroyed on disconnect. Nothing persists between sessions.
|
|
|
|
### Network isolation
|
|
|
|
Set `network = "none"` to completely isolate the container from the network. The operator can run local tools but can't reach external services.
|
|
|
|
## Management API
|
|
|
|
The management API runs alongside the SSH proxy in the same process. Both share an `Arc<SessionStore>` — when a session starts via SSH, the store updates; when the dashboard polls `/api/sessions`, it reads the same store. Zero serialization, zero IPC.
|
|
|
|
Enable via `[dashboard]` config section or `--features dashboard` (default on).
|
|
|
|
## Crate Structure
|
|
|
|
| Crate | Purpose |
|
|
|-------|---------|
|
|
| `bascule-core` | Library — server, handler, auth, PTY, proxy, container, hooks, store |
|
|
| `bascule-server` | Binary — CLI, config, tracing, management API |
|
|
| `bascule-auth-agent-id` | Optional — Entra Agent ID authentication |
|
|
| `bascule-shell` | Binary — Identity-aware login shell with TPM |
|
|
| `bascule-dashboard` | Library — Dioxus UI components |
|
|
| `bascule-dashboard-web` | Binary — WASM web dashboard target |
|