Git: one git user to control the repos of multiple

2019-09-06 03:29发布

问题:

I have a Raspberry Pi, which I use as a Git server. There are multiple physical users accessing it and currently each of these users has their own login on the server. In other words, the users John and Doe can login to the server with SSH by running ssh john@server.com or doe@server.com.

The physical users have private Git repositories, which no other user is able to access. E.g. John's repos are located in /home/john/repos and Doe's repos are located in /home/doe/repos on the server.

What I want is only one user called git, which controls all users' repos. E.g, instead of John having the remote john@server.com:repos/project.git, he would use git@server.com:john/project.git. Similarly, Doe would push to git@server.com:doe/some_other_project.git

How can this be achieved while making sure that people cannot access each other's repos? The server is accessed via SSH.

回答1:

The easiest solution is to set up someting like gitlab, which provides a web interface, various sorts of access control, and all sorts of other bells and whistles.

If you really want to roll your own:

Start with directions in the Git book for setting up your server to permit access to a shared user over ssh.

Those instructions will get you a shared git account to which everyone has access, and would let anyone push to/pull from any repository. We can implement a simple authorization layer that will restrict users to repositories in particular directories.

Start with a small wrapper script:

#!/bin/sh

repo_prefix=$1

eval set -- $SSH_ORIGINAL_COMMAND
case "$1" in
    (git-receive-pack|git-upload-pack|git-upload-archive)

        # prevent attempts at using dir/../path to escape
        # repository directory
        case "$2" in
            (*..*) echo "Invalid repository name." >&2
                   exit 1
                   ;;
        esac

        repo_path="$repo_prefix/$2"
        eval exec $1 $repo_path
        ;;

    (*) echo "Unsupported command" >&2
        exit 1
        ;;
esac

This will prepend a prefix path, provided as a command line argument, to all repository paths. Now, we need to arrange for this wrapper to intercept git operations.

To use this, you will need to modify the public keys you add to the git user's authorized_keys file. If you followed the instructions in the Git book, the authorized_keys file will have one or more public keys in it, like this:

ssh-rsa AAAA...== some comment

For each public key, you will need to add a configuration option that will cause the wrapper script to be called in place of the original command. From the sshd man page:

Each line of the file contains one key (empty lines and lines starting with a ‘#’ are ignored as comments). Protocol 1 public keys consist of the following space-separated fields: options, bits, exponent, modulus, comment. Protocol 2 public key consist of: options, keytype, base64-encoded key, comment. The options field is optional...

And slightly further down:

The following option specifications are supported (note that option keywords are case-insensitive): [...]

command="command"

Specifies that the command is executed whenever this key is used for authentication. The command supplied by the user (if any) is ignored...The command originally supplied by the client is available in the SSH_ORIGINAL_COMMAND environment variable.

With that in mind, we modify our authorized_keys file to look something like this:

command="/usr/bin/git-wrapper.sh username" ssh-rsa AAAA...===

This means that when someone connects with the corresponding private key, sshd will run git-wrapper.sh username, causing our git-wrapper.sh script to prepend repository paths with the string username, ensuring that git will only see repositories in the given directory. More specifically, when you run:

git push origin master

And assuming that origin points to project.git on the git server, thengit will attempt to run on the remote server the command:

git-receive-pack project.git

Our wrapper script will intercept that, and transform it into:

git-receive-pack $1/project.git

So for example, if our git git user home directory has no repositories:

git$ ls

And our authorized_keys file looks like this:

git$ cat .ssh/authorized_keys
command="/usr/bin/git-wrapper.sh alice" ssh-rsa ... alice@example.com
command="/usr/bin/git-wrapper.sh bob" ssh-rsa ... bob@example.com

Then if alice does this:

alice$ git remote add origin git@mygitserver:project.git
git push origin master

She will see:

fatal: 'alice/project.git' does not appear to be a git repository
fatal: Could not read from remote repository.

If we create the target repository:

git$ mkdir alice
git$ git init --bare alice/project.git

Then she can push:

alice$ git push origin master
[...]
To git@mygitserver:project.git
 * [new branch]      master -> master

But if bob were try to clone that repository:

bob$ git clone git@mygitserver:project.git

It would fail:

fatal: 'bob/project.git' does not appear to be a git repository
fatal: Could not read from remote repository.

Even if he tried something sneaky:

bob$ git clone git@mygitserver:../alice/project.git
Invalid repository name
fatal: Could not read from remote repository.

And that, in a somewhat verbose nutshell, is how you can access authorization for your git repository server. Note that this was all done for demonstration purposes only; you would want a substantially more robust script in a production environment.



标签: git ssh