bascule-oss/docs/architecture.md
Tyler King e7fc9fa5e1 feat: structured logging, tracing spans, comprehensive documentation
Observability:
  Structured JSON logging via BASCULE_LOG_FORMAT=json
  Tracing spans on auth (method, principal, peer)
  Tracing spans on session lifecycle (id, principal, backend, source_ip)
  Tracing spans on exec requests (session_id, command)
  Config: [telemetry] and [metrics] sections (OTel export planned)

Documentation (8 files, 489 lines):
  docs/quickstart.md — three-path getting started
  docs/configuration.md — full config reference with examples
  docs/authentication.md — all auth modes with setup guides
  docs/architecture.md — backends, traits, extension model, security
  docs/observability.md — logging, tracing, metrics
  docs/comparison.md — vs Teleport, Boundary, StrongDM
  images/README.md — curated image catalog
  README.md — features, comparison, quickstart, extension example

1557 lines Rust, 489 lines docs, 0 substrate deps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 23:45:03 -04:00

3.1 KiB

Architecture

Overview

Bascule is a single-binary SSH proxy with three pluggable layers:

Client SSH → Bascule Server
                │
                ├── AuthProvider (SSH keys / OIDC / Agent ID)
                │
                ├── SessionHandler hooks
                │     on_session_start → build_session_env → on_exec → on_session_end
                │
                └── SessionBackend
                      ├── LocalPty (portable-pty → /bin/bash)
                      ├── RemoteProxy (upstream SSH → target host)
                      └── Container (docker/podman → ephemeral image)

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:

#[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:

#[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.

Crate Structure

Crate Purpose
bascule-core Library — server, handler, auth, PTY, proxy, container, hooks
bascule-server Binary — CLI, config loading, tracing setup
bascule-auth-agent-id Optional — Entra Agent ID authentication