bascule-oss/README.md
Tyler King 043b9b9bdc feat: bascule-shell — identity-aware shell with TPM attestation
New crate: bascule-shell (471 lines, 1.8MB binary)
  Login shell that detects identity + platform attestation at startup.
  Wraps bash/zsh/fish — operator works normally, identity travels with them.

Identity detection (priority order):
  1. Entra via WSL2 interop
  2. Azure CLI
  3. Kerberos TGT
  4. Cached OIDC token
  5. System user (fallback)

Platform attestation:
  TPM 2.0 PCR values via tpm2_pcrread (PCRs 0,1,2,7,10,14)
  IMA measurement log hash + count
  Keylime agent state
  Entra device compliance (WSL2 only)
  Composite SHA-256 hash over all evidence

Shell features:
  Banner with identity + attestation summary
  BASCULE_* env vars injected into inner shell
  --info mode for dry-run display
  --json mode for machine-readable output
  --exec mode for single-command execution
  Configurable via ~/.config/bascule/shell.toml

Tested on Fedora with real TPM 2.0:
  6 PCRs successfully read from hardware
  All env vars propagated to inner shell
  1.8MB binary, 0 substrate deps

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:47:46 -04:00

3.4 KiB

Bascule

Identity-aware SSH proxy for modern infrastructure.

Bascule is a lightweight SSH proxy that authenticates users via SSH keys 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 file
  • AI agent authentication — Microsoft Entra Agent ID support (optional feature)
  • Session limiting — configurable max concurrent sessions
  • 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
Auth SSH keys, Entra Agent ID OIDC, SAML, GitHub OIDC, LDAP
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 custom authorization, audit logging, and session governance.

Documentation

Roadmap

  • OIDC authentication (Keycloak, Entra, Okta, Google)
  • Certificate-based authentication
  • OpenTelemetry OTLP trace export
  • Prometheus metrics endpoint
  • Session recording
  • Web UI for session management

License

Apache 2.0