brownbeagle-gitauth 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,92 @@
1
+ == GitAuth - SSH-based authentication for Shared Git Repositories.
2
+
3
+ If you've heard of Gitosis before, GitAuth is like Gitosis but A) in Ruby, B) slightly simpler to get going and C) doesn't use a git repository to manage users.
4
+
5
+ At the moment configuration / adding users is done via a single command - +gitauth+. For usage, see below.
6
+
7
+ === License
8
+
9
+ GitAuth is licensed under AGPL, with parts of the code being derived
10
+ from Gitorius - http://gitorious.org/
11
+
12
+ === Installing GitAuth
13
+
14
+ Getting started is relatively simple. First of, you'll need to log onto the remote server / your git host. Next, you'll need to install the gem:
15
+
16
+ sudo gem install brownbeagle-gitauth --source=https://gems.github.com/
17
+
18
+ Once that's done, the +gitauth+ and +gitauth-shell+ commands should be in your path.
19
+ Next, you'll want to (in most cases anyway) use a specific +git+ user to host repositories.
20
+
21
+ Using the example of ubuntu, we'll add a git user under which all actions will now take place (note, this is essentially the same as gitosis):
22
+
23
+ sudo adduser --disabled-password --shell /bin/bash --group --home /home/git --system --gecos 'gitauth user for version control' git
24
+
25
+ Now, whenever you run the +gitauth+ executable, you'll do so as the user you just created
26
+ above. For simplicity purposes, I added the the following to my zsh profile so I always
27
+ had it available. If you don't wish to, just use as you would without the alias:
28
+
29
+ alias asgit='sudo -H -u git'
30
+
31
+ And finally, to create a settings file and initialize .ssh and authorized_keys, perform the
32
+ following:
33
+
34
+ asgit gitauth install
35
+
36
+ Note that when it asks you for the gitauth shell path, the default will lock
37
+ it to the current gitauth version SO if you want it to stay up to date between gem versions
38
+ point it to the path for always-current executable (e.g. on Ubuntu 9.04 w/ apt-get ruby + gems,
39
+ +/var/lib/gems/1.8/bin/gitauth-shell+)
40
+
41
+ Also, Note that if you append a path to a public key to the end of the install command,
42
+ it will initialize a new +admin+ user who can also login via SSH. e.g.
43
+
44
+ asgit gitauth install id_rsa.pub
45
+
46
+ Would initialize an admin user with the given public key.
47
+
48
+ Note that from now on, all gitauth keys should be run either logged in as
49
+ git (via the admin user and ssh) or by being prefixed with asgit or "sudo -H -u git"
50
+
51
+ === Adding Users
52
+
53
+ Whenever you want to add a user, it's ass imple as:
54
+
55
+ gitauth adduser user-name path-to-public-key
56
+
57
+ Note that if the --admin option is specified, the user will
58
+ be able to log in to the shell via SSH and will also be able
59
+ to access any repository.
60
+
61
+ === Adding Repositories
62
+
63
+ Adding a repository is a two step process. First, you create it:
64
+
65
+ gitauth addrepo repo-name
66
+
67
+ Then, for every user who needs access, you do:
68
+
69
+ gitauth permissions repo-name user-name permission-type
70
+
71
+ Where permission type is read, write or all. If permission
72
+ type isn't specified, it will default to all.
73
+
74
+ === Accessing repos:
75
+
76
+ Finally, once you've added users / repos, using them is as simple
77
+ as doing the following on each users computer:
78
+
79
+ git clone git@your-remote-host:repo-name
80
+
81
+ Or
82
+
83
+ git clone git@your-remote-host:repo-name.git
84
+
85
+ Either form working just as well.
86
+
87
+ Note that for the first time you push, you will need
88
+ to use the full form:
89
+
90
+ git push origin master
91
+
92
+ As it starts as an empty repo.
data/bin/gitauth ADDED
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ # Copyright (C) 2009 BrownBeagle
5
+ # Copyright (C) 2008 Darcy Laycock <sutto@sutto.net>
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Affero General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Affero General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License
18
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ #++
20
+
21
+ require 'rubygems'
22
+ require 'readline'
23
+ require 'thor'
24
+ require File.join(File.dirname(__FILE__), "..", "lib", "gitauth")
25
+
26
+
27
+ class GitAuthRunner < Thor
28
+
29
+
30
+ desc "addrepo REPO-NAME [PATH-PART]", "Adds a new repository"
31
+ def addrepo(name, path = name)
32
+ GitAuth.setup!
33
+ if GitAuth::Repo.create(name, path)
34
+ $stdout.puts "Repo was successfully created"
35
+ else
36
+ $stderr.puts "There was an error creating the repo"
37
+ exit! 1
38
+ end
39
+ end
40
+
41
+ desc "adduser NAME PATH-TO-PUBLIC-KEY [--admin]", "Adds a user"
42
+ method_options :admin => :boolean
43
+ def adduser(name, key_path)
44
+ GitAuth.setup!
45
+ admin = !!(options && options[:admin])
46
+ if GitAuth::Users.create(name, admin, File.read(key_path).strip)
47
+ $stdout.puts "User added"
48
+ else
49
+ $stderr.puts "There was an error adding the given user"
50
+ exit!
51
+ end
52
+ end
53
+
54
+ desc "repos", "Lists all the current repos"
55
+ def repos
56
+ GitAuth.setup!
57
+ $stdout.puts "repositories:"
58
+ GitAuth::Repo.all.each do |repo|
59
+ $stdout.puts " - #{repo.name}"
60
+ end
61
+ end
62
+
63
+ desc "users", "Lists all users in the system"
64
+ def users
65
+ GitAuth.setup!
66
+ $stdout.puts "users:"
67
+ GitAuth::Users.all.each do |user|
68
+ $stdout.puts "- #{user.name}"
69
+ end
70
+ end
71
+
72
+
73
+ desc "permissions REPO USER [PERMISION=all,read,write]", "Adds Permissions for a user to a repository"
74
+ def permissions(repo, user, permissions = "all")
75
+ GitAuth.setup!
76
+ unless %w(read write all).include?(permissions)
77
+ $stderr.puts "Invalid permissions: #{permissions}"
78
+ exit! 1
79
+ end
80
+ repo = GitAuth::Repo.get(repo)
81
+ user = GitAuth::Users.get(user)
82
+ if repo.nil? || user.nil?
83
+ $stderr.puts "Invalid repository or user, please check the name"
84
+ exit! 1
85
+ end
86
+ repo.writeable_by(user) if %w(all write).include?(permissions)
87
+ repo.readable_by(user) if %w(all read).include?(permissions)
88
+ GitAuth::Users.save!
89
+ GitAuth::Repo.save!
90
+ $stdout.puts "Permissions Added"
91
+ end
92
+
93
+ desc "install [ADMIN-PUBLIC-KEY]", "creates and sets the permissions for .ssh and .ssh/authorized keys"
94
+ method_options :force_config => :boolean
95
+ def install(public_key_path = nil)
96
+ $stdout.print "Are you logged in as the correct user? (y/n) "
97
+ answer = Readline.readline
98
+ if answer !~ /^y/i
99
+ $stderr.puts "Please log in as the correct user and re-run"
100
+ exit! 1
101
+ end
102
+ require 'fileutils'
103
+ folder = File.expand_path("~/.ssh")
104
+ if !File.exist?(folder) || !File.directory?(folder)
105
+ FileUtils.mkdir(folder)
106
+ FileUtils.chmod(0700, folder)
107
+ end
108
+ authorized_keys = File.join(folder, "authorized_keys")
109
+ if !File.exist?(authorized_keys)
110
+ File.open(authorized_keys, "w+") do |f|
111
+ f.puts "## GitAuth - DO NO EDIT BELOW THIS LINE ##"
112
+ end
113
+ FileUtils.chmod(0600, authorized_keys)
114
+ end
115
+ gitauth_folder = File.expand_path("~/.gitauth/")
116
+ FileUtils.mkdir(gitauth_folder) if !File.exist?(gitauth_folder) || !File.directory?(gitauth_folder)
117
+ gitauth_settings_path = File.join(gitauth_folder, "settings.yml")
118
+ unless File.exist?(gitauth_settings_path) || (options && options[:force_config])
119
+ print "Where did you want repositories to be stored? (default: ~/repositories/): "
120
+ path = Readline.readline.strip
121
+ path = File.expand_path("~/repositories") if path.empty?
122
+ begin
123
+ FileUtils.mkdir_p(path)
124
+ rescue
125
+ $stderr.puts "There was an error making the repository folder: #{path}"
126
+ $stderr.puts "Please check again"
127
+ exit! 1
128
+ end
129
+ current_gitauth_shell_path = File.join(GitAuth::BASE_DIR, "bin", "gitauth-shell")
130
+ $stdout.print "What is the path to your gitauth-shell (default: '#{current_gitauth_shell_path}'): "
131
+ gitauth_shell_path = Readline.readline
132
+ gitauth_shell_path = current_gitauth_shell_path if gitauth_shell_path.empty?
133
+ File.open(gitauth_settings_path, "w+") do |f|
134
+ f.write({
135
+ "base_path" => path,
136
+ "authorized_keys_file" => authorized_keys,
137
+ "shell_executable" => gitauth_shell_path
138
+ }.to_yaml)
139
+ end
140
+ if !public_key_path.nil? && File.exist?(public_key_path)
141
+ GitAuth.setup!
142
+ created = GitAuth::Users.create("admin", true, File.read(public_key_path).strip)
143
+ if created
144
+ $stdout.puts "Admin User Created."
145
+ else
146
+ $stderr.puts "An admin user couldn't be created."
147
+ exit! 1
148
+ end
149
+ end
150
+ end
151
+ rescue Errno::EACCES
152
+ $stderr.puts "Hey, it looks you don't have access to that - sorry!"
153
+ exit! 1
154
+ end
155
+
156
+ end
157
+
158
+ if ARGV.empty?
159
+ GitAuthRunner.new.help
160
+ else
161
+ GitAuthRunner.start
162
+ end
data/bin/gitauth-shell ADDED
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #--
4
+ # Copyright (C) 2009 BrownBeagle
5
+ # Copyright (C) 2008 Darcy Laycock <sutto@sutto.net>
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Affero General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Affero General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License
18
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ #++
20
+
21
+
22
+ REAL_FILE = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__
23
+
24
+ # Require the proper gitauth file.
25
+ require File.expand_path(File.join(File.dirname(REAL_FILE), "..", "lib", "gitauth"))
26
+
27
+ GitAuth.setup!
28
+
29
+ # Gitorious does it so I should too!
30
+ File.umask(0022)
31
+
32
+ user_name = ARGV[0]
33
+ command = ENV["SSH_ORIGINAL_COMMAND"]
34
+
35
+ GitAuth::Client.start!(user_name, command) do |c|
36
+
37
+ c.on(:invalid_user) do |c|
38
+ c.exit_with_error "An invalid user / key was used. Please ensure it is setup with GitAuth"
39
+ end
40
+
41
+ c.on(:invalid_command) do |c|
42
+ if c.user.shell_accessible?
43
+ exec(ENV["SHELL"])
44
+ else
45
+ c.exit_with_error "SSH_ORIGINAL_COMMAND is needed, mmmkay?"
46
+ end
47
+ end
48
+
49
+ c.on(:invalid_repository) do |c|
50
+ c.exit_with_error "Ze repository you specified does not exist."
51
+ end
52
+
53
+ c.on(:bad_command) do |c|
54
+ c.exit_with_error "A Bad Command Has Failed Ye, Thou Shalt Not Continue."
55
+ end
56
+
57
+ c.on(:access_denied) do |c|
58
+ c.exit_with_error "These are not the droids you are looking for"
59
+ end
60
+
61
+ c.on(:fatal_error) do |c|
62
+ c.exit_with_error "Holy crap, we've imploded cap'n!"
63
+ end
64
+
65
+ end
66
+
@@ -0,0 +1,91 @@
1
+ #--
2
+ # Copyright (C) 2009 Brown Beagle Software
3
+ # Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
4
+ # Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
5
+ # Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
6
+ # Copyright (C) 2008 Darcy Laycock <sutto@sutto.net>
7
+ #
8
+ # This program is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
+ #++
21
+
22
+ module GitAuth
23
+ class Client
24
+
25
+ attr_accessor :user, :command
26
+
27
+ def initialize(user_name, command)
28
+ GitAuth.logger.debug "Initializing client with command: '#{command}' and user name '#{user_name}'"
29
+ @callbacks = Hash.new { |h,k| h[k] = [] }
30
+ @user = GitAuth::Users.get(user_name.to_s.strip)
31
+ @command = command
32
+ end
33
+
34
+ def on(command, &blk)
35
+ @callbacks[command.to_sym] << blk
36
+ end
37
+
38
+ def execute_callback!(command)
39
+ @callbacks[command.to_sym].each { |c| c.call(self) }
40
+ end
41
+
42
+ def exit_with_error(error)
43
+ GitAuth.logger.warn "Exiting with error: #{error}"
44
+ $stderr.puts error
45
+ exit! 1
46
+ end
47
+
48
+ def run!
49
+ if @user.nil?
50
+ execute_callback! :invalid_user
51
+ elsif @command.to_s.strip.empty?
52
+ execute_callback! :invalid_command
53
+ else
54
+ command = Command.parse!(@command)
55
+ repo = Repo.get(extract_repo_name(command))
56
+ if command.bad?
57
+ execute_callback! :bad_command
58
+ elsif repo.nil?
59
+ execute_callback! :invalid_repository
60
+ elsif user.can_execute?(command, repo)
61
+ # We can go ahead.
62
+ git_shell_argument = "#{command.verb} '#{repo.real_path}'"
63
+ # And execute that soab.
64
+ GitAuth.logger.info "Running command: #{git_shell_argument} for user: #{@user.name}"
65
+ exec("git-shell", "-c", git_shell_argument)
66
+ else
67
+ execute_callback! :access_denied
68
+ end
69
+ end
70
+ rescue Exception => e
71
+ GitAuth.logger.fatal "Exception: #{e.class.name}: #{e.message}"
72
+ e.backtrace.each do |l|
73
+ GitAuth.logger.fatal " => #{l}"
74
+ end
75
+ execute_callback! :fatal_error
76
+ end
77
+
78
+ def self.start!(user, command)
79
+ client = self.new(user, command)
80
+ yield client if block_given?
81
+ client.run!
82
+ end
83
+
84
+ protected
85
+
86
+ def extract_repo_name(command)
87
+ command.path.gsub(/\.git$/, "")
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,105 @@
1
+ #--
2
+ # Copyright (C) 2009 Brown Beagle Software
3
+ # Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)
4
+ # Copyright (C) 2007, 2008 Johan Sørensen <johan@johansorensen.com>
5
+ # Copyright (C) 2008 Tim Dysinger <tim@dysinger.net>
6
+ # Copyright (C) 2008 Tor Arne Vestbø <tavestbo@trolltech.com>
7
+ # Copyright (C) 2008 Darcy Laycock <sutto@sutto.net>
8
+ #
9
+ # This program is free software: you can redistribute it and/or modify
10
+ # it under the terms of the GNU Affero General Public License as published by
11
+ # the Free Software Foundation, either version 3 of the License, or
12
+ # (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ # GNU Affero General Public License for more details.
18
+ #
19
+ # You should have received a copy of the GNU Affero General Public License
20
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+ #++
22
+
23
+ # This along with the associated gitauth-shell command are inspired by
24
+ # (and essentially, derived from) gitosis (http://eagain.net/gitweb/?p=gitosis.git)
25
+ # and gitorius (http://gitorius.org)
26
+ # Gitosis is of this writing licensed under the GPLv2 and is copyright (c) Tommi Virtanen
27
+ # and can be found at http://eagain.net/gitweb/?p=gitosis.git
28
+ # GitAuth::Command is licensed under the same license
29
+
30
+ module GitAuth
31
+ class Command
32
+ class BadCommandError < StandardError; end
33
+
34
+ # Standard Commands
35
+ READ_COMMANDS = ["git-upload-pack", "git upload-pack"]
36
+ WRITE_COMMANDS = ["git-receive-pack", "git receive-pack"]
37
+ PATH_REGEXP = /^'([a-z0-9\-\+]+(\.git)?)'$/i.freeze
38
+
39
+ attr_reader :path, :verb, :command
40
+
41
+ def initialize(command)
42
+ @command = command
43
+ @verb = nil
44
+ @argument = nil
45
+ @path = nil
46
+ @bad_command = true
47
+ end
48
+
49
+ def bad?
50
+ !!@bad_command
51
+ end
52
+
53
+ def write?
54
+ !bad? && @verb_type == :write
55
+ end
56
+
57
+ def read?
58
+ !bad? && !write?
59
+ end
60
+
61
+ # These exceptions are FUGLY.
62
+ # Clean up, mmkay?
63
+ def process!
64
+ raise BadCommandError if @command.include?("\n")
65
+ @verb, @argument = split_command
66
+ raise BadCommandError if @argument.nil? || @argument.is_a?(Array)
67
+ # Check if it's read / write
68
+ if READ_COMMANDS.include?(@verb)
69
+ @verb_type = :read
70
+ elsif WRITE_COMMANDS.include?(@verb)
71
+ @verb_type = :write
72
+ else
73
+ raise BadCommandError
74
+ end
75
+ if PATH_REGEXP =~ @argument
76
+ @path = $1
77
+ raise BadCommandError unless @path
78
+ else
79
+ raise BadCommandError
80
+ end
81
+ @bad_command = false
82
+ rescue BadCommandError
83
+ end
84
+
85
+ def self.parse!(command)
86
+ command = self.new(command)
87
+ command.process!
88
+ command
89
+ end
90
+
91
+ protected
92
+
93
+ def split_command
94
+ parts = @command.split(" ")
95
+ if parts.size == 3
96
+ ["#{parts[0]} #{parts[1]}", parts[2]]
97
+ elsif parts.size == 2
98
+ parts
99
+ else
100
+ raise BadCommandError
101
+ end
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,106 @@
1
+ #--
2
+ # Copyright (C) 2009 Brown Beagle Software
3
+ # Copyright (C) 2008 Darcy Laycock <sutto@sutto.net>
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+ #++
18
+
19
+ require 'fileutils'
20
+ module GitAuth
21
+ class Repo
22
+ REPOS_PATH = File.join(GitAuth::GITAUTH_DIR, "repositories.yml")
23
+
24
+ def self.all
25
+ @@all_repositories ||= nil
26
+ end
27
+
28
+ def self.load!
29
+ self.all = YAML.load_file(REPOS_PATH) rescue nil if File.exist?(REPOS_PATH)
30
+ self.all = [] unless self.all.is_a?(Array)
31
+ end
32
+
33
+ def self.save!
34
+ load! if self.all.nil?
35
+ File.open(REPOS_PATH, "w+") do |f|
36
+ f.write self.all.to_yaml
37
+ end
38
+ end
39
+
40
+ def self.all=(value)
41
+ @@all_repositories = value
42
+ end
43
+
44
+ def self.get(name)
45
+ GitAuth.logger.debug "Getting Repo w/ name: '#{name}'"
46
+ self.all.detect { |r| r.name == name }
47
+ end
48
+
49
+ def self.create(name, path = name)
50
+ return false unless self.get(name).nil?
51
+ repository = self.new(name, path)
52
+ if repository.create_repo!
53
+ self.load!
54
+ self.all << repository
55
+ self.save!
56
+ return true
57
+ else
58
+ return false
59
+ end
60
+ end
61
+
62
+ attr_accessor :name, :path
63
+
64
+ def initialize(name, path, auto_create = false)
65
+ @name, @path = name, path
66
+ @permissions = {}
67
+ end
68
+
69
+ def writeable_by(user)
70
+ @permissions[:write] ||= []
71
+ @permissions[:write] << user.name
72
+ @permissions[:write].uniq!
73
+ end
74
+
75
+ def readable_by(user)
76
+ @permissions[:read] ||= []
77
+ @permissions[:read] << user.name
78
+ @permissions[:read].uniq!
79
+ end
80
+
81
+ def writeable_by?(user)
82
+ (@permissions[:write] || []).include? user.name
83
+ end
84
+
85
+ def readable_by?(user)
86
+ (@permissions[:read] || []).include? user.name
87
+ end
88
+
89
+ def real_path
90
+ File.join(GitAuth.settings.base_path, @path)
91
+ end
92
+
93
+ def create_repo!
94
+ path = self.real_path
95
+ unless File.exist?(path) && File.directory?(path)
96
+ FileUtils.mkdir_p(path)
97
+ output = ""
98
+ Dir.chdir(path) do
99
+ IO.popen("git init --bare") { |f| output << f.read }
100
+ end
101
+ return !!(output =~ /Initialized empty Git repository/)
102
+ end
103
+ end
104
+
105
+ end
106
+ end