简体   繁体   中英

How to restrict git SSH access based on ssh key given

I have a private git server with one user git and ssh key authentication. Currently, I am the only one using it, but I want to add more people and I want everyone to use user git to connect to the server and perform git clone , git push , etc. If all the repositories are "public", then I know how to solve this. But for example I want to have a private repository, that I will still clone via git clone git@server:repo with my SSH key, however I do not want other users to be able to clone it using their SSH keys.

I checked git-scm documentation on setting up the server (which was helpful for public repositories), and this post, however this post seems to only solve the problem for only private repositories.

TLDR: When you clone a repository on GitHub, you say git clone git@github.com:user/repo and your git username and ssh key are being sent over. Now, depending on whether you have the permissions to this repository, you can clone it, otherwise not. So basically, everyone is using git user on the surface, but under the hood some authorisation is taking place. How does GitHub handle this for example?

To implement a similar feature, you could rely on a dedicated tool such as gitolite .

Otherwise, you could install a full-blown GitLab server which would also provide fine-grained access control to the repositories, with "Git URLs" such as git@your-gitlab-domain.com:user/repo.git

(BTW it seems GitLab formerly relied on gitolite , before version 5.0.0 )

gitolite seems to be exactly what you are looking for managing access to repositories/branches based on the ssh key.

But if you want to build something like this from scratch you need to look into the options in the authorized_keys file, especially command and environment . With these you can force a specific command/script or add/overwrite environment variables based on which ssh key was used.

For example you could write a script that reads the allowed repositories for the user as its arguments and force it to be run for selected ssh keys:

# file ~/.ssh/authorized_keys
command="/home/git/bin/git-only-shell /home/git/repos/repo1" ssh-rsa AAAAB2...
command="/home/git/bin/git-only-shell /home/git/repos/repo1 /home/git/repos/repo2" ssh-rsa AAAAB3...

The script can now read the requested repo from $SSH_ORIGINAL_COMMAND and check that it is contained in the passed list. A complete - but crude - example implementation of this script git-only-shell could be like this:

#!/bin/bash

# verify that $SSH_ORIGINAL_COMMAND starts with git-upload-pack, git-upload-archive or
# git-receive-pack, followed by a space
if ! [[ "$SSH_ORIGINAL_COMMAND" == git-upload-pack\ * || "$SSH_ORIGINAL_COMMAND" == git-upload-archive\ * || "$SSH_ORIGINAL_COMMAND" == git-receive-pack\ * ]]; then
    echo "unsupported command" >&2
    exit 1
fi

# remove first word (git command)
ARGUMENTS="${SSH_ORIGINAL_COMMAND#git-* }"

# use eval to un-quote repo path (it is passed in single-quotes)
REPO_PATH="$(eval "echo $ARGUMENTS")"

# allowed repos are passed as arguments to this script
ALLOWED_REPOS="$@"

# check if repo was whitelisted
IS_ALLOWED=false
for repo in $ALLOWED_REPOS; do
    if [[ "$REPO_PATH" == "$repo" ]]; then
        IS_ALLOWED=true
    fi
done

if [[ $IS_ALLOWED == "false" ]]; then
    echo "access to this repo not allowed" >&2
    exit 1
fi

# execute the original command
eval "$SSH_ORIGINAL_COMMAND"

Tools like gitolite were too complicated and unpleasant to use or did not offer the desired features. I ended up with the following solution:

  • A PostgreSQL database with tables user and repo , and many-to-many table permissions .
  • A program written in C that checks whether a user has read/write permissions to access requested repository which is extracted from $SSH_ORIGINAL_COMMAND .
  • In .ssh/authorized_keys each key has its own command which calls a C program with username associated with that key and a repository extracted from SSH command when that key is used for git operations.

So, when a user adds an SSH key to his account, a line in .ssh/authorized_keys is added, eg

command="/home/git/check_perms username \"${SSH_ORIGINAL_COMMAND}\"" no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa ...

Then every time a git operation is performed using this key, the command is run and if user does not have sufficient permissions, git operation is terminated.

I solved this by using git-shell and a little bit of bash. The general idea is to check whether the key'd user is supposed to access a given repository, and if so, running their command with git-shell .

The shell script is as follows;

#!/bin/bash

SSH_ORIGINAL_COMMAND=${SSH_ORIGINAL_COMMAND#git-}
a=( ${SSH_ORIGINAL_COMMAND//\'/} ) # strip single qoutes [^1]

cmd=${a[0]} # git * command
path=${a[-1]} # targeted path
unset a[0] a[-1]

# optional arguments
if (( ${#a[@]} )); then
    args="#{a[*]} "
fi

# check if the target path is whitelisted
for a_path in $@; do
    f_path="${a_path}${path#@(a_path/|../)}/" # f(ull)_path
    if [ -d "$f_path" ]; then
        /path/to/git-shell -c "git $cmd $args'$f_path'"
    fi
done

We then modify the git user's .ssh/authorized_keys ;

command="/path/to/script /foo/ /bar/ /biz/" ssh-...

Attacks

Arbitrary command execution is prevented by exlusively using git-shell , so we only have to worry about access to unauthorised repositories -- this is addressed in two ways;

  • Removing instances of ../ when composing f_path .
  • Scoping the targeted path when checking for directory existence.

eg;

 - foo     [Authorised]
 |_ biz
 - bar
 |_ boo

====
- `git clone git@server.dom:foo/../bar/boo` fails
- `git clone git@server.dom:bar` fails
- `git clone git@server.dom:foo/biz` passes

References

[1]: The way that a is formed here may not play nice with globing; see this SO answer for more context.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM