summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJustin Wernick <justin@worthe-it.co.za>2023-03-30 00:03:59 +0200
committerJustin Wernick <justin@worthe-it.co.za>2023-03-30 00:03:59 +0200
commit3c59f9494f6b3cf9c8f9fb6cca0580d053329cf0 (patch)
treed5c80b5cdf79c8e377c085de5bde2af6abbecc7c
parent745cd8ab27480a3cf516a2684db2c4fd7cf2144c (diff)
Update path restrictions to allow shared repos
-rw-r--r--Dockerfile10
-rw-r--r--readme.org4
-rw-r--r--src/git.rs54
-rw-r--r--tests/server_shell.rs51
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<PathBuf, ShackleError> {
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<PathBuf, ShackleError> {
@@ -23,26 +24,45 @@ fn group_git_dir(group: &str) -> Result<PathBuf, ShackleError> {
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<bool, ShackleError> {
- let valid_dir = personal_git_dir()?;
- let relative_path = match path.strip_prefix(&valid_dir) {
+fn is_valid_git_repo_path(path: &PathBuf) -> Result<bool, ShackleError> {
+ 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<String>) -> Result<GitInitResult, ShackleError> {
@@ -80,7 +100,7 @@ pub fn init(repo_name: &str, group: &Option<String>) -> Result<GitInitResult, Sh
}
pub fn upload_pack(upload_pack_args: &GitUploadPackArgs) -> 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();