July 26, 2013

Git + mercurial (hg) restricted shared ssh server.

I had a problem at work, where we needed to restrict vcs user’s access to only allow git and mercurial commands. There was a server that had been designated as the central repository server that everyone wash pushing to and pulling from, but as we added more users it became apparent that we should lock things down a bit to prevent repo tampering since everyone had shell access to the server and write permissions to their repos.

There are several existing solutions for this for git and mercurial individually but nothing that encompassed both of them.  I found a great idea here Code Your Own Multi-User Private Git Server in 5 Minutes and extended it to include hg commands, then locked down the hg commands using the same logic as hg-ssh. I removed the read/write logic since hg handles commands differently than git, and I can control that with file permissions for the users and repos to a certain degree.

You need to setup command restricted authorized_keys files for each user, then point the command to this script.

#!/usr/bin/env ruby
# Example authorized_keys
#
# command="/usr/local/bin/vcs-srv jconway",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAA...examplekey...zzz jconway@jordanconway.com
#
# user is passed from authorized_keys

user = ARGV[0]
command = ENV['SSH_ORIGINAL_COMMAND']
abort "No login, just git and hg commands" unless user and command

# check the supplied command contains a valid git action 

valid_actions = ['git-receive-pack', 'git-upload-pack', 'hg']
action = command.split[0]
abort "git and hg commands only" unless valid_actions.include? action

# user made a valid request so handing over to git-shell unless it's a mercurial request

Kernel.exec 'git', 'shell', '-c', command if action != 'hg'

# Make sure the hg command is safe

abort "quit trying to do fancy hg stuff" unless command.split[0] == 'hg' and command.split[1] == '-R' and command.split[3] == 'serve' and command.split[4] == '--stdio' 

# If we made it this far, execute the command as is for mercurial
Kernel.exec command

Here is an example of the authorized_keys file

command="/usr/local/bin/vcs-srv jconway",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAA...examplekey...zzz jconway@jordanconway.com

I then used puppet to manage authorized_keys files for each user.