Skip to content

Commit

Permalink
Major SSH key management enhancement
Browse files Browse the repository at this point in the history
This commit adds some major enhancements to add SSH keys.

* multiple SSH keys per user
* command line changes
** `add-key` adds a key to a user
** `rm-key` removes a key
** `show-user` now lists all associated SSH keys
  • Loading branch information
leoc committed Oct 24, 2012
1 parent 404a2cd commit 323c9d2
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 54 deletions.
38 changes: 34 additions & 4 deletions bin/gitauth
Expand Up @@ -110,10 +110,9 @@ GitAuth::Application.processing(ARGV) do |a|
a.option(:admin, "Makes a user an admin user")
a.add("add-user NAME PATH-TO-PUBLIC-KEY", "Creates a user with a given public key") do |name, ssh_key, options|
GitAuth.prepare
die! "'#{ssh_key}' is not a valid path to a public key" if !File.file?(ssh_key)
admin = !!options[:admin]
contents = File.read(ssh_key).strip
if GitAuth::User.create(name, admin, contents)
user = GitAuth::User.create(name, admin, ssh_key)
if user
puts "Successfully added user '#{name}' (user #{admin ? 'is' : 'is not'} an admin)"
else
die! "There was an unknown error attempting to add a user called '#{name}'"
Expand Down Expand Up @@ -162,6 +161,19 @@ GitAuth::Application.processing(ARGV) do |a|
end
end

a.add("add-key USER KEY", "Adds a key to a user") do |user_name, key, options|
GitAuth.prepare

user = GitAuth::User.get(user_name)
die! "Unable to find user '#{user_name}'" unless user

if user.add_key_or_file!(key)
puts "Successfully added new key to '#{user_name}'"
else
die! "Unable to save key for '#{user_name}'"
end
end

a.add("ls-users", "Lists all users currently managed by gitauth") do |options|
GitAuth.prepare
puts "Users:"
Expand Down Expand Up @@ -214,6 +226,20 @@ GitAuth::Application.processing(ARGV) do |a|
puts "Removed group '#{name}'"
end

a.add("rm-key USER KEY", "Adds a users SSH key") do |user_name, key, options|
GitAuth.prepare

user = GitAuth::User.get(user_name)
die! "Unable to find user '#{user_name}'" unless user

if user.remove_key!(key)
puts "Successfully removed new key for '#{user_name}'"
else
die! "Unable to save key for '#{user_name}'"
end
end


a.add("rm-user-from-group USER GROUP", "Remove a user from a group") do |user_name, group_name, options|
GitAuth.prepare

Expand Down Expand Up @@ -276,8 +302,12 @@ GitAuth::Application.processing(ARGV) do |a|
puts "User Name: #{user.name}"
groups = user.groups
puts "\nGroups:"
puts " - This user isn't a member of any groups" if groups.blank?
puts " - This user isn't a member of any group" if groups.blank?
groups.each { |g| puts " - #{g.to_s}" }
keys = user.keys
puts "\nSSH Keys:"
puts " - This user has no keys" if keys.empty?
keys.each_key { |k| puts " - #{k.to_s}" }
end

a.add("enable-htaccess-auth", "Generates .htaccess and .htpasswd files, disabling the built in auth") do |options|
Expand Down
205 changes: 155 additions & 50 deletions lib/gitauth/user.rb
Expand Up @@ -19,94 +19,208 @@

module GitAuth
class User < SaveableClass(:users)
include GitAuth::Loggable

include GitAuth::Loggable

SSH_KEY_REGEX = /ssh-\w+ [a-zA-Z0-9\/\+]+=?=?/

def self.get(name)
logger.debug "Getting user for the name '#{name}'"
(all || []).detect { |r| r.name == name }
end

def self.create(name, admin, key)
# Basic sanity checking
return false if name.nil? || admin.nil? || key.nil?

# Require that the name is valid and admin is a boolean.
return false unless name =~ /^([\w\_\-\.]+)$/ && !!admin == admin

# Check there isn't an existing user
return false unless get(name).blank?
if (user = new(name, admin)).write_ssh_key!(key)

user = User.new(name, admin)
if user.add_key_or_file!(key)
add_item(user)
return true
return user
else
return false
return nil
end
end

attr_reader :name, :admin

def initialize(name, admin = false)
@name = name
@admin = admin
end

def to_s
@name.to_s
end

def write_ssh_key!(key)
cleaned_key = self.class.clean_ssh_key(key)
if cleaned_key.nil?
return false
else
output = "#{command_prefix} #{cleaned_key}"
File.open(GitAuth::Settings.authorized_keys_file, "a+") do |file|
file.puts output
end
return true
end
end


