diff --git a/josh-proxy/src/acl.rs b/josh-proxy/src/acl.rs new file mode 100644 index 00000000..81d6c242 --- /dev/null +++ b/josh-proxy/src/acl.rs @@ -0,0 +1,84 @@ +use regex::Regex; +use serde::Deserialize; +use std::{collections::HashMap, result::Result}; +use toml; + +#[derive(Debug, Clone)] +pub struct Validator { + // note: repo user paths + rules: Option>>>, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum Error { + Toml(toml::de::Error), + Regex(regex::Error), +} + +impl Validator { + pub fn new() -> Validator { + Validator { rules: None } + } + + pub fn from_toml(src: &str) -> std::result::Result { + // parse Rule from text + let raw: Doc = toml::from_str(&src).map_err(Error::Toml)?; + + // map Rule into HashMap>> + let dst = |x: Vec| { + x.into_iter() + .map(|v| Regex::new(v.as_str()).map_err(Error::Regex)) + .collect::, Error>>() + }; + let dst = |x: Option>| { + x.unwrap_or(Vec::new()) + .into_iter() + .map(|m| (m.user, dst(m.path))) + .map(|(s, r)| r.map(|v| (s, v))) + .collect::>, Error>>() + }; + let dst = raw + .repo + .unwrap_or(Vec::new()) + .into_iter() + .map(|r| (r.name, dst(r.rule))) + .map(|(s, r)| r.map(|v| (s, v))) + .collect::, Error>>()?; + + // return value + Ok(Validator { rules: Some(dst) }) + } + + pub fn is_accessible(self: &Validator, user: &str, repo: &str, path: &str) -> bool { + match &self.rules { + None => true, + Some(rule) => { + // e.g. if "we" want to access "http://localhost:8080/a/b.git:/c/d.git" + // then user = we, repo = a/b, path = c/d + let repo = repo.trim_start_matches("/"); + let repo = repo.trim_end_matches(".git"); + let path = path.trim_start_matches(":/"); + rule.get(repo) + .and_then(|r| r.get(user).map(|x| x.iter().any(|r| r.is_match(path)))) + .unwrap_or(false) + } + } + } +} + +#[derive(Debug, Deserialize)] +struct Doc { + pub repo: Option>, +} + +#[derive(Debug, Deserialize)] +struct Repo { + pub name: String, + pub rule: Option>, +} + +#[derive(Debug, Deserialize)] +struct Match { + pub user: String, + pub path: Vec, +} diff --git a/josh-proxy/src/bin/josh-proxy.rs b/josh-proxy/src/bin/josh-proxy.rs index bf8174f0..0ca1fe97 100644 --- a/josh-proxy/src/bin/josh-proxy.rs +++ b/josh-proxy/src/bin/josh-proxy.rs @@ -42,6 +42,7 @@ struct JoshProxyService { fetch_timers: Arc>, fetch_permits: Arc, filter_permits: Arc, + validator: josh_proxy::acl::Validator, poll: Polls, } @@ -398,6 +399,18 @@ async fn call_service( return Ok(builder.body(hyper::Body::empty())?); } + // e.g. "http://localhost:port/a/b.git:/c/d.git" will become "a/b.git" ":/c/d" + if !serv + .validator + .is_accessible(&username, &parsed_url.upstream_repo, &parsed_url.filter) + { + tracing::trace!("acl-validator"); + let builder = Response::builder() + .header("WWW-Authenticate", "Basic realm=User Visible Realm") + .status(hyper::StatusCode::UNAUTHORIZED); + return Ok(builder.body(hyper::Body::empty())?); + } + match fetch_upstream( serv.clone(), parsed_url.upstream_repo.to_owned(), @@ -605,6 +618,16 @@ async fn run_proxy() -> josh::JoshResult { josh_proxy::create_repo(&local)?; josh::cache::load(&local)?; + let validator = match ARGS.value_of("acl") { + None => josh_proxy::acl::Validator::new(), + Some(path) => { + let text = std::fs::read_to_string(path) + .map_err(|_| josh::josh_error("failed to read acl file"))?; + josh_proxy::acl::Validator::from_toml(text.as_str()) + .map_err(|_| josh::josh_error("errors in acl file"))? + } + }; + let proxy_service = Arc::new(JoshProxyService { port, repo_path: local.to_owned(), @@ -615,6 +638,7 @@ async fn run_proxy() -> josh::JoshResult { ARGS.value_of("n").unwrap_or("1").parse()?, )), filter_permits: Arc::new(tokio::sync::Semaphore::new(10)), + validator: validator, }); let ps = proxy_service.clone(); @@ -769,6 +793,12 @@ fn parse_args() -> clap::ArgMatches<'static> { .help("Duration between forced cache refresh") .takes_value(true), ) + .arg( + clap::Arg::with_name("acl") + .long("acl") + .help("Specify a config file to control user access") + .takes_value(true), + ) .get_matches_from(args) } diff --git a/josh-proxy/src/lib.rs b/josh-proxy/src/lib.rs index 3f4c13ab..830ac8f1 100644 --- a/josh-proxy/src/lib.rs +++ b/josh-proxy/src/lib.rs @@ -1,3 +1,4 @@ +pub mod acl; pub mod auth; pub mod juniper_hyper; diff --git a/run-josh.sh b/run-josh.sh index cbe54f1d..b60f2043 100644 --- a/run-josh.sh +++ b/run-josh.sh @@ -1,3 +1,4 @@ #!/bin/bash +[ -n "${JOSH_ACL}" ] && acl_arg="--acl ${JOSH_ACL}" cd /josh/ -RUST_BACKTRACE=1 josh-proxy --gc --local=/data/git/ --remote="${JOSH_REMOTE}" ${JOSH_EXTRA_OPTS} +RUST_BACKTRACE=1 josh-proxy --gc --local=/data/git/ --remote="${JOSH_REMOTE}" ${acl_arg} ${JOSH_EXTRA_OPTS}