Skip to content

Repository files navigation

Vauban

Vauban

A fortified bastion for privileged access management, built in Rust.

Vauban is an open-source security bastion, developed in Rust, designed to protect and control access to critical infrastructure across enterprise, industrial, and defense environments. Its architecture leverages proven, cutting-edge technologies: privilege separation inspired by OpenSSH and Capsicum sandboxing, a confinement mechanism developed with funding from DARPA (U.S. Department of Defense). The solution includes multi-factor authentication (MFA), role-based access control (RBAC), full session recording, sealed SMTP notifications, and real-time monitoring of SSH, RDP, and IACS (industrial tunnel) connections. Free and sovereignty-friendly, Vauban meets the traceability and audit requirements of sensitive environments while offering an open-source alternative to proprietary solutions.

Project Structure

vauban-supervisor/    # Process orchestrator, watchdog, signal handling
vauban-web/           # HTTPS server, REST API, WebSocket handlers, frontend
vauban-web-evidence/  # Inspect Capture analyzer + recording integrity hydrator
vauban-auth/          # Authentication, MFA, SSO, LDAP integration
vauban-access/        # Access control, groups, and instance-level authorization (Casbin)
vauban-vault/         # Secrets management, encryption/decryption service
vauban-audit/         # Audit logging, session recording
vauban-mailer/        # Sealed SMTP outbox drainer (Capsicum leaf)
vauban-proxy-ssh/     # SSH protocol proxy (russh)
vauban-proxy-rdp/     # RDP protocol proxy (IronRDP, H.264 encoding)
vauban-proxy-iacs/    # Industrial Automation and Control Systems (IACS) protocols proxy
vauban-db/            # Shared Diesel schema, migrations, table relationships
shared/               # IPC protocol, message types, common utilities
config/               # TOML configuration files
docs/                 # Technical architecture documentation

