separate out the code that sets up ~/.ssh/authorized_keys

NOTE: there are no *functional* changes in this for *normal*
    gitolite users.  It's just a chunk of code moving into a new
    subroutine etc.

KDE needs to populate the authkeys file from an LDAP store.  Other large
projects may have similar means to store keys, depending on how they do
their user provisioning so a generic solution is worth exploring.

This means that in these special cases
  - the gitolite-admin repo's keydir/ directory is not needed [1]
  - but they still need to create the authkeys file somehow

Implementation:

  - write a shim program to make the authkeys-generation code callable
    from the command line/shell.
  - set $GL_NO_SETUP_AUTHKEYS=1 in the rc file to disable authkey
    generation during a "compile" (admin repo push)

Expected usage of new program gl-setup-authkeys:

  - LDAP change triggers some script
  - this script collects all keys from LDAP, puts them in some
    directory, and then calls gl-setup-authkeys, passing it the name of
    the directory

ALSO PLEASE SEE COMMENTS AT THE TOP OF THE NEW PROGRAM IN THIS COMMIT
FOR SOME IMPORTANT DISCUSSION.

----

Footnotes:

[1] It doesn't make sense to use it if the keys will be maintained by
some other entity and can be called up as needed, and it adds an
unnecessary extra step.
This commit is contained in:
Sitaram Chamarty 2010-07-25 20:55:32 +05:30
parent 2dc02e9a75
commit c8879264e6
3 changed files with 168 additions and 94 deletions

View file

