Improve auth explain diagnostics
This commit is contained in:
parent
6f2fb53132
commit
ee80ff780c
7 changed files with 516 additions and 46 deletions
|
|
@ -3306,6 +3306,103 @@ fn can_sync_resource(
|
|||
.allowed)
|
||||
}
|
||||
|
||||
fn explain_auth_for_operator(
|
||||
store: &Store,
|
||||
subject: &str,
|
||||
resource: &str,
|
||||
capability: &str,
|
||||
) -> Result<AuthExplanation, NodeError> {
|
||||
let ops = load_auth_ops_for_resource(store, resource)?;
|
||||
let mut explanation = geth_auth::explain_auth_ops(
|
||||
&ops,
|
||||
PrincipalId::new(subject.to_owned()),
|
||||
ResourceId::new(resource.to_owned()),
|
||||
Capability::new(capability.to_owned()),
|
||||
);
|
||||
|
||||
if subject.starts_with("bearer:") {
|
||||
return Ok(explanation);
|
||||
}
|
||||
|
||||
let peer_card = store.get_peer_card(subject)?;
|
||||
let keychain_view = geth_keychain::reduce_keychain_ops(&load_keychain_ops(store)?);
|
||||
let subject_node = NodeId::new(subject.to_owned());
|
||||
let trusted_node = keychain_view.nodes.get(&subject_node);
|
||||
|
||||
match (trusted_node, peer_card) {
|
||||
(None, Some(_)) => {
|
||||
explanation.add_diagnostic("subject:discovered-only");
|
||||
explanation.add_diagnostic("trust:missing");
|
||||
if !explanation
|
||||
.reason
|
||||
.contains("discovery does not grant trust or authorization")
|
||||
{
|
||||
explanation.reason = format!(
|
||||
"subject is a discovered peer candidate only; discovery does not grant trust or authorization; {}",
|
||||
explanation.reason
|
||||
);
|
||||
}
|
||||
}
|
||||
(None, None) => {
|
||||
explanation.add_diagnostic("subject:unknown");
|
||||
explanation.add_diagnostic("trust:missing");
|
||||
if !explanation.allowed {
|
||||
explanation.reason = format!(
|
||||
"subject is not present in the keychain and has no discovered peer card; {}",
|
||||
explanation.reason
|
||||
);
|
||||
}
|
||||
}
|
||||
(Some(node), None) => {
|
||||
explanation.add_diagnostic("subject:trusted-node");
|
||||
if node.endpoints.is_empty() {
|
||||
explanation.add_diagnostic("endpoint-binding:missing");
|
||||
} else {
|
||||
explanation.add_diagnostic("endpoint-binding:present");
|
||||
explanation.add_diagnostic("discovery:missing-peer-card");
|
||||
}
|
||||
}
|
||||
(Some(node), Some(stored_card)) => {
|
||||
explanation.add_diagnostic("subject:trusted-node");
|
||||
let card = serde_json::from_str::<PeerCard>(&stored_card.card_json).ok();
|
||||
let matching_endpoint = card.as_ref().and_then(|card| {
|
||||
card.endpoints
|
||||
.iter()
|
||||
.map(|endpoint| endpoint.endpoint_id.as_str())
|
||||
.find(|endpoint| {
|
||||
keychain_view
|
||||
.endpoints
|
||||
.get(*endpoint)
|
||||
.is_some_and(|bound_node| bound_node == &node.id)
|
||||
})
|
||||
.map(ToOwned::to_owned)
|
||||
});
|
||||
|
||||
if let Some(endpoint) = matching_endpoint {
|
||||
explanation.add_diagnostic(format!("endpoint-binding:matched:{endpoint}"));
|
||||
} else if node.endpoints.is_empty() {
|
||||
explanation.add_diagnostic("endpoint-binding:missing");
|
||||
if !explanation.allowed {
|
||||
explanation.reason = format!(
|
||||
"trusted node has no active endpoint binding; {}",
|
||||
explanation.reason
|
||||
);
|
||||
}
|
||||
} else {
|
||||
explanation.add_diagnostic("endpoint-binding:missing-for-peer-card");
|
||||
if !explanation.allowed {
|
||||
explanation.reason = format!(
|
||||
"discovered peer card has no endpoint candidate bound to the trusted node; {}",
|
||||
explanation.reason
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(explanation)
|
||||
}
|
||||
|
||||
fn bearer_proof(
|
||||
bearer_secret: Option<String>,
|
||||
resource: &str,
|
||||
|
|
@ -3360,6 +3457,7 @@ fn explain_peer_or_bearer(
|
|||
allowed: false,
|
||||
reason,
|
||||
evaluated_ops: peer_explanation.evaluated_ops,
|
||||
diagnostics: vec!["subject:bearer-secret".to_owned()],
|
||||
};
|
||||
|
||||
if proof.resource != resource_id {
|
||||
|
|
@ -3425,6 +3523,11 @@ fn explain_peer_or_bearer(
|
|||
"bearer proof allows this resource-scoped capability without granting node identity"
|
||||
.to_owned(),
|
||||
evaluated_ops: peer_explanation.evaluated_ops,
|
||||
diagnostics: vec![
|
||||
"subject:bearer-secret".to_owned(),
|
||||
format!("bearer:active:{}", access.secret),
|
||||
"capability:matched".to_owned(),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -6756,35 +6859,12 @@ pub fn handle_request(
|
|||
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 {
|
||||
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))
|
||||
}
|
||||
}
|
||||
} => Ok(ControlResponse::AuthExplain(explain_auth_for_operator(
|
||||
&store,
|
||||
&subject,
|
||||
&resource,
|
||||
&capability,
|
||||
)?)),
|
||||
ControlRequest::AuthGrant {
|
||||
subject,
|
||||
resource,
|
||||
|
|
|
|||
Loading…
Reference in a new issue