Features

  • Secure Authentication: Session cookies (JWT) with mandatory MFA (TOTP) for the web UI; optional LDAPS directory bind; scoped API keys (vbn_…) for machine-to-machine /api/v1/* access
  • RBAC Integration: Role-based access control via IPC (Casbin), composed with role invariants and per-session access checks
  • Asset Management: SSH, RDP, and IACS assets with admin/user URL zones
  • Session Management: Track, record, and monitor proxy sessions (SSH asciicast, RDP fMP4, IACS PCAP bundles)
  • IACS Tunnel: EWS-facing SSH local-forward proxy with protocol-aware Inspect Capture for industrial recordings
  • Vault Secrets: Organizational secrets manager (web admin + M2M API with asset provenance)
  • Notifications: Sealed Capsicum mailer leaf drains the SMTP outbox via supervisor-brokered TCP
  • Post-Quantum Cryptography: Hybrid classical + PQ crypto in the SSH stack (russh / ml-kem)
  • Type Safety: Compile-time verified SQL queries (Diesel) and templates (Askama)

Technology Stack

  • Web Framework: Axum
  • Database: PostgreSQL with Diesel ORM
  • Cache: in-process no-op (no external cache server)
  • Templates: Askama (compile-time verified)
  • IPC: Unix pipes between services; SCM_RIGHTS FD brokering for TCP / recording / LDAPS / SMTP
  • Authentication: Session JWT (web), scoped API keys (M2M), Argon2id, TOTP, optional LDAPS

Security

This application follows strict security practices:

  • No unwrap() in production code paths
  • All user input validated with validator crate
  • Secrets managed with secrecy and zeroize
  • Post-quantum cryptography ready
  • Privilege separation via Unix pipes IPC
  • Comprehensive audit logging

Documentation

Detailed technical architecture documents are available in docs/technical/:

Document Description
Privilege Separation Architecture Process model, IPC protocol, Capsicum sandboxing, supervisor design
Vault Architecture Cryptographic design, key management, threat model, Vault Secrets (org secrets manager, M2M API, asset provenance)
RDP Session Architecture H.264 encoding, WebCodecs decoding, dynamic resolution, input pipeline
OpenH264 AVX2 Optimizations Custom AVX2 assembly for SAD and intra prediction (~50% CPU reduction)
ACME TLS Certificate Architecture Automatic certificate renewal, TLS-ALPN-01, zero-downtime rotation
Session Recording Architecture RDP segmented fMP4 + SSH asciicast v2 + IACS PCAP bundle (pcap-bundle) with synthetic L3/L4 (Wireshark-compatible); IACS gzip+BLAKE3 in audit; input redaction, DASH/asciinema playback, ZIP download
IAM Architecture Three-layer authorization (Casbin / PermissionContext, role invariants, session access), Argon2id auth service, JIT approval audit & separation of duties
LDAPS Auth Architecture Directory-backed login, LDAPS bind via vauban-auth, JIT provisioning, User Group aggregation (resolve / static / match mapping file), anti-downgrade
AccessGuard Architecture Shared shared::access_guard defense-in-depth RBAC re-check gate (fail-closed, 10s timeout, RAII pending-map)
IACS Proxy Architecture EWS-facing russh sshd, per-asset target resolution, Capsicum-aware FD passing (listener + Ed25519 host key), anti-SSRF supervisor broker, BLAKE3 session-token gate, boot Snapshot resync
IACS Inspect Capture Admin-only inline PCAP analyzer for IACS recordings: industrial-protocol-aware dissectors (Modbus/TCP, IEC-104, OPC-UA, PROFINET, passthrough), bounded TCP reassembly, tree<->hex bidirectional highlight, server-rendered HTMX + Tailwind, no inline JavaScript

Security Model

Vauban's security is built on defense in depth:

  1. Process isolation: Each service runs under a dedicated UID with no shared memory
  2. Capsicum confinement: After initialization, processes cannot open files, create sockets, or access the filesystem
  3. Credential isolation: Encryption keys are confined to vauban-vault; secrets are encrypted at rest in PostgreSQL and only decrypted transiently for session establishment, wrapped in zeroize-on-drop memory
  4. Network brokering: Sandboxed proxies cannot establish TCP connections directly; the supervisor brokers all outbound connections via SCM_RIGHTS file descriptor passing
  5. Memory safety: Rust's ownership model prevents buffer overflows, use-after-free, and data races
  6. Secret hygiene: Environment variables destroyed after reading, SensitiveString zeroized on drop

Building

Prerequisites

  • Rust 1.93+ (edition 2024)
  • just command runner
  • NASM (for OpenH264 assembly compilation)
  • PostgreSQL 18+
  • FreeBSD (for Capsicum sandbox; builds on macOS/Linux without sandboxing)

Build

# Full validation cycle (build + clippy + test)
just validate

# Build all crates (debug)
just build

# Build release binaries (optimized, LTO, stripped)
just release

# Run all tests
just test

# Run clippy lints
just clippy

Run

# Start all services via the supervisor
just run

The supervisor reads config/default.toml, forks child processes, sets up IPC pipes, drops privileges, and enters the watchdog loop. By default that is 7 always-on services (web, auth, access, vault, audit, proxy-ssh, proxy-rdp). Optionally +1 when [mailer].enabled, and +1 when [industrial].enabled (IACS proxy) -- up to 9 children.

Configuration

VAUBAN uses two distinct configuration strategies depending on how the binary is compiled. The build profile (--release or not) determines the behavior at compile time -- there is no runtime switch.

Build Profiles

Debug (just build) Release (just release)
Environment Configurable via VAUBAN_ENVIRONMENT Always Production
Default env development production
VAUBAN_ENVIRONMENT Functional Ignored (not compiled in)
Config files default.toml + {env}.toml + local.toml vauban.conf only
Config directory config/ (workspace root) /usr/local/etc/vauban/

Debug Build (Development)

Configuration files are layered in this order:

config/
├── default.toml      # Base values shared across environments
├── development.toml  # Development overrides (default)
├── testing.toml      # Testing overrides (cargo test)
└── local.toml        # Personal overrides (gitignored, optional)

You can switch the environment with VAUBAN_ENVIRONMENT:

export VAUBAN_ENVIRONMENT=development  # default when absent
export VAUBAN_ENVIRONMENT=testing      # used by cargo test
export VAUBAN_ENVIRONMENT=production   # loads vauban.conf even in debug

Release Build (Production)

The release binary loads a single self-contained configuration file:

/usr/local/etc/vauban/vauban.conf

VAUBAN_ENVIRONMENT is compiled out and has no effect. This guarantees that a production binary always runs in production mode, regardless of the runtime environment.

Configuration Directory Lookup

Both profiles resolve the config directory in this order:

  1. VAUBAN_CONFIG_DIR environment variable (if set; must exist)
  2. /usr/local/etc/vauban/ (FreeBSD package path) when present
  3. Workspace root config/ -- debug builds only

Release / packaged binaries never fall back to a compile-time workspace config/ path. The FreeBSD rc.d script exports VAUBAN_CONFIG_DIR=${vauban_config} (default /usr/local/etc/vauban). just run and just validate export VAUBAN_CONFIG_DIR to the repo config/ for local and staging use.

Secret Key

The application secret key can be set in three ways (highest priority first):

  1. Environment variable (cleared from memory after reading):
export VAUBAN_SECRET_KEY=$(openssl rand -base64 32)
  1. local.toml (debug builds, gitignored):
secret_key = "your-secure-random-key-here"
  1. vauban.conf (release builds, managed by +POST_INSTALL):
secret_key = "generated-at-install-time"

Cache

Vauban uses an in-process no-op cache and depends on no external cache server. The [cache] section only carries placeholders for a future in-memory backend:

[cache]
enabled = false
default_ttl_secs = 3600

Note: cache operations are no-ops today (reads always miss); the rate limiter is fully in-memory and single-process.

Database Setup

  1. Install Diesel CLI:
cargo install diesel_cli --no-default-features --features postgres
  1. Create the database:
createdb vauban
psql -c "CREATE USER vauban WITH PASSWORD 'vauban';"
psql -c "GRANT ALL PRIVILEGES ON DATABASE vauban TO vauban;"
psql -U postgres -d vauban -c "GRANT ALL ON SCHEMA public TO vauban; ALTER SCHEMA public OWNER TO vauban;"
  1. Run migrations:
diesel migration run --database-url postgresql://vauban:vauban@localhost/vauban

Note: Database URL is configured in config/default.toml. Adjust credentials as needed.

CLI Utilities

VAUBAN CLI administration is centralized in the supervisor binary. All utilities load their database configuration from config/*.toml (same as the main application).

Create Superuser

Create the initial superuser account:

# Production (release binary)
vauban-supervisor create-superuser

# Development (supervisor is default-members)
just run -- create-superuser

Reset Password

Reset a user's password:

# Production
vauban-supervisor reset-password <username>

# Development
just run -- reset-password <username>

Reset 2FA

Disable two-factor authentication for a user (the only way to disable MFA):

# Production
vauban-supervisor reset2fa <username>

# Development
just run -- reset2fa <username>

Seed Data

Populate the database with sample data for development:

# Production
vauban-supervisor seed-data

# Development
just run -- seed-data

Migrate Secrets

Batch-encrypt all plaintext secrets in the database using vauban-vault's keyring. This tool encrypts TOTP secrets and SSH credentials that would otherwise be stored in plaintext.

# Preview what would be migrated (no changes made)
vauban-supervisor migrate-secrets --dry-run
# or in development:
just run -- migrate-secrets --dry-run

# Run the migration
vauban-supervisor migrate-secrets
# or in development:
just run -- migrate-secrets

Prerequisites:

  • The master key must be available at /var/vauban/vault/master.key (or set VAUBAN_VAULT_MASTER_KEY_PATH). On FreeBSD, pkg install creates it via +POST_INSTALL when absent (root:vb-vault, mode 0440).
  • The key version file at /var/vauban/vault/key_version (optional, defaults to 1)

Environment variables:

Variable Default Description
VAUBAN_VAULT_MASTER_KEY_PATH /var/vauban/vault/master.key Path to the 32-byte master key
VAUBAN_VAULT_KEY_VERSION Read from file Override key version (must be >= 1)
VAUBAN_VAULT_KEY_VERSION_PATH /var/vauban/vault/key_version Path to key version file

The tool is idempotent: already-encrypted values (v{N}:... format) are skipped automatically. The --dry-run flag is recommended before any production migration.

What it migrates:

  • users.mfa_secret - TOTP secrets
  • assets.connection_config - Credential fields: password, private_key (SSH key material for auth_type = ssh_key), passphrase

Note: Encrypt-on-read is also built into the application itself. When a user logs in with a plaintext MFA secret, it is automatically encrypted and updated in the database. The migrate-secrets subcommand is useful for bulk migration of all secrets at once.

Migrate (schema)

Apply pending embedded database schema migrations through the baseline-aware runner. The supervisor never auto-migrates at boot; DDL is an explicit admin / package post-install step.

# Apply pending migrations
vauban-supervisor migrate
# or in development:
just run -- migrate

# Report pending migrations only (exit non-zero if any; no writes)
vauban-supervisor migrate --check

# Override the database URL (e.g. pkg post-install as the postgres OS user,
# which cannot read vauban.conf)
vauban-supervisor migrate --database-url postgresql:///vauban

URL resolution order: --database-url, then DATABASE_URL, then the configured database URL from vauban.conf / config/*.toml.

Asset Pubkeys

List SSH public keys of non-deleted assets with auth_type = ssh_key (OpenSSH public key text from connection_config; no vault decryption).

# psql-like table (default)
vauban-supervisor asset-pubkeys
# or in development:
just run -- asset-pubkeys

# One `user@host key` line per asset (rows without a public key skipped)
vauban-supervisor asset-pubkeys --format plain

API Endpoints

The /api/v1/* tree is the machine-to-machine surface. It authenticates exclusively via API keys (vbn_… prefix, sent as X-API-Key or Authorization: Bearer vbn_…). Human session JWTs (cookie or Bearer) are not accepted on /api/*. Each key carries coarse scopes (read, write, admin) intersected with the owner's Casbin permissions, plus an isolated secrets scope (outside the read/write/admin hierarchy) dedicated to the Vault Secrets endpoints. Keys are managed in the web UI at /accounts/apikeys.

Unauthenticated endpoints (POST /api/v1/auth/login) exist for legacy clients but return session JWTs usable only on web routes, not on subsequent /api/v1/* calls. Prefer API keys for automation.

When [api].enabled is false, every /api/v1/* route returns 501 Not Implemented (JSON body), all methods included.

When the API is enabled, status codes are honest (no anti-enumeration on the M2M surface): 401 for a missing or invalid API key, 403 for an insufficient scope or a denied authorization (including Vault Secrets provenance), 404 for a nonexistent or inactive resource, 400 for a malformed identifier (e.g. bad UUID). Every /api/* response carries Cache-Control: no-store.

Authentication

  • POST /api/v1/auth/login - Login (returns a web-session JWT; not for M2M)
  • POST /api/v1/auth/logout - Logout

Accounts

  • GET /api/v1/accounts - List users
  • POST /api/v1/accounts - Create user
  • GET /api/v1/accounts/{uuid} - Get user
  • PUT /api/v1/accounts/{uuid} - Update user
  • DELETE /api/v1/accounts/{uuid} - Delete user (501 Not Implemented)

Groups (Read-Only)

  • GET /api/v1/groups/{uuid}/members - List group members

Assets (User Zone -- read-only listing)

  • GET /api/v1/assets - List assets visible to the caller
  • DELETE /api/v1/assets/{uuid} - Delete asset (501 Not Implemented stub)

Assets (Admin Zone -- /api/v1/assets/manage/*, requires assets:manage)

  • POST /api/v1/assets/manage/ - Create asset
  • GET /api/v1/assets/manage/{uuid} - Get asset
  • PUT /api/v1/assets/manage/{uuid} - Update asset
  • GET /api/v1/assets/manage/groups - List asset groups
  • GET /api/v1/assets/manage/groups/{uuid}/assets - List assets in a group

SSH Host Key Verification (SSH assets, admin zone)

  • GET /api/v1/assets/manage/{uuid}/ssh-host-key - Host key status (verified, mismatch, or no_key)
  • POST /api/v1/assets/manage/{uuid}/ssh-host-key - Fetch host key from remote server (detects key changes)
  • POST /api/v1/assets/manage/{uuid}/ssh-host-key?confirm=true - Accept a changed host key

RDP Server Certificate Pinning (RDP assets, admin zone)

  • GET /api/v1/assets/manage/{uuid}/rdp-server-cert - Certificate status (verified, mismatch, or no_key)
  • POST /api/v1/assets/manage/{uuid}/rdp-server-cert - Fetch the server TLS certificate SPKI (detects certificate changes)
  • POST /api/v1/assets/manage/{uuid}/rdp-server-cert?confirm=true - Accept a changed certificate

Access Rules

  • GET /api/v1/access-rules - List access rules
  • POST /api/v1/access-rules - Create access rule
  • GET /api/v1/access-rules/{uuid} - Get access rule
  • PUT /api/v1/access-rules/{uuid} - Update access rule
  • DELETE /api/v1/access-rules/{uuid} - Delete access rule

Sessions

  • GET /api/v1/sessions - List sessions
  • POST /api/v1/sessions - Create session
  • GET /api/v1/sessions/{uuid} - Get session
  • POST /api/v1/sessions/{uuid}/terminate - Terminate session
  • DELETE /api/v1/sessions/{uuid} - Delete session (501 Not Implemented)

Vault Secrets (read-only, requires the dedicated secrets scope)

  • GET /api/v1/vault/secrets - List authorized secrets (metadata only)
  • GET /api/v1/vault/secrets/{uuid} - Get secret metadata (name, description, version, updated_at; the version counter increments on each rotation so consumers can detect one without downloading the value)
  • GET /api/v1/vault/secrets/{uuid}/value - Get the decrypted value (critical audit event)

The whole sub-tree is GET-only by design: secrets are created, rotated, and grouped in the web admin zone /vault/secrets (Casbin vault_secrets:manage). Beyond the API key, every call must pass asset provenance: the caller's source IP must match a registered SSH/RDP asset that actively proves its pinned host identity, and a (user group, secret group, asset group) access rule must allow the triple. See the Vault Architecture document, Section 11.

Testing

The project includes comprehensive tests following Rust best practices.

Test Structure

All integration tests live under vauban-web/tests/ and are compiled into a single binary, integration_tests:

vauban-web/tests/
├── integration_tests.rs   # Entry point (mod api; mod web; mod security; …)
├── common/                # TestApp harness, router, helpers
├── fixtures/              # DB fixtures (users, assets, sessions, …)
├── api/                   # REST API tests (/api/v1/*, API keys)
├── web/                   # HTML/HTMX page and navigation-flow tests
├── security/              # Auth, CSRF, rate limiting, IDOR, MFA invariants
├── middleware/            # PermissionContext, policy drift
├── services/              # Service-layer integration (DB-only)
├── ws/                    # WebSocket endpoint tests
└── ipc/                   # vauban-access in-process IPC tests

The workspace and vauban-proxy-rdp are tested separately (just test runs both).

Setting Up Test Database

  1. Run the setup script:
chmod +x vauban-web/scripts/setup_test_db.sh
./vauban-web/scripts/setup_test_db.sh

Or manually:

createdb vauban_test
psql -c "CREATE USER vauban_test WITH PASSWORD 'vauban_test';"
psql -c "GRANT ALL PRIVILEGES ON DATABASE vauban_test TO vauban_test;"
psql -U postgres -d vauban_test -c "GRANT ALL ON SCHEMA public TO vauban_test; ALTER SCHEMA public OWNER TO vauban_test;"
# Baseline-aware schema migrate (preferred over raw diesel migration run)
just run -- migrate --database-url postgresql://vauban_test:vauban_test@localhost/vauban_test

Note: Test configuration is in config/testing.toml. No environment variables needed.

Running Tests

# Full gate (workspace + vauban-proxy-rdp, --test-threads=1)
just test

# Workspace unit/lib tests only
just test --lib

# vauban-web integration binary (pin -p; --test is not workspace-wide)
cargo test -p vauban-web --test integration_tests -- --test-threads=1

# Subset by module name (examples)
cargo test -p vauban-web --test integration_tests -- web::login_post_expiry_test -- --test-threads=1
cargo test -p vauban-web --test integration_tests -- api::api_key_auth_test -- --test-threads=1

# Run tests with output
just test -- --nocapture

Test Coverage

  • Unit Tests: Services (auth, hash, JWT, TOTP), Models, Config, Error handling
  • Integration Tests: All API handlers, Database operations
  • Security Tests: Brute force protection, SQL injection, XSS prevention, Input validation

License

BSD 2-Clause License. See LICENSE for details.

Author

Richard Ben Aleya

About

A fortified bastion (PAM) designed to protect and control access to organization computing assets. Goal: deliver a unique free and open-source bastion software using the Rust programming language.

Topics

Resources

Stars

41 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages