Open-source SSH proxy with identity-aware shell (Rust)
Find a file
Claude Code 2520525ec6 feat(m5): bascule-shell prefers SPIFFE SVID URI as principal
Adds bascule_shell::identity::detect_spiffe_svid which reads a
SPIFFE SVID URI from /var/run/spire/svid-uri (override via
SPIFFE_SVID_PATH). When present it wins over Entra/AZ-CLI/
Kerberos/cached-OIDC/system-user, becoming the SAT session_leaf
actor field that QM's M5 SpiffeSvidEvaluator validates against
the cluster allowlist.

Why a file read instead of the SPIFFE Workload API: bascule-shell
ships independently from QM and the standard SPIRE k8s sidecar
writes the URI as /var/run/spire/svid-uri alongside svid.pem.
The file path is hermetic for tests and matches the deploy model.
If a future iteration needs continuous SVID URI rotation, switch
to a notify watcher or pull spiffe::workload_api.

Trust domain is parsed and surfaced as Identity.domain so the
banner / dashboard can show "spiffe://gh.dev" affiliation.

bascule_shell::main::set_env: auth_method == "spiffe" maps to
BASCULE_ROLES = "operator" by default. SPIRE-attested workloads
are explicitly cluster-issued so they get operator role until
per-workload provisioning lands. The existing precedence
(caller-set BASCULE_ROLES wins) is unchanged.

Bascule mTLS *channel* construction (Bascule -> QM gRPC
renegotiation) is intentionally NOT wired in this commit.
Per ADR D9 hot path is local; the renegotiation client is
deferred to M6 alongside the Rekor signing client because they
share the rustls dep tree.

Tested (Docker rust:1.88-bookworm):
  cargo build  -p bascule-shell -p bascule-core   clean
  cargo test   -p bascule-core --lib sat          7/7 (M1 regression)

Stacked on feat/m3-defcon-env.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Claude Code <claude@guildhouse.dev>
2026-04-07 21:05:05 -04:00
.github/workflows docs: DCO, NOTICE, and governance framework 2026-04-05 11:13:20 -04:00
charts/bascule docs: comprehensive documentation + developer experience polish 2026-04-05 10:53:08 -04:00
config docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
crates feat(m5): bascule-shell prefers SPIFFE SVID URI as principal 2026-04-07 21:05:05 -04:00
docs docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
images feat: structured logging, tracing spans, comprehensive documentation 2026-04-04 23:45:03 -04:00
.editorconfig docs: comprehensive documentation + developer experience polish 2026-04-05 10:53:08 -04:00
.gitignore feat: Bascule — identity-aware SSH proxy 2026-04-04 22:25:33 -04:00
Cargo.lock feat(m1): bascule-shell composes a real SAT anchored on session_leaf 2026-04-07 14:38:20 -04:00
Cargo.toml feat(m1): bascule-shell composes a real SAT anchored on session_leaf 2026-04-07 14:38:20 -04:00
CLAUDE.md docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
CONTRIBUTING.md docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
DCO docs: DCO, NOTICE, and governance framework 2026-04-05 11:13:20 -04:00
docker-compose.yml feat: bascule-shell — identity-aware shell with TPM attestation 2026-04-05 09:47:46 -04:00
Dockerfile feat: bascule-shell — identity-aware shell with TPM attestation 2026-04-05 09:47:46 -04:00
GOVERNANCE.md docs: DCO, NOTICE, and governance framework 2026-04-05 11:13:20 -04:00
LICENSE feat: bascule-shell — identity-aware shell with TPM attestation 2026-04-05 09:47:46 -04:00
Makefile docs: comprehensive documentation + developer experience polish 2026-04-05 10:53:08 -04:00
NOTICE docs: DCO, NOTICE, and governance framework 2026-04-05 11:13:20 -04:00
README.md docs: update all documentation for management API + dashboard 2026-04-05 17:17:18 -04:00
rustfmt.toml docs: comprehensive documentation + developer experience polish 2026-04-05 10:53:08 -04:00

Bascule

Identity-aware SSH proxy for modern infrastructure.

Bascule authenticates operators via SSH keys or AI agent tokens, then connects them to a local shell, remote host, or ephemeral container. No agents to install. 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, Helm, and container mode.

Features

Session Backends

Mode Config Description
Local PTY (default) Spawn a local shell process
Remote Proxy [proxy] Forward to a remote SSH host
Container [container] Ephemeral container per session (Docker/Podman/nerdctl)
Kubernetes [k8s] Shared jumphost with shell sidecar (config ready, runtime coming)

Authentication

  • SSH Keys — standard OpenSSH authorized_keys files
  • Accept All — development only, accepts any key
  • Entra Agent ID — Microsoft AI agent identity (--features agent-id)
  • SPIFFE/SPIRE — workload identity (config ready, runtime coming)

Security

  • Session limiting (semaphore-based max_sessions)
  • Container hardening (--cap-drop ALL, --security-opt no-new-privileges)
  • Container config validation (injection prevention)
  • Read-only rootfs option
  • NetworkPolicy for Kubernetes deployments

Management API + Dashboard

Built-in HTTP management API (port 9090, --features dashboard):

  • GET /api/sessions — active sessions with auth/backend/TPM status
  • GET /api/stats — aggregate analytics (auth breakdown, peak concurrent, TPM %)
  • GET /api/health — server health and version
  • WASM dashboard at /dashboard/ (coming soon)

Observability

  • Structured JSON logging (BASCULE_LOG_FORMAT=json)
  • Tracing spans on auth, session lifecycle, exec requests
  • Management API for real-time session monitoring

Client: bascule-shell

Identity-aware login shell with TPM attestation:

./target/release/bascule-shell --info
╔═══════════════════════════════════════════════════════╗
║  Bascule Shell v0.1.0                                 ║
║  Principal:  tking                                     ║
║  Method:     ssh-key                                   ║
║  TPM:        available (6 PCRs verified)               ║
║  Platform:   sha256:e9b95f002f54222d...                ║
╚═══════════════════════════════════════════════════════╝

See docs/bascule-shell.md.

Comparison

Bascule Teleport Boundary
License Apache 2.0 AGPL / Commercial MPL / Commercial
Agents required No Yes Yes
Control plane No Required Required
Container sessions Yes No No
AI Agent Identity Yes (Entra Agent ID) No No
Binary size ~7MB ~150MB ~100MB
Built-in dashboard Yes (port 9090) Yes No

See docs/comparison.md.

Deployment

  • Standalone: cargo build --release -p bascule-server
  • Docker: docker build -t bascule .
  • Kubernetes: helm install bascule charts/bascule/ — see docs/kubernetes.md

Extending Bascule

Implement SessionHandler to add custom policy:

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

struct AuditHandler;

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

See docs/architecture.md.

Governance

Bascule is maintained by Guildhouse LLC. Contributions are accepted under the DCO — you retain copyright to your contributions.

The SessionHandler and AuthProvider traits are public APIs. Implementations are the intellectual property of their authors. See GOVERNANCE.md.

Roadmap

Not yet implemented:

  • OIDC authentication (Keycloak, Entra, Okta)
  • K8s API exec backend runtime
  • SPIFFE/SPIRE auth runtime
  • OpenTelemetry OTLP exporter
  • Prometheus metrics endpoint
  • Session recording
  • Per-session Pod isolation

Documentation

License

Apache 2.0