def command_prefix
options = ["command=\"#{GitAuth::Settings.shell_executable} #{@name}\"",
"no-port-forwarding", "no-X11-forwarding", "no-agent-forwarding"]
options << "no-pty" if !shell_accessible?
options.join(",")
end


##
# Destroys the user. This means all information about this user
# will be removed from the database files (keys, group
# associations, permissions).
def destroy!
# Clear permissions.
GitAuth::Repo.all.each { |r| r.remove_permissions_for(self) }

# Clear group associations.
GitAuth::Group.all.each { |g| g.remove_member(self) }
# Remove the public key from the authorized_keys file.
auth_keys_path = GitAuth::Settings.authorized_keys_file
if File.exist?(auth_keys_path)
contents = File.read(auth_keys_path)
contents.gsub!(/#{command_prefix} ssh-\w+ [a-zA-Z0-9\/\+]+==\r?\n?/m, "")
File.open(auth_keys_path, "w+") { |f| f.write contents }
end

# Remove public keys from the authorized_keys file.
clear_all_keys!

# Remove this user object from the list of users.
self.class.all.reject! { |u| u == self }
# Finally, save everything

# Finally, save everything.
self.class.save!
GitAuth::Repo.save!
GitAuth::Group.save!
end


##
# Retrieves all groups, that this user is part of.
def groups
(Group.all || []).select { |g| g.member?(self) }
end


##
# Reads the SSH keys for the current user
# from the authorization file.
def keys
unless @keys
@keys = {}
File.open(GitAuth::Settings.authorized_keys_file, 'r') do |file|
while (line = file.gets)
if line =~ /^#{command_prefix} (#{SSH_KEY_REGEX}) (.+)$/
@keys[$2] = $1
end
end
end
end
@keys
end

##
# Removes all SSH keys for this users
def clear_all_keys!
keys.clear
write_keys!
end

##
# Checks whether the given argument is an existing file or not.
# If yes, read the files contents and try adding the content as a
# new key. Otherwise add the argument as a new key.
def add_key_or_file arg
if File.exists?(arg)
add_key File.read(arg)
else
add_key key
end
end

##
# Adds key or file and then saves authorized_keys file.
def add_key_or_file! arg
add_key_or_file(arg) and write_keys!
end

##
# Adds a key to the set of keys for this user. If a user already
# had this key associated, it gets updated.
def add_key key
if key =~ /^(#{SSH_KEY_REGEX}) (.+)$/
if (old_key = keys.key($1))
keys[$2] = keys.delete(old_key)
else
keys[$2] = $1
end
return true
end
return false
end

##
# Adds the key and saves the authorized_keys file.
def add_key! key
add_key(key) and write_keys!
end

##
# Removes the key from the user.
# Valid key arguments may be the key itself or the name of the key.
def remove_key key
if key =~ /^#{SSH_KEY_REGEX}$/
if (old_key = keys.key($1))
keys.delete(old_key)
return true
end
else
if keys[key]
keys.delete(key)
return true
end
end
return false
end

##
# Removes the key and saves the authorized_keys file.
def remove_key! key
remove_key(key) and write_keys!
end

##
# Write all keys to file.
def write_keys!
begin
keyset = []
File.open(GitAuth::Settings.authorized_keys_file, "r") do |file|
while (line = file.gets)
if line !~ /^#{command_prefix}/ and line =~ /#{SSH_KEY_REGEX} .+/
keyset << line
end
end
end
keys.each_pair do |name, key|
keyset << "#{command_prefix} #{key} #{name}\n"
end
File.open(GitAuth::Settings.authorized_keys_file, "w+") do |file|
file.puts "## GitAuth - DO NO EDIT BELOW THIS LINE ##"
file.puts keyset.join
end
return true
rescue Exception => e
puts "Some error happened while saving authorized_keys file: #{e}"
return false
end
end

def admin?
!!@admin
end

alias shell_accessible? admin?

def pushable?(repo)
admin? || repo.writeable_by?(self)
end

def pullable?(repo)
admin? || repo.readable_by?(self)
end

def can_execute?(command, repo)
return if command.bad?
if command.write?
Expand All @@ -117,19 +231,10 @@ def can_execute?(command, repo)
pullable?(repo)
end
end

def self.clean_ssh_key(key)
if key =~ /^(ssh-\w+ [a-zA-Z0-9\/\+]+==?).*$/
return $1
else
return nil
end
end


def self.valid_key?(key)
clean_ssh_key(key).present?
key.present? && key =~ /^#{SSH_KEY_REGEX} .+$/
end

end
Users = User # For Backwards Compat.
end
end

0 comments on commit 323c9d2

Please sign in to comment.