From c5f342a835392826c1f7def3491c526521716b42 Mon Sep 17 00:00:00 2001 From: Sitaram Chamarty Date: Sun, 13 Nov 2011 17:37:01 +0530 Subject: [PATCH] sshkeys-lint total rewrite, and gl-setup now uses it ...in "admin check" mode --- src/gl-setup | 6 ++ src/sshkeys-lint | 241 ++++++++++++++++++++++++++++++----------------- 2 files changed, 158 insertions(+), 89 deletions(-) diff --git a/src/gl-setup b/src/gl-setup index 6215474..e7f4385 100755 --- a/src/gl-setup +++ b/src/gl-setup @@ -136,3 +136,9 @@ gl-compile-conf -q # now that the admin repo is created, you have to set the hooks properly; best # do it by running install again gl-install -q + +# ---- + +# the never-ending quest to help with bloody ssh issues... +cd $GL_ADMINDIR/keydir +[ -n "$pubkey_file" ] && $GL_BINDIR/sshkeys-lint < $HOME/.ssh/authorized_keys -q -a $admin_name diff --git a/src/sshkeys-lint b/src/sshkeys-lint index 3d9de51..d7e3e18 100755 --- a/src/sshkeys-lint +++ b/src/sshkeys-lint @@ -1,109 +1,172 @@ -#!/usr/bin/perl -w - +#!/usr/bin/perl use strict; -our (%users, %linenos, %pubkeyfiles); +use warnings; -my $thisbin = $0; -$thisbin = "$ENV{PWD}/$thisbin" unless $thisbin =~ /^\//; +# complete rewrite of the sshkeys-lint program. Usage has changed, see +# usage() function or run without arguments. -usage() unless $ARGV[0] and -f $ARGV[0]; -my @authlines = filelines($ARGV[0]); -my $lineno = 0; -for (@authlines) -{ - $lineno++; - my $in_gs = (/^# gitolite start/ .. /^# gitolite end/); - next if /\# gitolite (start|end)/; +use Getopt::Long; +my $admin = 0; +my $quiet = 0; +GetOptions('admin|a=s' => \$admin, 'quiet|q' => \$quiet); - my $user = ""; - $user = "host $1" if /^command=.*gl-mirror-shell (\S+?)"/; - $user = "user $1" if /^command=.*gl-auth-command (\S+?)"/; - $user = "shell user $1" if /^command=.*gl-auth-command -s (\S+?)"/; +use Data::Dumper; +$Data::Dumper::Deepcopy = 1; +$|++; - die "line $lineno: unrecognised line\n" unless /^(?:command=".*(?:gl-mirror-shell|gl-auth-command(?: -s)?) (?:\S+?)"\S+ )?(?:ssh-rsa|ssh-dss) (\S+)/; - my $key = $1; - if ($linenos{$key}) { - warn "authkeys file line $lineno is repeat of line $linenos{$key}, will be ignored by server sshd\n"; - next; +my $in_gl_section = 0; +my $warnings = 0; + +sub dbg { + use Data::Dumper; + for my $i (@_) { + print STDERR "DBG: " . Dumper($i); } - $linenos{$key} = $lineno; - $users{$key} = ($user ? "maps to $user" : "gets you a command line"); } -print "\n"; +sub msg { + my $warning = shift; + return if $quiet and not $warning; + $warnings++ if $warning; + print "sshkeys-lint: " . ( $warning ? "WARNING: " : "" ) . $_ for @_; +} -# all *.pub in current dir should be exactly one line, starting with ssh-rsa -# or ssh-dss +@ARGV or not -t or usage(); +our @pubkeyfiles = @ARGV; @ARGV = (); -my @pubkeys = sort glob("*.pub"); -die "no *.pub files here\n" unless @pubkeys; -for my $pub (@pubkeys) { - my @lines = grep { ! /^\s*#/ } filelines($pub); - die "$pub has more than one line\n" if @lines > 1; - die "$pub does not start with ssh-rsa or ssh-dss\n" unless $lines[0] =~ /^(?:ssh-rsa|ssh-dss) (\S+)/; - my $key = $1; - print "$pub seems to be A COPY OF $pubkeyfiles{$key}\n" if $pubkeyfiles{$key}; - $pubkeyfiles{$key} ||= $pub; - if ($users{$key}) { - print "$pub $users{$key}\n"; +# ------------------------------------------------------------------------ + +my @authkeys; +my %seen_fprints; +my %pkf_by_fp; +msg 0, "==== checking authkeys file:\n"; +fill_authkeys(); # uses up STDIN + +if ($admin) { + my $fp = fprint("$admin.pub"); + my $fpu = ( $seen_fprints{$fp}{user} || 'no access' ); + # dbg("fpu = $fpu, admin=$admin"); + die "\t\t*** FATAL ***\n" . + "$admin.pub maps to $fpu, not $admin.\n" . + "You will not be able to access gitolite with this key.\n" . + "Look for the 'ssh troubleshooting' link in http://sitaramc.github.com/gitolite/.\n" + if $fpu ne "user $admin"; +} + +msg 0, "==== checking pubkeys:\n" if @pubkeyfiles; +for my $pkf (@pubkeyfiles) { + my $fp = fprint($pkf); + msg 1, "$pkf appears to be a COPY of $pkf_by_fp{$fp}\n" if $pkf_by_fp{$fp}; + $pkf_by_fp{$fp} ||= $pkf; + my $fpu = ( $seen_fprints{$fp}{user} || 'no access' ); + msg 0, "$pkf maps to $fpu\n"; +} + +if ($warnings) { + print "\n$warnings warnings found\n"; +} + +exit $warnings; + +# ------------------------------------------------------------------------ +sub fill_authkeys { + while (<>) { + my $seq = $.; + next if ak_comment($_); # also sets/clears $in_gl_section global + my $fp = fprint($_); + my $user = user($_); + + check($seq, $fp, $user); + + $authkeys[$seq]{fprint} = $fp; + $authkeys[$seq]{ustatus} = $user; + } +} + +sub check { + my ($seq, $fp, $user) = @_; + + msg 1, "line $seq, $user key found *outside* gitolite section!\n" + if $user =~ /^user / and not $in_gl_section; + + msg 1, "line $seq, $user key found *inside* gitolite section!\n" + if $user !~ /^user / and $in_gl_section; + + if ($seen_fprints{$fp}) { + msg 1, "authkeys line $seq ($user) will be ignored by sshd; " . + "same key found on line " . + $seen_fprints{$fp}{seq} . " (" . + $seen_fprints{$fp}{user} . ")\n"; + return; + } + + $seen_fprints{$fp}{seq} = $seq; + $seen_fprints{$fp}{user} = $user; +} + +sub user { + my $user = ''; + $user ||= "user $1" if /^command=.*gl-auth-command (.*?)"/; + $user ||= "host $1" if /^command=.*gl-mirror-shell (.*?)"/; + $user ||= "unknown command" if /^command/; + $user ||= "shell access" if /^ssh-(rsa|dss)/; + + return $user; +} + +sub ak_comment { + my $_ = shift; + $in_gl_section = 1 if /^# gitolite start/; + $in_gl_section = 0 if /^# gitolite end/; + die "gitosis? what's that?\n" if /^#.*gitosis/; + return /^\s*#/; +} + +sub fprint { + my $_ = shift; + my ($fh, $tempfn, $in); + if (/ssh-(dss|rsa) /) { + # an actual key was passed. Since ssh-keygen requires an actual file, + # make a temp file to take the data and pass on to ssh-keygen + s/^.* (ssh-dss|ssh-rsa)/$1/; + use File::Temp qw(tempfile); + ($fh, $tempfn) = tempfile(); + $in = $tempfn; + print $fh $_; + close $fh; } else { - print "$pub has NO ACCESS to the server\n"; + # a filename was passed + $in = $_; } + # dbg("in = $in"); + -f $in or die "file not found: $in\n"; + open($fh, "ssh-keygen -l -f $in |") or die "could not fork: $!\n"; + my $fp = <$fh>; + # dbg("fp = $fp"); + close $fh; + unlink $tempfn if $tempfn; + die "fprint failed\n" unless $fp =~ /([0-9a-f][0-9a-f](:[0-9a-f][0-9a-f])+)/; + return $1; } -print <; -} - -sub usage -{ - print STDERR <