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).
109 lines
3.7 KiB
Rust
109 lines
3.7 KiB
Rust
//! IDENTITY namespace (0x0002) — OIDC token validation + authorization.
|
|
|
|
use async_trait::async_trait;
|
|
|
|
use crate::shellstream::{identity, ShellstreamResponse};
|
|
use super::NamespaceHandler;
|
|
|
|
pub struct IdentityHandler {
|
|
dev_mode: bool,
|
|
}
|
|
|
|
impl IdentityHandler {
|
|
pub fn new(dev_mode: bool) -> Self {
|
|
Self { dev_mode }
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl NamespaceHandler for IdentityHandler {
|
|
async fn handle(
|
|
&self,
|
|
function_id: u16,
|
|
payload: &[u8],
|
|
session_id: &[u8; 16],
|
|
) -> ShellstreamResponse {
|
|
match function_id {
|
|
identity::AUTHENTICATE => self.authenticate(payload, session_id).await,
|
|
identity::AUTHORIZE => self.authorize(payload, session_id).await,
|
|
identity::WHOAMI => self.resolve(payload, session_id).await,
|
|
_ => ShellstreamResponse::error(
|
|
*session_id,
|
|
0,
|
|
&format!("Unknown IDENTITY function: {function_id:#06x}"),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl IdentityHandler {
|
|
async fn authenticate(&self, payload: &[u8], session_id: &[u8; 16]) -> ShellstreamResponse {
|
|
let payload_parsed: serde_json::Value =
|
|
rmp_serde::from_slice(payload).unwrap_or(serde_json::Value::Null);
|
|
|
|
if self.dev_mode {
|
|
// Dev mode: accept any token, return synthetic identity
|
|
let response = rmp_serde::to_vec(&serde_json::json!({
|
|
"subject": "dev-user",
|
|
"email": "dev@guildhouse.local",
|
|
"issuer": "dev-mode",
|
|
"verified": true,
|
|
}))
|
|
.unwrap_or_default();
|
|
return ShellstreamResponse::ok(*session_id, 0, response);
|
|
}
|
|
|
|
// TODO: Validate JWT via OidcAuthProvider (Stage 3 — after moving to bascule-core)
|
|
let token = payload_parsed
|
|
.get("token")
|
|
.and_then(|v| v.as_str())
|
|
.unwrap_or("");
|
|
|
|
if token.is_empty() {
|
|
return ShellstreamResponse::denied(*session_id, 0, "No token provided");
|
|
}
|
|
|
|
// Placeholder: accept token and return minimal claims
|
|
let response = rmp_serde::to_vec(&serde_json::json!({
|
|
"subject": "unknown",
|
|
"verified": false,
|
|
"message": "OIDC validation not yet implemented",
|
|
}))
|
|
.unwrap_or_default();
|
|
ShellstreamResponse::ok(*session_id, 0, response)
|
|
}
|
|
|
|
async fn authorize(&self, payload: &[u8], session_id: &[u8; 16]) -> ShellstreamResponse {
|
|
if self.dev_mode {
|
|
let response = rmp_serde::to_vec(&serde_json::json!({
|
|
"authorized": true,
|
|
}))
|
|
.unwrap_or_default();
|
|
return ShellstreamResponse::ok(*session_id, 0, response);
|
|
}
|
|
|
|
let payload_parsed: serde_json::Value =
|
|
rmp_serde::from_slice(payload).unwrap_or(serde_json::Value::Null);
|
|
|
|
let subject = payload_parsed.get("subject").and_then(|v| v.as_str()).unwrap_or("");
|
|
let resource = payload_parsed.get("resource").and_then(|v| v.as_str()).unwrap_or("");
|
|
let action = payload_parsed.get("action").and_then(|v| v.as_str()).unwrap_or("");
|
|
|
|
tracing::debug!(subject, resource, action, "identity.authorize");
|
|
|
|
// Default: allow in soft mode
|
|
let response = rmp_serde::to_vec(&serde_json::json!({
|
|
"authorized": true,
|
|
}))
|
|
.unwrap_or_default();
|
|
ShellstreamResponse::ok(*session_id, 0, response)
|
|
}
|
|
|
|
async fn resolve(&self, _payload: &[u8], session_id: &[u8; 16]) -> ShellstreamResponse {
|
|
let response = rmp_serde::to_vec(&serde_json::json!({
|
|
"message": "resolve not yet implemented",
|
|
}))
|
|
.unwrap_or_default();
|
|
ShellstreamResponse::ok(*session_id, 0, response)
|
|
}
|
|
}
|