feat(rbac): complete HTTP endpoint integration + role configs
HTTP Endpoints with RBAC: - ingest_handler: project-level write access check - learn_handler: project-level write access check - projects_handler: filter returned projects by user access - query_handler: filter search results by resource access - context_handler: project-level read access check Example Role Configurations (config/roles/): - admin.yaml: full access to all resources - portfolio-agent.yaml: public visitor access - authenticated-user.yaml: logged-in user access - homelab-team.yaml: team-scoped project access All 660+ tests passing.
This commit is contained in:
@@ -2,9 +2,9 @@
|
||||
|
||||
## Summary
|
||||
|
||||
**Status**: Phases 1-7 complete with RBAC wired into HTTP + retrieval. 660+ tests passing.
|
||||
**Status**: Phases 1-7 complete with RBAC fully integrated. 660+ tests passing.
|
||||
|
||||
**Latest commit**: RBAC wired into HTTP server and retrieval pipeline
|
||||
**Latest commit**: RBAC wired into all HTTP endpoints + example role configs
|
||||
|
||||
---
|
||||
|
||||
@@ -111,8 +111,17 @@
|
||||
- ✅ **to_rbac_claims()**: Convert JwtClaims to RBAC Claims
|
||||
- ✅ **query_handler**: RBAC filtering on search results
|
||||
- ✅ **context_handler**: Project-level access check before lookup
|
||||
- ✅ **projects_handler**: Filter projects by user access
|
||||
- ✅ **ingest_handler**: Project-level write access check
|
||||
- ✅ **learn_handler**: Project-level write access check
|
||||
- ✅ **query_result_to_resource_meta()**: Convert results for RBAC filtering
|
||||
|
||||
### Example Role Configurations
|
||||
- ✅ `config/roles/admin.yaml`: Full access
|
||||
- ✅ `config/roles/portfolio-agent.yaml`: Public visitor access
|
||||
- ✅ `config/roles/authenticated-user.yaml`: Logged-in user access
|
||||
- ✅ `config/roles/homelab-team.yaml`: Team-scoped access example
|
||||
|
||||
### AuthorizedPipeline (Legacy - deprecated)
|
||||
- ✅ `AuthorizedPipeline`: wraps FullPipeline with access control
|
||||
- ✅ 13 unit tests, all passing
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# Admin role: full access to all resources
|
||||
name: admin
|
||||
description: Full administrative access to all resources
|
||||
|
||||
rules:
|
||||
- resources: ["*"]
|
||||
verbs: [read, write, delete, query]
|
||||
# No scope restrictions - access everything
|
||||
@@ -0,0 +1,15 @@
|
||||
# Authenticated User role: logged in via Authentik
|
||||
name: authenticated-user
|
||||
description: Logged in user with access to all public + private docs
|
||||
|
||||
rules:
|
||||
# Can read all wiki, embeddings, skills (including private)
|
||||
- resources: [wiki, embedding, skill]
|
||||
verbs: [read, query]
|
||||
# No visibility restriction - can see public AND private
|
||||
|
||||
# Can manage own conversations (any project)
|
||||
- resources: [conversation]
|
||||
verbs: [read, write, delete]
|
||||
scope:
|
||||
owner: self
|
||||
@@ -0,0 +1,17 @@
|
||||
# Homelab Team role: full access to homelab project
|
||||
name: homelab-team
|
||||
description: Team members with full access to homelab project
|
||||
|
||||
rules:
|
||||
# Full read/write access to homelab wiki and skills
|
||||
- resources: [wiki, skill, embedding]
|
||||
verbs: [read, write, query]
|
||||
scope:
|
||||
projects: [homelab]
|
||||
|
||||
# Can manage conversations in homelab
|
||||
- resources: [conversation]
|
||||
verbs: [read, write, delete]
|
||||
scope:
|
||||
projects: [homelab]
|
||||
# Note: no owner restriction - team can see all conversations
|
||||
@@ -0,0 +1,18 @@
|
||||
# Portfolio Agent role: public visitor access via portfolio site
|
||||
name: portfolio-agent
|
||||
description: Public visitor access - read public docs, manage own conversations
|
||||
|
||||
rules:
|
||||
# Can read/query public wiki and embeddings from allowed projects
|
||||
- resources: [wiki, embedding]
|
||||
verbs: [read, query]
|
||||
scope:
|
||||
projects: [homelab, rbc, aws, portfolio]
|
||||
visibility: public
|
||||
|
||||
# Can read/write own conversations in portfolio project only
|
||||
- resources: [conversation]
|
||||
verbs: [read, write]
|
||||
scope:
|
||||
projects: [portfolio]
|
||||
owner: self
|
||||
@@ -441,6 +441,23 @@ pub async fn ingest_handler(
|
||||
}
|
||||
|
||||
let project = body.project.clone();
|
||||
|
||||
// RBAC: Check project-level write access
|
||||
if let Some(guard) = &state.access_guard {
|
||||
let rbac_claims = to_rbac_claims(&claims);
|
||||
let project_resource = ResourceMeta::new(&project, ResourceType::Project, &project);
|
||||
|
||||
if !guard.can_write(&rbac_claims, &project_resource).await {
|
||||
tracing::warn!(
|
||||
"RBAC denied write access to project '{}' for user '{}'",
|
||||
project, claims.sub
|
||||
);
|
||||
return HttpResponse::Forbidden().json(json!({
|
||||
"error": "forbidden",
|
||||
"reason": format!("write access denied to project '{}'", project)
|
||||
}));
|
||||
}
|
||||
}
|
||||
let ingest_id = body.ingest_id.clone();
|
||||
let records: Vec<(String, String)> = body
|
||||
.records
|
||||
@@ -639,6 +656,23 @@ pub async fn learn_handler(
|
||||
}
|
||||
|
||||
let project = body["project"].as_str().unwrap_or("knowledge").to_string();
|
||||
|
||||
// RBAC: Check project-level write access
|
||||
if let Some(guard) = &state.access_guard {
|
||||
let rbac_claims = to_rbac_claims(&claims);
|
||||
let project_resource = ResourceMeta::new(&project, ResourceType::Project, &project);
|
||||
|
||||
if !guard.can_write(&rbac_claims, &project_resource).await {
|
||||
tracing::warn!(
|
||||
"RBAC denied write access to project '{}' for user '{}'",
|
||||
project, claims.sub
|
||||
);
|
||||
return HttpResponse::Forbidden().json(json!({
|
||||
"error": "forbidden",
|
||||
"reason": format!("write access denied to project '{}'", project)
|
||||
}));
|
||||
}
|
||||
}
|
||||
let text = match body["text"].as_str() {
|
||||
Some(t) => t.to_string(),
|
||||
None => return HttpResponse::BadRequest().json(json!({
|
||||
@@ -1017,7 +1051,22 @@ pub async fn projects_handler(
|
||||
|
||||
match result {
|
||||
Ok(rows) => {
|
||||
let projects: Vec<String> = rows.into_iter().map(|(p,)| p).collect();
|
||||
let mut projects: Vec<String> = rows.into_iter().map(|(p,)| p).collect();
|
||||
|
||||
// RBAC: Filter projects by access
|
||||
if let Some(guard) = &state.access_guard {
|
||||
let rbac_claims = to_rbac_claims(&claims);
|
||||
let mut allowed_projects = Vec::new();
|
||||
|
||||
for project in projects {
|
||||
let resource = ResourceMeta::new(&project, ResourceType::Project, &project);
|
||||
if guard.can_read(&rbac_claims, &resource).await {
|
||||
allowed_projects.push(project);
|
||||
}
|
||||
}
|
||||
projects = allowed_projects;
|
||||
}
|
||||
|
||||
HttpResponse::Ok().json(json!({
|
||||
"projects": projects,
|
||||
"count": projects.len()
|
||||
|
||||
Reference in New Issue
Block a user