summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/authorization/authorizer.rs5
-rw-r--r--src/authorization/cedar_authorizer.rs183
-rw-r--r--src/authorization/check_service.rs33
-rw-r--r--src/authorization/entities.rs145
-rw-r--r--src/authorization/mod.rs11
-rw-r--r--src/authorization/server.rs42
-rw-r--r--src/bin/cli.rs94
-rw-r--r--src/gitlab/api.rs53
-rw-r--r--src/gitlab/group.rs10
-rw-r--r--src/gitlab/member.rs9
-rw-r--r--src/gitlab/mod.rs11
-rw-r--r--src/gitlab/namespace.rs11
-rw-r--r--src/gitlab/project.rs11
-rw-r--r--src/lib.rs6
14 files changed, 0 insertions, 624 deletions
diff --git a/src/authorization/authorizer.rs b/src/authorization/authorizer.rs
deleted file mode 100644
index 62733585..00000000
--- a/src/authorization/authorizer.rs
+++ /dev/null
@@ -1,5 +0,0 @@
-use envoy_types::ext_authz::v3::pb::CheckRequest;
-
-pub trait Authorizer: std::fmt::Debug + std::marker::Sync + std::marker::Send + 'static {
- fn authorize(&self, request: CheckRequest) -> bool;
-}
diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs
deleted file mode 100644
index 0d30ee77..00000000
--- a/src/authorization/cedar_authorizer.rs
+++ /dev/null
@@ -1,183 +0,0 @@
-use super::authorizer::Authorizer;
-use std::fs;
-use std::str::FromStr;
-
-#[derive(Debug)]
-pub struct CedarAuthorizer {
- authorizer: cedar_policy::Authorizer,
- entities: cedar_policy::Entities,
- policies: cedar_policy::PolicySet,
-}
-
-impl CedarAuthorizer {
- pub fn new(
- policies: cedar_policy::PolicySet,
- entities: cedar_policy::Entities,
- ) -> CedarAuthorizer {
- CedarAuthorizer {
- policies,
- entities,
- authorizer: cedar_policy::Authorizer::new(),
- }
- }
-
- pub fn new_from(path: &std::path::Path, entities: cedar_policy::Entities) -> CedarAuthorizer {
- Self::new(
- Self::load_from(path).unwrap_or_else(|e| {
- tracing::error!(
- path = ?path,
- error = %e,
- "Failed to load Cedar policies, using empty policy set"
- );
- cedar_policy::PolicySet::default()
- }),
- entities,
- )
- }
-
- fn load_from(
- path: &std::path::Path,
- ) -> Result<cedar_policy::PolicySet, Box<dyn std::error::Error>> {
- if !path.exists() {
- return Ok(cedar_policy::PolicySet::default());
- }
-
- if path.is_file() && path.extension().is_some_and(|ext| ext == "cedar") {
- let content = fs::read_to_string(path)?;
- return Ok(cedar_policy::PolicySet::from_str(&content)?);
- }
-
- if !path.is_dir() {
- return Ok(cedar_policy::PolicySet::default());
- }
-
- let mut policies = cedar_policy::PolicySet::new();
- for entry in fs::read_dir(path)? {
- policies.merge(&Self::load_from(&entry?.path())?, true)?;
- }
- Ok(policies)
- }
-
- fn map_from(
- &self,
- http_request: envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
- ) -> Result<cedar_policy::Request, Box<dyn std::error::Error>> {
- let principal = self.principal_from(&http_request)?;
- let permission = self.permission_from(&http_request)?;
- let resource = self.resource_from(&http_request)?;
- let context = self.context_from(http_request)?;
-
- Ok(cedar_policy::Request::new(
- principal, permission, resource, context, None,
- )?)
- }
-
- fn principal_from(
- &self,
- http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
- ) -> Result<cedar_policy::EntityUid, Box<dyn std::error::Error>> {
- let subject = http_request
- .headers
- .get("x-jwt-claim-sub")
- .map_or("", |v| v);
-
- Ok(cedar_policy::EntityUid::from_type_name_and_id(
- cedar_policy::EntityTypeName::from_str("User")?,
- cedar_policy::EntityId::from_str(subject)?,
- ))
- }
-
- fn permission_from(
- &self,
- http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
- ) -> Result<cedar_policy::EntityUid, Box<dyn std::error::Error>> {
- Ok(cedar_policy::EntityUid::from_type_name_and_id(
- cedar_policy::EntityTypeName::from_str("Action")?,
- cedar_policy::EntityId::from_str(&http_request.method)?,
- ))
- }
-
- fn resource_from(
- &self,
- http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
- ) -> Result<cedar_policy::EntityUid, Box<dyn std::error::Error>> {
- Ok(cedar_policy::EntityUid::from_type_name_and_id(
- cedar_policy::EntityTypeName::from_str("Resource")?,
- cedar_policy::EntityId::from_str(&http_request.path)?,
- ))
- }
-
- fn context_from(
- &self,
- http_request: envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest,
- ) -> Result<cedar_policy::Context, Box<dyn std::error::Error>> {
- let mut items = std::collections::HashMap::new();
-
- items.insert("host".to_string(), self.safe_string(&http_request.host));
- items.insert("method".to_string(), self.safe_string(&http_request.method));
- items.insert("path".to_string(), self.safe_string(&http_request.path));
-
- Ok(cedar_policy::Context::from_pairs(
- items.into_iter().collect::<Vec<_>>(),
- )?)
- }
-
- fn safe_string(&self, item: &str) -> cedar_policy::RestrictedExpression {
- cedar_policy::RestrictedExpression::new_string(item.to_string())
- }
-}
-
-impl Default for CedarAuthorizer {
- fn default() -> Self {
- Self::new_from(
- std::path::Path::new("./etc/authzd"),
- cedar_policy::Entities::empty(),
- )
- }
-}
-
-impl Authorizer for CedarAuthorizer {
- fn authorize(&self, request: envoy_types::ext_authz::v3::pb::CheckRequest) -> bool {
- let http_request = match request
- .attributes
- .as_ref()
- .and_then(|attr| attr.request.as_ref())
- .and_then(|req| req.http.as_ref())
- {
- Some(http) => http,
- None => return false,
- };
-
- match self.map_from(http_request.clone()) {
- Ok(cedar_request) => {
- let response =
- self.authorizer
- .is_authorized(&cedar_request, &self.policies, &self.entities);
-
- let decision = response.decision();
-
- tracing::info!(
- decision = ?decision,
- diagnostics = ?response.diagnostics(),
- principal = %cedar_request.principal().unwrap(),
- action = %cedar_request.action().unwrap(),
- resource = %cedar_request.resource().unwrap(),
- host = %http_request.host,
- method = %http_request.method,
- path = %http_request.path,
- "http"
- );
-
- matches!(decision, cedar_policy::Decision::Allow)
- }
- Err(e) => {
- tracing::error!(
- error = %e,
- path = %http_request.path,
- "Failed to create Cedar request"
- );
- false
- }
- }
- }
-}
diff --git a/src/authorization/check_service.rs b/src/authorization/check_service.rs
deleted file mode 100644
index f8c7577f..00000000
--- a/src/authorization/check_service.rs
+++ /dev/null
@@ -1,33 +0,0 @@
-use envoy_types::ext_authz::v3::CheckResponseExt;
-use envoy_types::ext_authz::v3::pb::{CheckRequest, CheckResponse};
-use std::sync::Arc;
-use tonic::{Request, Response, Status};
-
-use super::authorizer::Authorizer;
-
-#[derive(Debug)]
-pub struct CheckService {
- authorizer: Arc<dyn Authorizer + Send + Sync>,
-}
-
-impl CheckService {
- pub fn new(authorizer: Arc<dyn Authorizer + Send + Sync>) -> Self {
- Self { authorizer }
- }
-}
-
-#[tonic::async_trait]
-impl envoy_types::ext_authz::v3::pb::Authorization for CheckService {
- async fn check(
- &self,
- request: Request<CheckRequest>,
- ) -> Result<Response<CheckResponse>, Status> {
- if self.authorizer.authorize(request.into_inner()) {
- Ok(Response::new(CheckResponse::with_status(Status::ok("OK"))))
- } else {
- Ok(Response::new(CheckResponse::with_status(
- Status::unauthenticated("Unauthorized"),
- )))
- }
- }
-}
diff --git a/src/authorization/entities.rs b/src/authorization/entities.rs
deleted file mode 100644
index 050f6f26..00000000
--- a/src/authorization/entities.rs
+++ /dev/null
@@ -1,145 +0,0 @@
-use crate::gitlab::Api;
-use serde::Serialize;
-use std::collections::HashSet;
-use std::future::Future;
-use std::pin::Pin;
-
-type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
-
-// Cedar entity structures
-// Note: We define custom types instead of using cedar_policy::Entity directly because:
-// 1. Cedar's Entity type is for runtime use, not JSON serialization
-// 2. These types ensure our JSON output matches Cedar's expected format exactly
-// 3. The #[serde(rename)] attributes handle Cedar's specific field naming requirements
-#[derive(Debug, Serialize)]
-pub struct CedarEntity {
- pub uid: CedarUid,
- pub attrs: serde_json::Value,
- pub parents: Vec<CedarParent>,
-}
-
-#[derive(Debug, Serialize)]
-pub struct CedarUid {
- #[serde(rename = "type")]
- pub entity_type: String,
- pub id: String,
-}
-
-#[derive(Debug, Serialize)]
-pub struct CedarParent {
- #[serde(rename = "type")]
- pub parent_type: String,
- pub id: String,
-}
-
-pub struct EntitiesRepository {
- api: Api,
-}
-
-impl EntitiesRepository {
- pub fn new(api: Api) -> EntitiesRepository {
- EntitiesRepository { api }
- }
-
- pub async fn all(
- &self,
- project_path: String,
- ) -> Result<Vec<CedarEntity>, Box<dyn std::error::Error>> {
- let mut entities = Vec::new();
- let mut groups = HashSet::new();
-
- let project = self.api.get_project(&project_path).await?;
-
- entities.push(CedarEntity {
- uid: CedarUid {
- entity_type: "Project".to_string(),
- id: project.id.to_string(),
- },
- attrs: serde_json::json!({
- "name": project.name,
- "path": project.path,
- "full_path": format!("{}/{}", project.namespace.full_path, project.path),
- }),
- parents: if project.namespace.kind == "group" {
- vec![CedarParent {
- parent_type: "Group".to_string(),
- id: project.namespace.id.to_string(),
- }]
- } else {
- vec![]
- },
- });
-
- for member in self.api.get_project_members(project.id).await? {
- if member.state == "active" {
- entities.push(CedarEntity {
- uid: CedarUid {
- entity_type: "User".to_string(),
- id: member.id.to_string(),
- },
- attrs: serde_json::json!({
- "username": member.username,
- "access_level": member.access_level,
- }),
- parents: vec![],
- });
- }
- }
-
- if project.namespace.kind == "group" {
- self.fetch_hierarchy(project.namespace.id, &mut entities, &mut groups)
- .await?;
- }
-
- Ok(entities)
- }
-
- /// Validates that the entities can be parsed by Cedar
- pub fn is_valid(entities: &[CedarEntity]) -> Result<(), Box<dyn std::error::Error>> {
- let json = serde_json::to_string(entities)?;
- cedar_policy::Entities::from_json_str(&json, None)?;
- Ok(())
- }
-
- fn fetch_hierarchy<'a>(
- &'a self,
- group_id: u64,
- entities: &'a mut Vec<CedarEntity>,
- groups: &'a mut HashSet<u64>,
- ) -> BoxFuture<'a, Result<(), Box<dyn std::error::Error>>> {
- Box::pin(async move {
- if groups.contains(&group_id) {
- return Ok(());
- }
-
- groups.insert(group_id);
-
- let group = self.api.get_group(group_id).await?;
-
- let parents = if let Some(parent_id) = group.parent_id {
- self.fetch_hierarchy(parent_id, entities, groups).await?;
- vec![CedarParent {
- parent_type: "Group".to_string(),
- id: parent_id.to_string(),
- }]
- } else {
- vec![]
- };
-
- entities.push(CedarEntity {
- uid: CedarUid {
- entity_type: "Group".to_string(),
- id: group.id.to_string(),
- },
- attrs: serde_json::json!({
- "name": group.name,
- "path": group.path,
- "full_path": group.full_path,
- }),
- parents,
- });
-
- Ok(())
- })
- }
-}
diff --git a/src/authorization/mod.rs b/src/authorization/mod.rs
deleted file mode 100644
index d687d53f..00000000
--- a/src/authorization/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-pub mod authorizer;
-pub mod cedar_authorizer;
-pub mod check_service;
-pub mod entities;
-pub mod server;
-
-pub use authorizer::Authorizer;
-pub use cedar_authorizer::CedarAuthorizer;
-pub use check_service::CheckService;
-pub use entities::{CedarEntity, EntitiesRepository};
-pub use server::Server;
diff --git a/src/authorization/server.rs b/src/authorization/server.rs
deleted file mode 100644
index 31bf2af8..00000000
--- a/src/authorization/server.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use super::cedar_authorizer::CedarAuthorizer;
-use super::check_service::CheckService;
-use envoy_types::ext_authz::v3::pb::AuthorizationServer;
-use std::sync::Arc;
-
-pub struct Server {
- router: tonic::transport::server::Router,
-}
-
-impl Server {
- pub fn new<T: super::Authorizer>(authorizer: T) -> Result<Server, Box<dyn std::error::Error>> {
- let authorization_service =
- AuthorizationServer::new(CheckService::new(Arc::new(authorizer)));
-
- Ok(Self::new_with(|mut builder| {
- builder.add_service(authorization_service)
- }))
- }
-
- pub fn new_with<F>(f: F) -> Server
- where
- F: FnOnce(tonic::transport::Server) -> tonic::transport::server::Router,
- {
- let builder = tonic::transport::Server::builder()
- .trace_fn(
- |req| tracing::info_span!("rpc", method = %req.method(), path = %req.uri().path()),
- )
- .timeout(std::time::Duration::from_secs(30));
- let router = f(builder);
- Server { router }
- }
-
- pub async fn serve(self, addr: std::net::SocketAddr) -> Result<(), tonic::transport::Error> {
- self.router.serve(addr).await
- }
-}
-
-impl Default for Server {
- fn default() -> Self {
- Self::new(CedarAuthorizer::default()).unwrap()
- }
-}
diff --git a/src/bin/cli.rs b/src/bin/cli.rs
deleted file mode 100644
index 78aa1ba1..00000000
--- a/src/bin/cli.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-use authzd::EntitiesRepository;
-use authzd::gitlab::Api;
-use clap::{Parser, Subcommand};
-
-#[derive(Parser, Debug)]
-#[command(
- author,
- version,
- about = "Authorization CLI for managing Cedar entities and policies"
-)]
-struct Args {
- #[command(subcommand)]
- command: Commands,
-}
-
-#[derive(Subcommand, Debug)]
-enum Commands {
- /// Generate entities from GitLab API
- Generate {
- /// Project ID or path (e.g., gitlab-org/gitlab)
- #[arg(short, long)]
- project: String,
-
- /// Output file path
- #[arg(short, long, default_value = "entities.json")]
- output: String,
-
- /// GitLab API token
- #[arg(short, long, env = "GITLAB_TOKEN")]
- token: String,
-
- /// GitLab instance URL
- #[arg(
- short = 'H',
- long,
- env = "GITLAB_HOST",
- default_value = "https://gitlab.com"
- )]
- host: String,
- },
- Server {
- /// Address to bind to
- #[arg(short, long, env = "BIND_ADDR", default_value = "127.0.0.1:50052")]
- addr: String,
- },
-}
-
-#[tokio::main]
-async fn main() -> Result<(), Box<dyn std::error::Error>> {
- let args = Args::parse();
-
- match args.command {
- Commands::Generate {
- project,
- output,
- token,
- host,
- } => {
- let repository = EntitiesRepository::new(Api::new(token, host));
- let entities = repository.all(project).await?;
- EntitiesRepository::is_valid(&entities)?;
- let json = serde_json::to_string_pretty(&entities)?;
- std::fs::write(&output, json)?;
-
- println!(
- "Successfully generated {} entities to {}",
- entities.len(),
- output
- );
- }
- Commands::Server { addr } => {
- tracing_subscriber::fmt()
- .json()
- .with_ansi(false)
- .with_current_span(true)
- .with_file(true)
- .with_level(false)
- .with_line_number(true)
- .with_max_level(tracing::Level::INFO)
- .with_span_list(true)
- .with_target(false)
- .with_thread_ids(false)
- .with_thread_names(false)
- .init();
-
- tracing::info!(address = %addr, "Starting");
- authzd::authorization::Server::new(authzd::authorization::CedarAuthorizer::default())?
- .serve(addr.parse().unwrap())
- .await?;
- }
- }
-
- Ok(())
-}
diff --git a/src/gitlab/api.rs b/src/gitlab/api.rs
deleted file mode 100644
index 9047ddaf..00000000
--- a/src/gitlab/api.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-use crate::gitlab::{Group, Member, Project};
-use reqwest::Client;
-
-pub struct Api {
- pub token: String,
- pub host: String,
- client: Client,
-}
-
-impl Api {
- pub fn new(token: String, host: String) -> Api {
- Api {
- token,
- host,
- client: Client::new(),
- }
- }
-
- pub async fn get_project(&self, project: &str) -> Result<Project, Box<dyn std::error::Error>> {
- self.get::<Project>(format!("/api/v4/projects/{}", urlencoding::encode(project)))
- .await
- }
-
- pub async fn get_project_members(
- &self,
- project_id: u64,
- ) -> Result<Vec<Member>, Box<dyn std::error::Error>> {
- self.get::<Vec<Member>>(format!("/api/v4/projects/{project_id}/members/all"))
- .await
- }
-
- pub async fn get_group(&self, group_id: u64) -> Result<Group, Box<dyn std::error::Error>> {
- self.get::<Group>(format!("/api/v4/groups/{group_id}"))
- .await
- }
-
- async fn get<T: serde::de::DeserializeOwned>(
- &self,
- path: String,
- ) -> Result<T, Box<dyn std::error::Error>> {
- let url = format!("{}{}", self.host.trim_end_matches('/'), path);
-
- Ok(self
- .client
- .get(&url)
- .header("PRIVATE-TOKEN", &self.token)
- .send()
- .await?
- .error_for_status()?
- .json()
- .await?)
- }
-}
diff --git a/src/gitlab/group.rs b/src/gitlab/group.rs
deleted file mode 100644
index 6b00e87d..00000000
--- a/src/gitlab/group.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-use serde::Deserialize;
-
-#[derive(Debug, Deserialize)]
-pub struct Group {
- pub id: u64,
- pub name: String,
- pub path: String,
- pub full_path: String,
- pub parent_id: Option<u64>,
-}
diff --git a/src/gitlab/member.rs b/src/gitlab/member.rs
deleted file mode 100644
index b44b88f2..00000000
--- a/src/gitlab/member.rs
+++ /dev/null
@@ -1,9 +0,0 @@
-use serde::Deserialize;
-
-#[derive(Debug, Deserialize)]
-pub struct Member {
- pub id: u64,
- pub username: String,
- pub state: String,
- pub access_level: u64,
-}
diff --git a/src/gitlab/mod.rs b/src/gitlab/mod.rs
deleted file mode 100644
index e1993d81..00000000
--- a/src/gitlab/mod.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-pub mod api;
-pub mod group;
-pub mod member;
-pub mod namespace;
-pub mod project;
-
-pub use api::Api;
-pub use group::Group;
-pub use member::Member;
-pub use namespace::Namespace;
-pub use project::Project;
diff --git a/src/gitlab/namespace.rs b/src/gitlab/namespace.rs
deleted file mode 100644
index d4a1e8f4..00000000
--- a/src/gitlab/namespace.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use serde::Deserialize;
-
-#[derive(Debug, Deserialize)]
-pub struct Namespace {
- pub id: u64,
- pub name: String,
- pub path: String,
- pub kind: String,
- pub full_path: String,
- pub parent_id: Option<u64>,
-}
diff --git a/src/gitlab/project.rs b/src/gitlab/project.rs
deleted file mode 100644
index ba88c2e3..00000000
--- a/src/gitlab/project.rs
+++ /dev/null
@@ -1,11 +0,0 @@
-use serde::Deserialize;
-
-use super::Namespace;
-
-#[derive(Debug, Deserialize)]
-pub struct Project {
- pub id: u64,
- pub name: String,
- pub path: String,
- pub namespace: Namespace,
-}
diff --git a/src/lib.rs b/src/lib.rs
deleted file mode 100644
index 3681a859..00000000
--- a/src/lib.rs
+++ /dev/null
@@ -1,6 +0,0 @@
-pub mod authorization;
-pub mod gitlab;
-
-pub use authorization::{
- Authorizer, CedarAuthorizer, CedarEntity, CheckService, EntitiesRepository, Server,
-};