Wire auth explain to local auth ops

This commit is contained in:
Eric Wendland 2026-05-16 16:32:03 +02:00
commit f54920bb65
9 changed files with 299 additions and 19 deletions

View file

@ -1,6 +1,6 @@
pub mod service;
use geth_auth::AuthExplanation;
use geth_auth::{AuthExplanation, AuthOp, AuthOpKind};
use geth_cas::{LocalCas, hash_path};
use geth_config::{GethConfig, GethPaths, RelayMode};
use geth_control::{
@ -16,10 +16,12 @@ use geth_ssh_identity::{
certificate_id, revocation_id, ssh_public_key_fingerprint,
};
use geth_store::{
Store, StoredResource, StoredSshCertRequest, StoredSshCertificate, StoredSshRevocation,
Store, StoredAuthOp, StoredResource, StoredSshCertRequest, StoredSshCertificate,
StoredSshRevocation,
};
use geth_types::{
NodeId, ResourceId, ResourceKind, ResourceName, SshCertId, SshCertRequestId, UnixMillis,
AuthOpId, Capability, NodeId, PrincipalId, ResourceId, ResourceKind, ResourceName, SshCertId,
SshCertRequestId, UnixMillis,
};
use std::path::Path;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
@ -249,16 +251,67 @@ pub fn handle_request(
resource,
capability,
} => {
if store.get_peer_card(&subject)?.is_some() {
Ok(ControlResponse::AuthExplain(
AuthExplanation::discovered_candidate(subject, resource, capability),
))
let ops = load_auth_ops_for_resource(&store, &resource)?;
let discovered = store.get_peer_card(&subject)?.is_some();
if ops.is_empty() {
if discovered {
Ok(ControlResponse::AuthExplain(
AuthExplanation::discovered_candidate(subject, resource, capability),
))
} else {
Ok(ControlResponse::AuthExplain(AuthExplanation::stub(
subject, resource, capability,
)))
}
} else {
Ok(ControlResponse::AuthExplain(AuthExplanation::stub(
subject, resource, capability,
)))
let mut explanation = geth_auth::explain_auth_ops(
&ops,
PrincipalId::new(subject.clone()),
ResourceId::new(resource.clone()),
Capability::new(capability.clone()),
);
if discovered && !explanation.allowed {
explanation.reason = format!(
"subject is a discovered peer candidate only; discovery does not grant trust or authorization; {}",
explanation.reason
);
}
Ok(ControlResponse::AuthExplain(explanation))
}
}
ControlRequest::AuthGrant {
subject,
resource,
capability,
grant_id,
} => {
let created_at = UnixMillis(geth_store::now_ms());
let grant_id =
grant_id.unwrap_or_else(|| generated_grant_id(&subject, &resource, &capability));
let op = AuthOp {
id: generated_auth_op_id("grant-create", &resource, &grant_id, created_at),
resource: ResourceId::new(resource),
created_at,
kind: AuthOpKind::GrantCreate {
grant_id,
principal: PrincipalId::new(subject),
capabilities: vec![Capability::new(capability)],
},
};
store_auth_op(&store, &op)?;
Ok(ControlResponse::AuthOpRecorded { op })
}
ControlRequest::AuthRevoke { resource, grant_id } => {
let created_at = UnixMillis(geth_store::now_ms());
let op = AuthOp {
id: generated_auth_op_id("grant-revoke", &resource, &grant_id, created_at),
resource: ResourceId::new(resource),
created_at,
kind: AuthOpKind::GrantRevoke { grant_id },
};
store_auth_op(&store, &op)?;
Ok(ControlResponse::AuthOpRecorded { op })
}
ControlRequest::SshCertRequest {
public_key_path,
cert_kind,
@ -447,6 +500,45 @@ fn stored_resource_to_descriptor(stored: StoredResource) -> Result<ResourceDescr
))
}
fn store_auth_op(store: &Store, op: &AuthOp) -> Result<(), NodeError> {
store.insert_auth_op(&StoredAuthOp {
op_id: op.id.to_string(),
resource_id: op.resource.to_string(),
op_json: serde_json::to_string(op)?,
created_at_ms: op.created_at.0,
})?;
Ok(())
}
fn load_auth_ops_for_resource(store: &Store, resource: &str) -> Result<Vec<AuthOp>, NodeError> {
store
.list_auth_ops_for_resource(resource)?
.into_iter()
.map(|stored| serde_json::from_str(&stored.op_json).map_err(NodeError::from))
.collect()
}
fn generated_grant_id(subject: &str, resource: &str, capability: &str) -> String {
format!(
"grant:{}",
geth_crypto::blake3_hex(format!("{subject}\0{resource}\0{capability}").as_bytes())
)
}
fn generated_auth_op_id(
kind: &str,
resource: &str,
stable_id: &str,
created_at: UnixMillis,
) -> AuthOpId {
AuthOpId::new(format!(
"auth-op:{}",
geth_crypto::blake3_hex(
format!("{}\0{kind}\0{resource}\0{stable_id}", created_at.0).as_bytes()
)
))
}
fn stable_node_id(agent_id: &str) -> String {
format!("node:{agent_id}")
}