Bascule shell runtime workspace — governed shell access layer for Substrate/Guildhouse FFC deployments. Crates: - bascule-agent: node agent with SSH server + command filtering - bascule-core: audit, grant engine, ceremony types, session - bascule-filter-core: log line filtering (stdio protocol) - bascule-gateway: OIDC auth, session management, SAT validation - bascule-node-agent: k8s DaemonSet agent (pod watcher, BPF manager) - bascule-proto: protobuf definitions - bascule-shell: governed SSH shell (commands, elevation, REPL) - bascule-tail: chronicle log tail + fanout - ceremony-engine: ceremony lifecycle (6 types + request/resolution) 172 tests passing. Implements SBS-SPEC-0001 shell model. Reference impl for SPEC-SHELLOPS-0001 Layer 1 (root shell).
147 lines
4.3 KiB
Rust
147 lines
4.3 KiB
Rust
//! bascule-agent — governed application sidecar.
|
|
//!
|
|
//! A single Rust process serving two interfaces over one governance engine:
|
|
//!
|
|
//! 1. **Governance IPC** — Unix socket at `/var/run/substrate/agent.sock`
|
|
//! Django middleware, Celery workers, and co-located apps talk Shellstream here.
|
|
//!
|
|
//! 2. **Interactive Shell** — SSH server on port 2222 (optional)
|
|
//! Bascule Gateway, `sb shell`, and Dashboard WebSocket connect here.
|
|
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use anyhow::Context;
|
|
use clap::Parser;
|
|
use tracing::{error, info};
|
|
|
|
mod command_filter;
|
|
mod config;
|
|
mod governance_server;
|
|
mod namespace;
|
|
mod session_store;
|
|
mod shellstream;
|
|
mod ssh_server;
|
|
|
|
use governance_server::GovernanceServer;
|
|
use namespace::NamespaceRouter;
|
|
use session_store::SessionStore;
|
|
use shellstream::Namespace;
|
|
use ssh_server::AgentSSHServer;
|
|
|
|
#[derive(Parser)]
|
|
#[command(name = "bascule-agent", about = "Governed application sidecar")]
|
|
struct Cli {
|
|
/// Path to shell.toml configuration file.
|
|
#[arg(long, default_value = "/etc/substrate/shell.toml")]
|
|
config: PathBuf,
|
|
|
|
/// Run with default config (dev mode, no config file needed).
|
|
#[arg(long)]
|
|
dev: bool,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> anyhow::Result<()> {
|
|
// Initialize tracing
|
|
tracing_subscriber::fmt()
|
|
.with_env_filter(
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
|
)
|
|
.json()
|
|
.init();
|
|
|
|
let cli = Cli::parse();
|
|
|
|
let config = if cli.dev {
|
|
info!("Starting in dev mode with default configuration");
|
|
config::AgentConfig::default_config()
|
|
} else {
|
|
config::AgentConfig::load(&cli.config)
|
|
.with_context(|| format!("Failed to load config from {:?}", cli.config))?
|
|
};
|
|
|
|
info!(
|
|
socket_path = %config.agent.socket_path,
|
|
ssh_enabled = config.agent.ssh.enabled,
|
|
namespace_backend = %config.agent.namespaces.backend,
|
|
"bascule-agent starting"
|
|
);
|
|
|
|
let dev_mode = config.agent.namespaces.backend == "soft";
|
|
let config = Arc::new(config);
|
|
|
|
// Build namespace router with all soft-mode handlers
|
|
let mut router = NamespaceRouter::new();
|
|
router.register(
|
|
Namespace::Crypto,
|
|
Arc::new(namespace::crypto::CryptoHandler::new()),
|
|
);
|
|
router.register(
|
|
Namespace::Identity,
|
|
Arc::new(namespace::identity::IdentityHandler::new(dev_mode)),
|
|
);
|
|
router.register(
|
|
Namespace::Secrets,
|
|
Arc::new(namespace::secrets::SecretsHandler::new()),
|
|
);
|
|
router.register(
|
|
Namespace::Governance,
|
|
Arc::new(namespace::governance::GovernanceHandler::new(dev_mode)),
|
|
);
|
|
router.register(
|
|
Namespace::Attestation,
|
|
Arc::new(namespace::attestation::AttestationHandler::new(
|
|
config.agent.namespaces.attestation.default_posture.clone(),
|
|
)),
|
|
);
|
|
router.register(
|
|
Namespace::Audit,
|
|
Arc::new(namespace::audit::AuditHandler::new()),
|
|
);
|
|
router.register(
|
|
Namespace::Network,
|
|
Arc::new(namespace::network::NetworkHandler::new()),
|
|
);
|
|
router.register(
|
|
Namespace::Intelligence,
|
|
Arc::new(namespace::intelligence::IntelligenceHandler::new()),
|
|
);
|
|
|
|
let router = Arc::new(router);
|
|
let sessions = Arc::new(SessionStore::new());
|
|
|
|
// Start governance IPC server
|
|
let server = GovernanceServer::new(config.clone(), router.clone(), sessions);
|
|
let ipc_handle = tokio::spawn(async move {
|
|
if let Err(e) = server.serve().await {
|
|
error!(error = %e, "Governance IPC server failed");
|
|
}
|
|
});
|
|
|
|
// Start SSH shell server (if enabled)
|
|
let ssh_handle = if config.agent.ssh.enabled {
|
|
let ssh_server = AgentSSHServer::new(config.clone(), router.clone())?;
|
|
Some(tokio::spawn(async move {
|
|
if let Err(e) = ssh_server.serve().await {
|
|
error!(error = %e, "SSH shell server failed");
|
|
}
|
|
}))
|
|
} else {
|
|
info!("SSH shell server disabled");
|
|
None
|
|
};
|
|
|
|
info!("bascule-agent ready");
|
|
|
|
// Wait for shutdown signal
|
|
tokio::signal::ctrl_c().await?;
|
|
info!("bascule-agent shutting down");
|
|
ipc_handle.abort();
|
|
if let Some(h) = ssh_handle {
|
|
h.abort();
|
|
}
|
|
|
|
Ok(())
|
|
}
|