feat(bascule-gateway): derive ShellClass at ceremony grant from posture
Read the cluster's operational posture level from the posture-current ConfigMap at ceremony grant time. Derive ShellClass via derive_shell_class() and stamp into the granted SessionScope. - Normal posture (5) -> ShellClass::System - Any DEFCON escalation -> ShellClass::Application - Fail-closed: missing ConfigMap -> Lockdown -> Application - posture_level_at_establishment stored for audit/breach comparison Signed-off-by: Tyler King <tking@guildhouse.dev> Signed-off-by: Tyler J King <tking727@gmail.com>
This commit is contained in:
parent
e28be3335d
commit
1a54cc3877
3 changed files with 66 additions and 1 deletions
|
|
@ -18,6 +18,9 @@ accord-core = { path = "../../guildhouse/services/accord-core" }
|
|||
accord-opa = { path = "../../guildhouse/services/accord-opa" }
|
||||
qm-core = { path = "../../guildhouse/services/qm-core" }
|
||||
|
||||
# Cross-workspace path dep — substrate governance types (for PostureLevel).
|
||||
governance-types = { path = "../../substrate/crates/governance-types" }
|
||||
|
||||
# Kubernetes
|
||||
kube = { workspace = true }
|
||||
k8s-openapi = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -98,7 +98,23 @@ impl bascule_proto::bascule_v1::bascule_gateway_server::BasculeGateway for Bascu
|
|||
};
|
||||
|
||||
match response {
|
||||
CeremonyResponse::Granted(grant) => {
|
||||
CeremonyResponse::Granted(mut grant) => {
|
||||
// Derive ShellClass from the cluster's current posture level.
|
||||
// Reads posture-current ConfigMap, maps level to PostureLevel,
|
||||
// derives ShellClass, and stamps into the granted scope.
|
||||
let posture_level = read_posture_level().await;
|
||||
let shell_class = bascule_core::derive_shell_class(posture_level, None);
|
||||
grant.granted_scope.shell_class = shell_class;
|
||||
grant.granted_scope.posture_level_at_establishment =
|
||||
Some(posture_level.to_wire());
|
||||
|
||||
tracing::info!(
|
||||
ceremony_id = %grant.ceremony_id,
|
||||
posture_level = ?posture_level,
|
||||
shell_class = %shell_class,
|
||||
"Session shell class derived at ceremony grant"
|
||||
);
|
||||
|
||||
let session = self
|
||||
.session_manager
|
||||
.create_session(&grant)
|
||||
|
|
@ -403,6 +419,10 @@ fn proto_scope_to_core(proto: &bascule_proto::bascule_v1::SessionScope) -> Sessi
|
|||
pathways: proto.pathways.iter().map(|p| parse_pathway(p)).collect(),
|
||||
mutation_budget: proto.mutation_budget,
|
||||
can_delegate: proto.can_delegate,
|
||||
// ShellClass is server-derived from posture, not client-requested.
|
||||
// Set to default here; stamped by the ceremony grant path.
|
||||
shell_class: bascule_core::ShellClass::default(),
|
||||
posture_level_at_establishment: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -497,3 +517,43 @@ fn core_global_to_proto(core: &GlobalScope) -> bascule_proto::bascule_v1::Global
|
|||
can_view_topology: core.can_view_topology,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Posture level reader ---
|
||||
|
||||
/// Read the cluster's current operational posture level from the
|
||||
/// `posture-current` ConfigMap. Falls back to `PostureLevel::Lockdown`
|
||||
/// (fail-closed) if the ConfigMap is missing or unreadable.
|
||||
async fn read_posture_level() -> governance_types::PostureLevel {
|
||||
use governance_types::PostureLevel;
|
||||
|
||||
let client = match kube::Client::try_default().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "kube client init failed for posture read");
|
||||
return PostureLevel::Lockdown;
|
||||
}
|
||||
};
|
||||
|
||||
let namespace = std::env::var("ENFORCEMENT_NAMESPACE")
|
||||
.unwrap_or_else(|_| "guildhouse-infra".into());
|
||||
|
||||
use k8s_openapi::api::core::v1::ConfigMap;
|
||||
use kube::api::Api;
|
||||
|
||||
let api: Api<ConfigMap> = Api::namespaced(client, &namespace);
|
||||
match api.get("posture-current").await {
|
||||
Ok(cm) => {
|
||||
let level_u8: u8 = cm
|
||||
.data
|
||||
.as_ref()
|
||||
.and_then(|d| d.get("level"))
|
||||
.and_then(|v| v.parse().ok())
|
||||
.unwrap_or(1);
|
||||
PostureLevel::from_wire(level_u8).unwrap_or(PostureLevel::Lockdown)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "posture-current ConfigMap read failed");
|
||||
PostureLevel::Lockdown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,6 +221,8 @@ impl SessionManager {
|
|||
pathways: vec![ChangePathway::DryRunOnly],
|
||||
mutation_budget: Some(0),
|
||||
can_delegate: false,
|
||||
shell_class: bascule_core::ShellClass::default(),
|
||||
posture_level_at_establishment: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue