mirror of
https://github.com/sadoyan/aralez.git
synced 2026-04-29 22:38:36 +08:00
Path level authentication
This commit is contained in:
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user