GitHandler Build Status

Accept and handle git repository requests over SSH

Installation

Install using rubygems:

gem install git_handler

Or using latest source code:

git clone git://github.com/sosedoff/git-handler.git
cd git-handler
bundle install
rake install

Usage

If you already have an operation system configured, make sure you have git user in your system. In order to use git_handler you'll need to generate a customized SSH public key and add it to ~/.ssh/authorized_keys on server. Generation should be something that needs to be implemented in your application or script, there is functionality already built for that:

require 'git_handler/public_key'

# Load your current pub key
content = File.read(File.expand_path('~/.ssh/id_rsa.pub'))

# Create a key
key = GitHandler::PublicKey.new(content)

Now, to convert loaded key into a system key just run:

key.to_system_key('/usr/bin/git_proxy')
# => command="/usr/bin/git_proxy",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNjN3ZUOoosWeuJ7KczE5FAOzwZ+Z51KSQvqTCb7ccBi4u+pPYcGEYr2t0cx/BUcx/ZGE8ih+zxN1qM8KmM0uluuy54itHsKFdAwoibkbG22fQc2DY0RmktXXB/w6LxmFuQrmz0fkcbkE39pm5k6Nw6mqks5HjM7aDXRdwM8fSrq0PjfUNiESIrIAeEMGhtZFaj+WZVMfXaIlgzxZsAUpUULhN4j069v8VgxWyyOUT+gwcQB8lVc0BVYhptlFaJBtwhfWvOAviSuK7Cpjh60NdkZ3R2QYeh6wb6fF+KGCkM4iED4PZ1Ep8fRzrbCHky4VHSOyOvg9qKcgP1h+e+diD 

SSH public key is now ready for usage on server side. Drop it into ~/home/git/.ssh/authorized_keys file if your user is git. The whole purpose of key modifications is that we're restricting SSH to a specific command or script on server, which gives us ability to control permissions and other restrictions.

Control script

In the example above as you can see we specify /usr/bin/git_proxy to be executed once SSH connection is being established. GitHandler provides a simple api to verify and execute git request that comes from client.

Example of /usr/bin/git_proxy file:

#!/usr/bin/env ruby
require 'git_handler'

config = GitHandler::Configuration.new

# Configuration has a bunch of options:
# :user       - Git user, default: git
# :home_path  - Home path, default: /home/git
# :repos_path - Path to repositories, default: /home/git/repositories
# :log_path   - Git requests logger, default: /var/log/git_handler.log

begin
  session = GitHandler::Session.new(config)
  session.execute(ARGV, ENV)
rescue Exception => ex
  STDERR.puts "Error: #{ex.message}"
  exit(1)
end

NOTE: Script must have permissions for execution.

Session instance will check if incoming git request has a valid environment and valid git command. After check is complete it will shell out to git-shell -c COMMAND to perform an original git command. Providing block to session.execute will override default and allow you to control the logic:

session.execute(ARGV, ENV) do |request|

  # Yields GitHandler::Request instance that
  # contains all information about git request, env and repo

  STDERR.puts "-----------------------------"
  STDERR.puts "REMOTE IP: #{request.remote_ip}"
  STDERR.puts "ARGS: #{request.args.inspect}"
  STDERR.puts "ENV: #{request.env.inspect}"
  STDERR.puts "REPO: #{request.repo}"
  STDERR.puts "REPO PATH: #{request.repo_path}"
  STDERR.puts "COMMAND: #{request.command}"
  STDERR.puts "-----------------------------"
end

By default, if request has invalid environment attributes or not a git request, session raises GitHandler::SessionError. If you dont want to handle exceptions, just use session.execute_safe method:

session = GitHandler::Session.new(config)
session.execute_safe(ARGV, ENV)

To test if all that works try this:

ssh -vT git@YOUR_HOST.com

In the debug output you'll something similar:

debug1: Remote: Agent forwarding disabled.
debug1: Remote: Pty allocation disabled.
debug1: Remote: Forced command.
debug1: Remote: Port forwarding disabled.
debug1: Remote: X11 forwarding disabled.
debug1: Remote: Agent forwarding disabled.
debug1: Remote: Pty allocation disabled.
debug1: Sending environment.
debug1: Sending env LANG = en_US.UTF-8

>>> Error: Invalid git request <<<<

debug1: client_input_channel_req: channel 0 rtype exit-status reply 0
debug1: client_input_channel_req: channel 0 rtype [email protected] reply 0
debug1: channel 0: free: client-session, nchannels 1
Transferred: sent 2384, received 2880 bytes, in 0.3 seconds
Bytes per second: sent 7308.1, received 8828.6
debug1: Exit status 1

This means that everything works. Script does not provide any shell access and only allows git requests. To test that, create an empty repository:

mkdir /home/git/repositories
cd /home/git/repositories
git init --bare testrepo.git

And clone it (on local machine):

git clone git@YOUR_HOST.com:testrepo.git

Server side configuration

In case you dont have a git user on your server, here is a quick manual on how to get it rolling.

Create a git user:

adduser --home /home/git --disabled-password git

Restrict SSH authentication only via public keys. Open file /etc/ssh/sshd_config and add this snippet to the end:

Match User !root
  PasswordAuthentication no

This will disable password authentications for everyone except root, or other user of your choice. You'll need to restart ssh daemon:

/etc/init.d/ssh restart

Authorized Keys

GitHandler provides a simple api to manage your authorized_keys file content.

Each write operation issues a lock File::LOCK_EX on file.

Example:

require 'git_handler/public_key'
require 'git_handler/authorized_keys'

# Read your local ssh public key content
content = File.read(File.expand_path('~/.ssh/id_rsa.pub'))

# Create a new key
key = GitHandler::PublicKey.new(content)

# Write formatted key to authorized_keys file
GitHandler::AuthorizedKeys.write_key('/path/to/file', key, 'my_command')

You can also write multiple keys:

GitHandler::AuthorizedKeys.write_keys('/path/to/file', [k1, k2, k3], 'my_command')

Testing

To run the test suite execute:

rake test

License

See LICENSE file for details