brownbeagle-gitauth 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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