Two of the four #[allow(dead_code)] methods in access.rs were dead
because nothing exposed them externally. access.rs itself is fine —
list_roles, set_role, can_access all have live callers. But get_role
and is_enabled were shaped as public API with no surface to call
them through.
Fix adds two small routes under /access (where the rest of the
access surface lives):
GET /access/roles/{agent}
Calls AccessControl::get_role(agent). Returns 404 with a clear
message when the agent isn't registered so clients distinguish
"unknown agent" from "access denied." Part of P13-001
(ops tooling needs per-agent role introspection).
GET /access/enabled
Calls AccessControl::is_enabled(). Returns {"enabled": bool}.
Dashboards + ops tooling poll this to confirm auth posture of
the running gateway — distinct from /health which answers
"is the process up" vs "is access enforcement on."
#[allow(dead_code)] removed from both methods — they have live
callers now via these routes, the linter will enforce that going
forward.
Still #[allow(dead_code)] on access.rs: masked_fields + log_query.
Both need cross-crate wiring:
- masked_fields wants the agent's role + query response columns,
called in response shaping (queryd returning to gateway path)
- log_query wants post-execution audit, called after every SQL
execution on the gateway boundary
Both are P13-001 phase 2 work — need AgentIdentity plumbed through
the /query nested router before the call sites make sense. Flagged
for follow-up.
Workspace warnings still at 0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
83 lines
2.3 KiB
Rust
83 lines
2.3 KiB
Rust
use axum::{
|
|
Json, Router,
|
|
extract::{Path, Query, State},
|
|
http::StatusCode,
|
|
response::IntoResponse,
|
|
routing::{get, post},
|
|
};
|
|
use serde::Deserialize;
|
|
|
|
use crate::access::{AccessControl, AgentRole};
|
|
|
|
pub fn router(ac: AccessControl) -> Router {
|
|
Router::new()
|
|
.route("/roles", get(list_roles))
|
|
.route("/roles", post(set_role))
|
|
// Scrum iter 11 / P13-001 finding: get_role was #[allow(dead_code)]
|
|
// because nothing called it — dead until exposed. Route activates it.
|
|
// Returns 404 when the agent isn't registered so clients can
|
|
// distinguish "missing role" from "access denied."
|
|
.route("/roles/{agent}", get(get_role))
|
|
.route("/enabled", get(enabled_status))
|
|
.route("/audit", get(query_audit))
|
|
.route("/check", post(check_access))
|
|
.with_state(ac)
|
|
}
|
|
|
|
async fn list_roles(State(ac): State<AccessControl>) -> impl IntoResponse {
|
|
Json(ac.list_roles().await)
|
|
}
|
|
|
|
async fn set_role(
|
|
State(ac): State<AccessControl>,
|
|
Json(role): Json<AgentRole>,
|
|
) -> impl IntoResponse {
|
|
let name = role.agent_name.clone();
|
|
ac.set_role(role).await;
|
|
(StatusCode::OK, format!("role set: {name}"))
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct AuditQuery {
|
|
limit: Option<usize>,
|
|
}
|
|
|
|
async fn query_audit(
|
|
State(ac): State<AccessControl>,
|
|
Query(q): Query<AuditQuery>,
|
|
) -> impl IntoResponse {
|
|
Json(ac.recent_audit(q.limit.unwrap_or(50)).await)
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct CheckRequest {
|
|
agent: String,
|
|
sensitivity: shared::types::Sensitivity,
|
|
}
|
|
|
|
async fn check_access(
|
|
State(ac): State<AccessControl>,
|
|
Json(req): Json<CheckRequest>,
|
|
) -> impl IntoResponse {
|
|
let allowed = ac.can_access(&req.agent, &req.sensitivity).await;
|
|
Json(serde_json::json!({
|
|
"agent": req.agent,
|
|
"sensitivity": req.sensitivity,
|
|
"allowed": allowed,
|
|
}))
|
|
}
|
|
|
|
async fn get_role(
|
|
State(ac): State<AccessControl>,
|
|
Path(agent): Path<String>,
|
|
) -> impl IntoResponse {
|
|
match ac.get_role(&agent).await {
|
|
Some(role) => Ok(Json(role)),
|
|
None => Err((StatusCode::NOT_FOUND, format!("no role registered for agent '{agent}'"))),
|
|
}
|
|
}
|
|
|
|
async fn enabled_status(State(ac): State<AccessControl>) -> impl IntoResponse {
|
|
Json(serde_json::json!({ "enabled": ac.is_enabled() }))
|
|
}
|