From 3c59f9494f6b3cf9c8f9fb6cca0580d053329cf0 Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Thu, 30 Mar 2023 00:03:59 +0200 Subject: Update path restrictions to allow shared repos --- Dockerfile | 10 ++++++++-- readme.org | 4 ++-- src/git.rs | 54 +++++++++++++++++++++++++++++++++++---------------- tests/server_shell.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 95 insertions(+), 24 deletions(-) diff --git a/Dockerfile b/Dockerfile index a743200..96c605f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,8 +12,14 @@ RUN sed -i /etc/ssh/sshd_config \ -e 's/#PermitEmptyPasswords.*/PermitEmptyPasswords yes/' RUN adduser --shell /usr/bin/shackle shukkie && passwd -d shukkie -RUN mkdir -p /home/shukkie/git/shukkie && chown -R shukkie:shukkie /home/shukkie/git -RUN git init --bare /home/shukkie/disallowed.git && chown -R shukkie:shukkie /home/shukkie/disallowed.git +RUN mkdir -p /home/shukkie/git/shukkie && \ + chown -R shukkie:shukkie /home/shukkie/git +RUN groupadd shukkies-company --users shukkie && \ + mkdir -p /home/shukkie/git/shukkies-company && \ + chmod 2770 /home/shukkie/git/shukkies-company && \ + chown -R root:shukkies-company /home/shukkie/git/shukkies-company +RUN git init --bare /home/shukkie/disallowed.git && \ + chown -R shukkie:shukkie /home/shukkie/disallowed.git COPY . /opt/shackle ARG SHELL=target/debug/shackle diff --git a/readme.org b/readme.org index 31767bf..3bb1ecb 100644 --- a/readme.org +++ b/readme.org @@ -30,11 +30,11 @@ Pijul. - [X] restrict repos to only acceptable paths - [X] clone / pull - [X] push -- [-] git init of shared repos +- [X] git init of shared repos - [X] create the shared repo in the right place - [X] use the right file permissions and config - [X] don't allow this to be a group the user isn't in - - [ ] allow pull and push from shared repos + - [X] allow pull and push from shared repos - [ ] listing of repos - [ ] set repo descriptions - [ ] set the main branch of a repo diff --git a/src/git.rs b/src/git.rs index dcdb71b..7337a6a 100644 --- a/src/git.rs +++ b/src/git.rs @@ -3,19 +3,20 @@ use crate::{ ShackleError, }; use git2::{Repository, RepositoryInitMode, RepositoryInitOptions}; -use std::{ - path::{Path, PathBuf}, - process::Command, -}; +use std::{path::PathBuf, process::Command}; use user_info::{get_user_groups, get_username}; pub struct GitInitResult { pub path: PathBuf, } +fn git_dir_prefix() -> PathBuf { + PathBuf::from("git") +} + fn personal_git_dir() -> Result { let username = get_username().ok_or(ShackleError::UserReadError)?; - Ok(Path::new("git").join(username)) + Ok(git_dir_prefix().join(username)) } fn group_git_dir(group: &str) -> Result { @@ -23,26 +24,45 @@ fn group_git_dir(group: &str) -> Result { if !groups.iter().any(|g| g == group) { Err(ShackleError::InvalidGroup) } else { - Ok(Path::new("git").join(group)) + Ok(git_dir_prefix().join(group)) } } -fn is_valid_personal_git_repo(path: &PathBuf) -> Result { - let valid_dir = personal_git_dir()?; - let relative_path = match path.strip_prefix(&valid_dir) { +fn is_valid_git_repo_path(path: &PathBuf) -> Result { + let prefix = git_dir_prefix(); + let relative_path = match path.strip_prefix(&prefix) { Ok(relative_path) => relative_path, Err(_) => { return Ok(false); } }; - if relative_path.parent() != Some(Path::new("")) - || relative_path.extension().map(|ext| ext == "git") != Some(true) - { - return Ok(false); + let mut it = relative_path.iter(); + let group = it.next(); + let repo_name = it.next(); + let end = it.next(); + + match (group, repo_name, end) { + (_, _, Some(_)) | (None, _, _) | (_, None, _) => Ok(false), + (Some(group_name), Some(_repo_name), _) => { + if relative_path.extension().map(|ext| ext == "git") != Some(true) { + Ok(false) + } else { + let group_name = group_name.to_string_lossy(); + + let user_name = get_username(); + let is_valid_personal_repo_path = user_name + .map(|user_name| user_name == group_name) + .unwrap_or(false); + + let user_groups = get_user_groups(); + let is_valid_shared_repo_path = + user_groups.iter().any(|group| group.as_ref() == group_name); + + Ok(is_valid_personal_repo_path || is_valid_shared_repo_path) + } + } } - - Ok(true) } pub fn init(repo_name: &str, group: &Option) -> Result { @@ -80,7 +100,7 @@ pub fn init(repo_name: &str, group: &Option) -> Result Result<(), ShackleError> { - if !is_valid_personal_git_repo(&upload_pack_args.directory)? { + if !is_valid_git_repo_path(&upload_pack_args.directory)? { return Err(ShackleError::InvalidDirectory); } @@ -107,7 +127,7 @@ pub fn upload_pack(upload_pack_args: &GitUploadPackArgs) -> Result<(), ShackleEr } pub fn receive_pack(receive_pack_args: &GitReceivePackArgs) -> Result<(), ShackleError> { - if !is_valid_personal_git_repo(&receive_pack_args.directory)? { + if !is_valid_git_repo_path(&receive_pack_args.directory)? { return Err(ShackleError::InvalidDirectory); } diff --git a/tests/server_shell.rs b/tests/server_shell.rs index a22b732..d1453b1 100644 --- a/tests/server_shell.rs +++ b/tests/server_shell.rs @@ -140,6 +140,23 @@ fn expect_prompt(p: &mut PtySession) -> Result<()> { fn make_new_repo(c: &TestContext, repo_name: &str) -> Result<()> { let mut p = connect_to_ssh_server_interactively(&c)?; p.send_line(&format!("git-init {}", repo_name))?; + p.exp_string(&format!( + "Successfully created \"git/shukkie/{}.git\"", + repo_name + ))?; + expect_prompt(&mut p)?; + p.send_line("exit")?; + p.exp_eof()?; + Ok(()) +} + +fn make_new_shared_repo(c: &TestContext, group: &str, repo_name: &str) -> Result<()> { + let mut p = connect_to_ssh_server_interactively(&c)?; + p.send_line(&format!("git-init --group {} {}", group, repo_name))?; + p.exp_string(&format!( + "Successfully created \"git/{}/{}.git\"", + group, repo_name + ))?; expect_prompt(&mut p)?; p.send_line("exit")?; p.exp_eof()?; @@ -165,10 +182,14 @@ fn clone_git_repo(c: &TestContext, path: &str) -> Assert { .assert() } -fn clone_git_repo_relative_path(c: &TestContext, repo_name: &str) -> Assert { +fn clone_git_repo_relative_personal_path(c: &TestContext, repo_name: &str) -> Assert { clone_git_repo(c, &format!("/~/git/shukkie/{}.git", repo_name)) } +fn clone_git_repo_relative_shared_path(c: &TestContext, group: &str, repo_name: &str) -> Assert { + clone_git_repo(c, &format!("/~/git/{}/{}.git", group, repo_name)) +} + fn push_git_repo(c: &TestContext, repo_name: &str) -> Assert { let repo_dir = c.workdir.as_ref().join(repo_name); Command::new("git") @@ -204,7 +225,7 @@ fn git_clone_works_with_an_empty_repo() -> Result<()> { let c = spawn_ssh_server()?; let repo_name = "my-new-clonable-repo"; make_new_repo(&c, repo_name)?; - clone_git_repo_relative_path(&c, repo_name).success(); + clone_git_repo_relative_personal_path(&c, repo_name).success(); Ok(()) } @@ -214,7 +235,31 @@ fn git_push_works() -> Result<()> { let c = spawn_ssh_server()?; let repo_name = "my-new-pushable-repo"; make_new_repo(&c, repo_name)?; - clone_git_repo_relative_path(&c, repo_name).success(); + clone_git_repo_relative_personal_path(&c, repo_name).success(); + commit_dummy_content(&c, repo_name)?; + push_git_repo(&c, repo_name).success(); + + Ok(()) +} + +#[test] +fn git_clone_works_with_an_empty_shared_repo() -> Result<()> { + let c = spawn_ssh_server()?; + let repo_name = "my-new-clonable-repo"; + let group = "shukkies-company"; + make_new_shared_repo(&c, group, repo_name)?; + clone_git_repo_relative_shared_path(&c, group, repo_name).success(); + + Ok(()) +} + +#[test] +fn git_push_works_with_shared_repo() -> Result<()> { + let c = spawn_ssh_server()?; + let repo_name = "my-new-pushable-repo"; + let group = "shukkies-company"; + make_new_shared_repo(&c, group, repo_name)?; + clone_git_repo_relative_shared_path(&c, group, repo_name).success(); commit_dummy_content(&c, repo_name)?; push_git_repo(&c, repo_name).success(); -- cgit v1.2.3