Open-source SSH proxy with identity-aware shell (Rust)
Find a file
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
config feat: Entra Agent ID auth provider + governance leak cleanup 2026-04-04 22:35:32 -04:00
crates feat: structured logging, tracing spans, comprehensive documentation 2026-04-04 23:45:03 -04:00
docs feat: structured logging, tracing spans, comprehensive documentation 2026-04-04 23:45:03 -04:00
images feat: structured logging, tracing spans, comprehensive documentation 2026-04-04 23:45:03 -04:00
.gitignore feat: Bascule — identity-aware SSH proxy 2026-04-04 22:25:33 -04:00
Cargo.lock feat: structured logging, tracing spans, comprehensive documentation 2026-04-04 23:45:03 -04:00
Cargo.toml feat: Entra Agent ID auth provider + governance leak cleanup 2026-04-04 22:35:32 -04:00
README.md feat: structured logging, tracing spans, comprehensive documentation 2026-04-04 23:45:03 -04:00

Bascule

Identity-aware SSH proxy for modern infrastructure.

Bascule is a lightweight SSH proxy that authenticates users via SSH keys, OIDC, or AI agent tokens, then connects them to a local shell, remote host, or ephemeral container. No agents. No control plane. One binary.

Quick Start

cargo build --release -p bascule-server
./target/release/bascule --config config/bascule.example.toml
# In another terminal:
ssh -p 2222 localhost

See docs/quickstart.md for Docker and container mode.

Session Modes

Mode Config Use case
Local PTY (default) Spawn a local shell
Remote proxy [proxy] Forward to a remote SSH host
Container [container] Ephemeral container per session

Container mode

Each SSH session spawns a fresh container. The image defines the toolset — if it's not in the image, the operator can't use it.

[container]
image = "bascule-shell:k8s-ops"
ephemeral = true
hardened = true
memory_limit = "512m"

Features

  • Three backends — local PTY, remote SSH proxy, ephemeral containers
  • Identity-aware sessions — every connection authenticated and attributed
  • SSH key authentication — standard authorized_keys, no surprises
  • AI agent authentication — native Microsoft Entra Agent ID support
  • Right-sized images — curated container images (minimal, k8s-ops, net-ops, dev)
  • SessionHandler trait — extend with custom policy, audit, or recording
  • Structured logging — JSON format for production observability
  • Small footprint — single binary, ~7MB, <64MB memory

Comparison

Bascule Teleport Boundary
Agents None Required Required
Control plane None Required Required
License Apache 2.0 AGPL MPL
Container sessions Native No No
AI Agent Identity Native No No
Binary size ~7MB ~150MB ~100MB

See docs/comparison.md for the full comparison.

Extending Bascule

Implement the SessionHandler trait to add custom behavior:

use bascule_core::hooks::{SessionHandler, SessionInfo};

struct MyAuditHandler;

#[async_trait]
impl SessionHandler for MyAuditHandler {
    async fn on_session_start(&self, session: &SessionInfo) -> anyhow::Result<()> {
        log::info!("{} connected from {}", session.principal, session.source_ip);
        Ok(())
    }

    async fn on_exec(&self, session: &SessionInfo, command: &str) -> anyhow::Result<()> {
        log::info!("{} executed: {}", session.principal, command);
        Ok(())
    }
}

Projects like Guildhouse use the SessionHandler trait to add authorization contexts, completion receipts, and merkle-anchored audit trails.

Documentation

License

Apache 2.0