Rust implementation of OCI Distribution Spec with granular access control
at main 92 lines 2.6 kB view raw
1use base64::{prelude::BASE64_STANDARD, Engine}; 2use std::sync::Arc; 3 4use crate::metrics; 5use crate::permissions::{has_permission, Action}; 6use crate::response::unauthorized; 7use crate::state::{self, User}; 8use axum::{ 9 body::Body, 10 extract::State, 11 http::{HeaderMap, Response}, 12}; 13 14fn parse_auth_header(headers: &HeaderMap) -> Option<User> { 15 let auth_header = headers.get("authorization")?; 16 let auth_str = auth_header.to_str().ok()?; 17 let auth_decoded_vec = BASE64_STANDARD 18 .decode(auth_str.trim_start_matches("Basic ")) 19 .ok()?; 20 let decoded = String::from_utf8(auth_decoded_vec).ok()?; 21 22 let parts: Vec<&str> = decoded.split(':').collect(); 23 if parts.len() == 2 { 24 Some(User { 25 username: parts[0].to_string(), 26 password: parts[1].to_string(), 27 permissions: vec![], 28 }) 29 } else { 30 None 31 } 32} 33 34/// Authenticate user from headers and return User object 35pub async fn authenticate_user(state: &Arc<state::App>, headers: &HeaderMap) -> Result<User, ()> { 36 let user = parse_auth_header(headers).ok_or(())?; 37 38 let users = state.users.lock().await; 39 for u in users.iter() { 40 if u.username == user.username && u.password == user.password { 41 return Ok(u.clone()); 42 } 43 } 44 45 metrics::AUTH_FAILURES_TOTAL.inc(); 46 Err(()) 47} 48 49/// Check if authenticated user has permission for the action 50pub async fn check_permission( 51 state: &Arc<state::App>, 52 headers: &HeaderMap, 53 repository: &str, 54 tag: Option<&str>, 55 action: Action, 56) -> Result<User, ()> { 57 // First authenticate 58 let user = authenticate_user(state, headers).await?; 59 60 // Then check permission 61 if has_permission(&user, repository, tag, action) { 62 Ok(user) 63 } else { 64 log::warn!( 65 "User {} denied {} access to {}/{}", 66 user.username, 67 action.as_str(), 68 repository, 69 tag.unwrap_or("*") 70 ); 71 metrics::PERMISSION_DENIALS_TOTAL.inc(); 72 Err(()) 73 } 74} 75 76pub(crate) async fn get(State(data): State<Arc<state::App>>, headers: HeaderMap) -> Response<Body> { 77 log::info!("Incoming request headers: {:?}", headers); 78 79 match authenticate_user(&data, &headers).await { 80 Ok(user) => { 81 log::info!("User {} authenticated successfully", user.username); 82 Response::builder() 83 .status(200) 84 .body(Body::from("200 OK")) 85 .unwrap() 86 } 87 Err(_) => { 88 log::warn!("Authentication failed"); 89 unauthorized(&data.args.host) 90 } 91 } 92}