@ -472,6 +472,117 @@ sub cli_repo_rights {
print "$perm $creator\n";
}
# ----------------------------------------------------------------------------
# setup the ~/.ssh/authorized_keys file
# ----------------------------------------------------------------------------
sub setup_authkeys
{
# ARGUMENTS
my($bindir, $GL_KEYDIR, $user_list_p) = @_;
# calling from outside the normal compile script may mean that argument 2
# may not be passed; so make sure it's a valid hashref, even if empty
$user_list_p = {} unless $user_list_p;
# CONSTANTS
# command and options for authorized_keys
my $AUTH_COMMAND="$bindir/gl-auth-command";
my $AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";
# START
my $authkeys_fh = wrap_open( "<", $ENV{HOME} . "/.ssh/authorized_keys",
"\tFor security reasons, gitolite will not *create* this file if it does\n" .
"\tnot already exist. Please see the \"admin\" document for details\n");
my $newkeys_fh = wrap_open( ">", $ENV{HOME} . "/.ssh/new_authkeys" );
# save existing authkeys minus the GL-added stuff
while (<$authkeys_fh>)
{
print $newkeys_fh $_ unless (/^# gito(sis-)?lite start/../^# gito(sis-)?lite end/);
}
# add our "start" line, each key on its own line (prefixed by command and
# options, in the standard ssh authorized_keys format), then the "end" line.
print $newkeys_fh "# gitolite start\n";
wrap_chdir($GL_KEYDIR);
my @not_in_config; # pubkeys exist but users don't appear in the config file
for my $pubkey (`find . -type f`)
{
chomp($pubkey); $pubkey =~ s(^\./)();
# security check (thanks to divVerent for catching this)
unless ($pubkey =~ $REPONAME_PATT) {
print STDERR "$pubkey contains some unsavoury characters; ignored...\n";
next;
}
# lint check 1
unless ($pubkey =~ /\.pub$/)
{
print STDERR "WARNING: pubkey files should end with \".pub\", ignoring $pubkey\n";
next;
}
my $user = $pubkey;
$user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub
$user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz
# lint check 2 -- don't print right now; just collect the messages
push @not_in_config, "$user($pubkey)" if %$user_list_p and not $user_list_p->{$user};
$user_list_p->{$user} = 'has pubkey' if %$user_list_p;
# apparently some pubkeys don't end in a newline...
my $pubkey_content;
{
local $/ = undef;
local @ARGV = ($pubkey);
$pubkey_content = <>;
}
$pubkey_content =~ s/\s*$/\n/;
# don't trust files with multiple lines (i.e., something after a newline)
if ($pubkey_content =~ /\n./)
{
print STDERR "WARNING: a pubkey file can only have one line (key); ignoring $pubkey\n" .
" If you want to add multiple public keys for a single user, use\n" .
" \"user\@host.pub\" file names. See the \"one user, many keys\"\n" .
" section in doc/3-faq-tips-etc.mkd for details.\n";
next;
}
print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS ";
print $newkeys_fh $pubkey_content;
}
# lint check 2 -- print less noisily
if (@not_in_config > 10) {
print STDERR "$WARN You have " . scalar(@not_in_config) . " pubkeys that do not appear to be used in the config\n";
} elsif (@not_in_config) {
print STDERR "$WARN the following users (pubkey files in parens) do not appear in the config file:\n", join(",", sort @not_in_config), "\n";
}
# lint check 3; a little more severe than the first two I guess...
{
my @no_pubkey =
grep { $_ !~ /^(gitweb|daemon|\@.*|~\$creator|\$readers|\$writers)$/ }
grep { $user_list_p->{$_} ne 'has pubkey' }
keys %{$user_list_p};
if (@no_pubkey > 10) {
print STDERR "$WARN You have " . scalar(@no_pubkey) . " users WITHOUT pubkeys...!\n";
} elsif (@no_pubkey) {
print STDERR "$WARN the following users have no pubkeys:\n", join(",", sort @no_pubkey), "\n";
}
}
print $newkeys_fh "# gitolite end\n";
close $newkeys_fh or die "$ABRT close newkeys failed: $!\n";
# all done; overwrite the file (use cat to avoid perm changes)
system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys");
system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys")
and die "couldn't write authkeys file\n";
system("rm $ENV{HOME}/.ssh/new_authkeys");
}
# ----------------------------------------------------------------------------
# S P E C I A L C O M M A N D S
# ----------------------------------------------------------------------------

View file

@ -54,7 +54,7 @@ open STDOUT, ">", "/dev/null" if (@ARGV and shift eq '-q');
# these are set by the "rc" file
our ($GL_ADMINDIR, $GL_CONF, $GL_KEYDIR, $GL_CONF_COMPILED, $REPO_BASE, $REPO_UMASK, $PROJECTS_LIST, $GIT_PATH, $GL_WILDREPOS, $GL_GITCONFIG_KEYS, $GL_PACKAGE_HOOKS, $GL_BIG_CONFIG, $GL_NO_DAEMON_NO_GITWEB, $GL_NO_CREATE_REPOS, $GL_NO_SETUP_AUTHKEYS);
# and these are set by gitolite.pm
our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $AUTH_COMMAND, $AUTH_OPTIONS, $ABRT, $WARN);
our ($REPONAME_PATT, $REPOPATT_PATT, $USERNAME_PATT, $ABRT, $WARN);
# the common setup module is in the same directory as this running program is
my $bindir = $0;
@ -73,10 +73,6 @@ $ENV{PATH} .= ":$GIT_PATH" if $GIT_PATH;
# definitions specific to this program
# ----------------------------------------------------------------------------
# command and options for authorized_keys
$AUTH_COMMAND="$bindir/gl-auth-command";
$AUTH_OPTIONS="no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty";
# groups can now represent user groups or repo groups.
# $groups{group}{member} = "master" (or name of fragment file in which the
@ -559,93 +555,6 @@ unless ($GL_NO_DAEMON_NO_GITWEB) {
# "compile" ssh authorized_keys
# ----------------------------------------------------------------------------
# NOTE: for now we assume that setting up authkeys is the LAST thing we do!
exit 0 if $GL_NO_SETUP_AUTHKEYS;
my $authkeys_fh = wrap_open( "<", $ENV{HOME} . "/.ssh/authorized_keys",
"\tFor security reasons, gitolite will not *create* this file if it does\n" .
"\tnot already exist. Please see the \"admin\" document for details\n");
my $newkeys_fh = wrap_open( ">", $ENV{HOME} . "/.ssh/new_authkeys" );
# save existing authkeys minus the GL-added stuff
while (<$authkeys_fh>)
{
print $newkeys_fh $_ unless (/^# gito(sis-)?lite start/../^# gito(sis-)?lite end/);
unless ($GL_NO_SETUP_AUTHKEYS) {
&setup_authkeys($bindir, $GL_KEYDIR, \%user_list);
}
# add our "start" line, each key on its own line (prefixed by command and
# options, in the standard ssh authorized_keys format), then the "end" line.
print $newkeys_fh "# gitolite start\n";
wrap_chdir($GL_KEYDIR);
my @not_in_config; # pubkeys exist but users don't appear in the config file
for my $pubkey (`find . -type f`)
{
chomp($pubkey); $pubkey =~ s(^\./)();
# security check (thanks to divVerent for catching this)
unless ($pubkey =~ $REPONAME_PATT) {
print STDERR "$pubkey contains some unsavoury characters; ignored...\n";
next;
}
# lint check 1
unless ($pubkey =~ /\.pub$/)
{
print STDERR "WARNING: pubkey files should end with \".pub\", ignoring $pubkey\n";
next;
}
my $user = $pubkey;
$user =~ s(.*/)(); # foo/bar/baz.pub -> baz.pub
$user =~ s/(\@[^.]+)?\.pub$//; # baz.pub, baz@home.pub -> baz
# lint check 2 -- don't print right now; just collect the messages
push @not_in_config, "$user($pubkey)" unless $user_list{$user};
$user_list{$user} = 'has pubkey';
# apparently some pubkeys don't end in a newline...
my $pubkey_content = `cat $pubkey`;
$pubkey_content =~ s/\s*$/\n/;
# don't trust files with multiple lines (i.e., something after a newline)
if ($pubkey_content =~ /\n./)
{
print STDERR "WARNING: a pubkey file can only have one line (key); ignoring $pubkey\n" .
" If you want to add multiple public keys for a single user, use\n" .
" \"user\@host.pub\" file names. See the \"one user, many keys\"\n" .
" section in doc/3-faq-tips-etc.mkd for details.\n";
next;
}
print $newkeys_fh "command=\"$AUTH_COMMAND $user\",$AUTH_OPTIONS ";
print $newkeys_fh $pubkey_content;
}
# lint check 2 -- print less noisily
if (@not_in_config > 10) {
print STDERR "$WARN You have " . scalar(@not_in_config) . " pubkeys that do not appear to be used in the config\n";
} elsif (@not_in_config) {
print STDERR "$WARN the following users (pubkey files in parens) do not appear in the config file:\n", join(",", sort @not_in_config), "\n";
}
# lint check 3; a little more severe than the first two I guess...
{
my @no_pubkey =
grep { $_ !~ /^(gitweb|daemon|\@.*|~\$creator|\$readers|\$writers)$/ }
grep { $user_list{$_} ne 'has pubkey' }
keys %user_list;
if (@no_pubkey > 10) {
print STDERR "$WARN You have " . scalar(@no_pubkey) . " users WITHOUT pubkeys...!\n";
} elsif (@no_pubkey) {
print STDERR "$WARN the following users have no pubkeys:\n", join(",", sort @no_pubkey), "\n";
}
}
print $newkeys_fh "# gitolite end\n";
close $newkeys_fh or die "$ABRT close newkeys failed: $!\n";
# all done; overwrite the file (use cat to avoid perm changes)
system("cat $ENV{HOME}/.ssh/authorized_keys > $ENV{HOME}/.ssh/old_authkeys");
system("cat $ENV{HOME}/.ssh/new_authkeys > $ENV{HOME}/.ssh/authorized_keys")
and die "couldn't write authkeys file\n";
system("rm $ENV{HOME}/.ssh/new_authkeys");
# NOTE: if you're adding code here that is unrelated to setting up authkeys,
# remember that control may not reach here if a sysadm has set
# GL_NO_SETUP_AUTHKEYS in the rc file.

54
src/gl-setup-authkeys Executable file
View file

@ -0,0 +1,54 @@
#!/usr/bin/perl -w
# shim program
# arg-1: keydir
# - an external program populates "keydir" with *all* keys and then
# calls us, giving "keydir" as arg-1
# - we then call gitolite.pm's "setup_authkeys" function to do its thing
# IMPLEMENTATION NOTE: make sure this is in the same directory as
# "gitolite.pm" and all the rest of "src/".
# DISCUSSION:
#
# For now, we will assume *all* the keys are in the keydir passed. The
# setup_authkeys routine factored out from the old gl-compile-conf is
# not setup to take a partial set of keys and create the
# ~/.ssh/authorized_keys file.
#
# Also, there are issues to do with *deleted* keys that need to be taken
# care of.
#
# All in all, unless it is shown to be quite inefficient, I'd much
# prefer processing *all* keys each time there is a change.
# setup
my $bindir = $0;
$bindir =~ s/\/[^\/]+$//;
$bindir = "$ENV{PWD}/$bindir" unless $bindir =~ /^\//;
require "$bindir/gitolite.pm";
# prevent newbie from running it accidentally and clobbering his authkeys
# file!
if (@ARGV and $ARGV[0] eq '-batch') {
shift;
} else {
print STDERR "
This is a cronnable, batchable, program to rewrite ~/.ssh/authorized_keys
using public keys in a given directory.
If you are ABSOLUTELY sure you know what you're doing, here's how:
$0 -batch keydir
where 'keydir' contains a bunch of '*.pub' files\n\n";
exit 1;
}
# quick sanity check and run
my $keydir = shift or die "I need a directory name\n";
-d $keydir or die "$keydir should be a directory\n";
&setup_authkeys($bindir, $keydir);