Path level authentication

This commit is contained in:
Ara Sadoyan
2026-03-03 19:35:16 +01:00
parent 3afa2f209f
commit 9d986f9a28
7 changed files with 77 additions and 15 deletions

View File

@@ -57,22 +57,23 @@ fn validate(auth: &dyn AuthValidator, session: &Session) -> bool {
auth.validate(session) auth.validate(session)
} }
pub fn authenticate(c: &[Arc<str>], session: &Session) -> bool { // pub fn authenticate(c: &[Arc<str>], session: &Session) -> bool {
match &*c[0] { pub fn authenticate(auth_type: &Arc<str>, credentials: &Arc<str>, session: &Session) -> bool {
match &*auth_type.clone() {
"basic" => { "basic" => {
let auth = BasicAuth(&*c[1]); let auth = BasicAuth(&*credentials.clone());
validate(&auth, session) validate(&auth, session)
} }
"apikey" => { "apikey" => {
let auth = ApiKeyAuth(&*c[1]); let auth = ApiKeyAuth(&*credentials.clone());
validate(&auth, session) validate(&auth, session)
} }
"jwt" => { "jwt" => {
let auth = JwtAuth(&*c[1]); let auth = JwtAuth(&*credentials.clone());
validate(&auth, session) validate(&auth, session)
} }
_ => { _ => {
println!("Unsupported authentication mechanism : {}", c[0]); println!("Unsupported authentication mechanism : {}", auth_type);
false false
} }
} }

View File

@@ -70,6 +70,7 @@ async fn build_upstreams(fullist: &UpstreamsDashMap, method: &str, client: &Clie
to_https: upstream.to_https, to_https: upstream.to_https,
rate_limit: upstream.rate_limit, rate_limit: upstream.rate_limit,
healthcheck: upstream.healthcheck, healthcheck: upstream.healthcheck,
authorization: upstream.authorization.clone(),
}; };
if scheme.healthcheck.unwrap_or(true) { if scheme.healthcheck.unwrap_or(true) {

View File

@@ -35,6 +35,7 @@ pub async fn for_consul(url: String, token: Option<String>, conf: &ServiceMappin
to_https: conf.to_https.unwrap_or(false), to_https: conf.to_https.unwrap_or(false),
rate_limit: conf.rate_limit, rate_limit: conf.rate_limit,
healthcheck: None, healthcheck: None,
authorization: None,
}); });
inner_vec.push(to_add); inner_vec.push(to_add);
} }
@@ -68,6 +69,7 @@ pub async fn for_kuber(url: &str, token: &str, conf: &ServiceMapping) -> Option<
to_https: conf.to_https.unwrap_or(false), to_https: conf.to_https.unwrap_or(false),
rate_limit: conf.rate_limit, rate_limit: conf.rate_limit,
healthcheck: None, healthcheck: None,
authorization: None,
}); });
inner_vec.push(to_add); inner_vec.push(to_add);
} }

View File

@@ -34,7 +34,10 @@ pub async fn load_configuration(d: &str, kind: &str) -> (Option<Configuration>,
}; };
let parsed: Config = match serde_yaml::from_str(&yaml_data) { let parsed: Config = match serde_yaml::from_str(&yaml_data) {
Ok(cfg) => cfg, Ok(cfg) => {
// println!("{:#?}", cfg);
cfg
}
Err(e) => { Err(e) => {
error!("Failed to parse upstreams file: {}", e); error!("Failed to parse upstreams file: {}", e);
return (None, e.to_string()); return (None, e.to_string());
@@ -97,6 +100,7 @@ async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config)
info!("Applied Global Rate Limit : {} request per second", rate); info!("Applied Global Rate Limit : {} request per second", rate);
} }
// ======================================================================================== //
if let Some(auth) = &parsed.authorization { if let Some(auth) = &parsed.authorization {
let name = auth.get("type").unwrap_or(&"".to_string()).to_string(); let name = auth.get("type").unwrap_or(&"".to_string()).to_string();
let creds = auth.get("creds").unwrap_or(&"".to_string()).to_string(); let creds = auth.get("creds").unwrap_or(&"".to_string()).to_string();
@@ -107,6 +111,7 @@ async fn populate_headers_and_auth(config: &mut Configuration, parsed: &Config)
} else { } else {
config.extraparams.authentication = DashMap::new(); config.extraparams.authentication = DashMap::new();
} }
// ======================================================================================== //
} }
async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) { async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
@@ -126,9 +131,17 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
build_headers(&path_config.server_headers, config, &mut sl); build_headers(&path_config.server_headers, config, &mut sl);
client_header_list.insert(Arc::from(path.as_str()), hl); client_header_list.insert(Arc::from(path.as_str()), hl);
server_header_list.insert(Arc::from(path.as_str()), sl); server_header_list.insert(Arc::from(path.as_str()), sl);
let mut server_list = Vec::new(); let mut server_list = Vec::new();
for server in &path_config.servers { for server in &path_config.servers {
let mut path_auth: Option<Arc<InnerAuth>> = None;
if let Some(pa) = &path_config.authorization {
let y: InnerAuth = InnerAuth {
auth_type: Arc::from(pa.auth_type.clone()),
auth_cred: Arc::from(pa.auth_cred.clone()),
};
path_auth = Some(Arc::from(y));
}
if let Some((ip, port_str)) = server.split_once(':') { if let Some((ip, port_str)) = server.split_once(':') {
if let Ok(port) = port_str.parse::<u16>() { if let Ok(port) = port_str.parse::<u16>() {
server_list.push(Arc::from(InnerMap { server_list.push(Arc::from(InnerMap {
@@ -139,6 +152,7 @@ async fn populate_file_upstreams(config: &mut Configuration, parsed: &Config) {
to_https: path_config.to_https.unwrap_or(false), to_https: path_config.to_https.unwrap_or(false),
rate_limit: path_config.rate_limit, rate_limit: path_config.rate_limit,
healthcheck: path_config.healthcheck, healthcheck: path_config.healthcheck,
authorization: path_auth,
})); }));
} }
} }

View File

@@ -14,6 +14,7 @@ pub struct Extraparams {
pub to_https: Option<bool>, pub to_https: Option<bool>,
pub sticky_sessions: bool, pub sticky_sessions: bool,
pub authentication: DashMap<Arc<str>, Vec<Arc<str>>>, pub authentication: DashMap<Arc<str>, Vec<Arc<str>>>,
// pub authentication: InnerAuth,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
} }
@@ -70,7 +71,13 @@ pub struct HostConfig {
pub paths: HashMap<String, PathConfig>, pub paths: HashMap<String, PathConfig>,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
} }
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct Auth {
#[serde(rename = "type")]
pub auth_type: String,
#[serde(rename = "creds")]
pub auth_cred: String,
}
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
pub struct PathConfig { pub struct PathConfig {
pub servers: Vec<String>, pub servers: Vec<String>,
@@ -80,6 +87,8 @@ pub struct PathConfig {
pub server_headers: Option<Vec<String>>, pub server_headers: Option<Vec<String>>,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub healthcheck: Option<bool>, pub healthcheck: Option<bool>,
// pub authorization: Option<HashMap<String, String>>,
pub authorization: Option<Auth>,
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Configuration { pub struct Configuration {
@@ -116,7 +125,13 @@ pub struct AppConfig {
pub rungroup: Option<String>, pub rungroup: Option<String>,
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct InnerAuth {
pub auth_type: Arc<str>,
pub auth_cred: Arc<str>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InnerMap { pub struct InnerMap {
pub address: Arc<str>, pub address: Arc<str>,
pub port: u16, pub port: u16,
@@ -125,6 +140,8 @@ pub struct InnerMap {
pub to_https: bool, pub to_https: bool,
pub rate_limit: Option<isize>, pub rate_limit: Option<isize>,
pub healthcheck: Option<bool>, pub healthcheck: Option<bool>,
// pub authorization: Option<DashMap<Arc<str>, Arc<str>>>,
pub authorization: Option<Arc<InnerAuth>>,
} }
#[allow(dead_code)] #[allow(dead_code)]
@@ -139,6 +156,7 @@ impl InnerMap {
to_https: Default::default(), to_https: Default::default(),
rate_limit: Default::default(), rate_limit: Default::default(),
healthcheck: Default::default(), healthcheck: Default::default(),
authorization: Default::default(),
} }
} }
} }

View File

@@ -124,11 +124,23 @@ pub fn compare_dashmaps(map1: &UpstreamsDashMap, map2: &UpstreamsDashMap) -> boo
return false; // Path exists in map1 but not in map2 return false; // Path exists in map1 but not in map2
}; };
let (vec2, _counter2) = entry2.value(); let (vec2, _counter2) = entry2.value();
let set1: HashSet<_> = vec1.iter().collect();
let set2: HashSet<_> = vec2.iter().collect(); if vec1.len() != vec2.len() {
if set1 != set2 {
return false; return false;
} }
for item in vec1.iter() {
let count1 = vec1.iter().filter(|&x| x == item).count();
let count2 = vec2.iter().filter(|&x| x == item).count();
if count1 != count2 {
return false;
}
}
// let set1: HashSet<_> = vec1.iter().collect();
// let set2: HashSet<_> = vec2.iter().collect();
// if set1 != set2 {
// return false;
// }
} }
} }
true true
@@ -168,6 +180,7 @@ pub fn clone_idmap_into(original: &UpstreamsDashMap, cloned: &UpstreamsIdMap) {
to_https: false, to_https: false,
rate_limit: None, rate_limit: None,
healthcheck: None, healthcheck: None,
authorization: None,
}; };
cloned.insert(id, Arc::from(to_add)); cloned.insert(id, Arc::from(to_add));

View File

@@ -70,17 +70,20 @@ impl ProxyHttp for LB {
} }
async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> { async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> {
let ep = _ctx.extraparams.as_ref(); let ep = _ctx.extraparams.as_ref();
// ======================================================================================== //
println!("{:?}", ep);
if let Some(auth) = ep.authentication.get("authorization") { if let Some(auth) = ep.authentication.get("authorization") {
let authenticated = authenticate(auth.value(), &session); let authenticated = authenticate(&auth.value()[0], &auth.value()[1], &session);
if !authenticated { if !authenticated {
let _ = session.respond_error(401).await; let _ = session.respond_error(401).await;
warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path()); warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path());
return Ok(true); return Ok(true);
} }
}; };
// ======================================================================================== //
let hostname = return_header_host_from_upstream(session, &self.ump_upst); let hostname = return_header_host_from_upstream(session, &self.ump_upst);
_ctx.hostname = hostname; _ctx.hostname = hostname;
let mut backend_id = None; let mut backend_id = None;
@@ -101,9 +104,19 @@ impl ProxyHttp for LB {
None => return Ok(false), None => return Ok(false),
Some(host) => { Some(host) => {
let optioninnermap = self.get_host(host, session.req_header().uri.path(), backend_id); let optioninnermap = self.get_host(host, session.req_header().uri.path(), backend_id);
match optioninnermap { match optioninnermap {
None => return Ok(false), None => return Ok(false),
Some(ref innermap) => { Some(ref innermap) => {
if let Some(auth) = &innermap.authorization {
let authenticated = authenticate(&auth.auth_type, &auth.auth_cred, &session);
if !authenticated {
let _ = session.respond_error(401).await;
warn!("Forbidden: {:?}, {}", session.client_addr(), session.req_header().uri.path());
return Ok(true);
}
}
if let Some(rate) = innermap.rate_limit.or(ep.rate_limit) { if let Some(rate) = innermap.rate_limit.or(ep.rate_limit) {
let rate_key = session.client_addr().and_then(|addr| addr.as_inet()).map(|inet| inet.ip()); let rate_key = session.client_addr().and_then(|addr| addr.as_inet()).map(|inet| inet.ip());
let curr_window_requests = RATE_LIMITER.observe(&rate_key, 1); let curr_window_requests = RATE_LIMITER.observe(&rate_key, 1);