mirror of
https://github.com/CloudNebulaProject/barycenter.git
synced 2026-04-10 13:10:42 +00:00
Add a Zanzibar-style relationship-based access control engine with OPA-style ABAC condition evaluation. Policies, roles, resources, and grants are defined in KDL files loaded from a configured directory at startup. Exposes a read-only REST API (POST /v1/check, /v1/expand, GET /healthz) on a dedicated port when authz.enabled = true. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
86 lines
2.8 KiB
Rust
86 lines
2.8 KiB
Rust
use axum::http::StatusCode;
|
|
use axum::response::{IntoResponse, Response};
|
|
use axum::Json;
|
|
use miette::Diagnostic;
|
|
use serde_json::json;
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error, Diagnostic)]
|
|
pub enum AuthzError {
|
|
#[error("Failed to load policy file `{path}`")]
|
|
#[diagnostic(
|
|
code(barycenter::authz::policy_load),
|
|
help("Check that the file exists and contains valid KDL syntax")
|
|
)]
|
|
PolicyLoadError {
|
|
path: String,
|
|
#[source]
|
|
source: std::io::Error,
|
|
},
|
|
|
|
#[error("Invalid policy: {0}")]
|
|
#[diagnostic(
|
|
code(barycenter::authz::invalid_policy),
|
|
help("Each policy file must contain valid `resource`, `role`, `rule`, or `grant` KDL nodes")
|
|
)]
|
|
InvalidPolicy(String),
|
|
|
|
#[error("Invalid grant: {0}")]
|
|
#[diagnostic(
|
|
code(barycenter::authz::invalid_grant),
|
|
help("Grant syntax: grant \"relation\" on=\"type/id\" to=\"subject_type/id\" (optionally to=\"type/id#relation\")")
|
|
)]
|
|
InvalidGrant(String),
|
|
|
|
#[error("Invalid condition expression: {0}")]
|
|
#[diagnostic(
|
|
code(barycenter::authz::invalid_condition),
|
|
help("Supported operators: ==, !=, >, <, >=, <=, &&, ||, !, in. Paths use dot notation (e.g. request.ip)")
|
|
)]
|
|
InvalidCondition(String),
|
|
|
|
#[error("Undefined resource type `{0}`")]
|
|
#[diagnostic(
|
|
code(barycenter::authz::undefined_resource),
|
|
help("Define the resource type with: resource \"<name>\" {{ relations {{ ... }} permissions {{ ... }} }}")
|
|
)]
|
|
UndefinedResourceType(String),
|
|
|
|
#[error("Undefined role `{0}`")]
|
|
#[diagnostic(
|
|
code(barycenter::authz::undefined_role),
|
|
help("Define the role with: role \"<name>\" {{ permissions {{ ... }} }}")
|
|
)]
|
|
UndefinedRole(String),
|
|
|
|
#[error("Cyclic role inheritance detected: {0}")]
|
|
#[diagnostic(
|
|
code(barycenter::authz::cyclic_roles),
|
|
help("Check the `includes` lists in your role definitions for circular references")
|
|
)]
|
|
CyclicRoleInheritance(String),
|
|
|
|
#[error("KDL parse error: {0}")]
|
|
#[diagnostic(
|
|
code(barycenter::authz::kdl_parse),
|
|
help("Check your KDL file syntax — see https://kdl.dev for the specification")
|
|
)]
|
|
KdlParse(String),
|
|
|
|
#[error("I/O error: {0}")]
|
|
#[diagnostic(code(barycenter::authz::io))]
|
|
Io(#[from] std::io::Error),
|
|
}
|
|
|
|
impl IntoResponse for AuthzError {
|
|
fn into_response(self) -> Response {
|
|
let (status, message) = match &self {
|
|
AuthzError::InvalidPolicy(_)
|
|
| AuthzError::InvalidGrant(_)
|
|
| AuthzError::InvalidCondition(_) => (StatusCode::BAD_REQUEST, self.to_string()),
|
|
_ => (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()),
|
|
};
|
|
let body = json!({ "error": message });
|
|
(status, Json(body)).into_response()
|
|
}
|
|
}
|