summaryrefslogtreecommitdiffstats
path: root/src/commands/sshkeys-lint
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 14:17:27 +0000
commitaae1a14ea756102251351d96e2567b4986d30e2b (patch)
treea1af617672e26aee4c1031a3aa83e8ff08f6a0a5 /src/commands/sshkeys-lint
parentInitial commit. (diff)
downloadgitolite3-upstream.tar.xz
gitolite3-upstream.zip
Adding upstream version 3.6.12.upstream/3.6.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/commands/sshkeys-lint')
-rwxr-xr-xsrc/commands/sshkeys-lint176
1 files changed, 176 insertions, 0 deletions
diff --git a/src/commands/sshkeys-lint b/src/commands/sshkeys-lint
new file mode 100755
index 0000000..3f07b13
--- /dev/null
+++ b/src/commands/sshkeys-lint
@@ -0,0 +1,176 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+# complete rewrite of the sshkeys-lint program. Usage has changed, see
+# usage() function or run without arguments.
+use lib $ENV{GL_LIBDIR};
+use Gitolite::Common;
+
+use Getopt::Long;
+my $admin = 0;
+my $quiet = 0;
+my $help = 0;
+GetOptions( 'admin|a=s' => \$admin, 'quiet|q' => \$quiet, 'help|h' => \$help );
+
+use Data::Dumper;
+$Data::Dumper::Deepcopy = 1;
+$|++;
+
+my $in_gl_section = 0;
+my $warnings = 0;
+my $KEYTYPE_REGEX = qr/\b(?:ssh-(?:rsa|dss|ed25519)|ecdsa-sha2-nistp(?:256|384|521))\b/;
+
+sub msg {
+ my $warning = shift;
+ return if $quiet and not $warning;
+ $warnings++ if $warning;
+ print "sshkeys-lint: " . ( $warning ? "WARNING: " : "" ) . $_ for @_;
+}
+
+usage() if $help;
+
+our @pubkeyfiles = @ARGV; @ARGV = ();
+my $kd = "$ENV{HOME}/.gitolite/keydir";
+if ( not @pubkeyfiles ) {
+ chomp( @pubkeyfiles = `find $kd -type f -name "*.pub" | sort` );
+}
+
+if ( -t STDIN ) {
+ @ARGV = ("$ENV{HOME}/.ssh/authorized_keys");
+}
+
+# ------------------------------------------------------------------------
+
+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 = ( $fp && $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://gitolite.com/gitolite/ssh.html.\n"
+ if $fpu ne "user $admin";
+ #>>>
+}
+
+msg 0, "==== checking pubkeys:\n" if @pubkeyfiles;
+for my $pkf (@pubkeyfiles) {
+ # get the short name for the pubkey file
+ ( my $pkfsn = $pkf ) =~ s(^$kd/)();
+
+ my $fp = fprint($pkf);
+ next unless $fp;
+ msg 1, "$pkfsn appears to be a COPY of $pkf_by_fp{$fp}\n" if $pkf_by_fp{$fp};
+ $pkf_by_fp{$fp} ||= $pkfsn;
+ my $fpu = ( $seen_fprints{$fp}{user} || 'no access' );
+ msg 0, "$pkfsn 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=.*gitolite-shell (.*?)"/;
+ $user ||= "unknown command" if /^command/;
+ $user ||= "shell access" if /$KEYTYPE_REGEX/;
+
+ return $user;
+}
+
+sub ak_comment {
+ local $_ = 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 {
+ local $_ = shift;
+ my ($fp, $output);
+ if ( /$KEYTYPE_REGEX/ ) {
+ # an actual key was passed. ssh-keygen CAN correctly handle options on
+ # the front of the key, so don't bother to strip them at all.
+ ($fp, $output) = ssh_fingerprint_line($_);
+ } else {
+ # a filename was passed
+ ($fp, $output) = ssh_fingerprint_file($_);
+ # include the line of input as well, as it won't always be included by the ssh-keygen command
+ warn "Bad line: $_\n" unless $fp;
+ }
+ # sshkeys-lint should only be run by a trusted admin, so we can give the output here.
+ warn "$output\n" unless $fp;
+ return $fp;
+}
+
+# ------------------------------------------------------------------------
+=for usage
+
+Usage: gitolite sshkeys-lint [-q] [optional list of pubkey filenames]
+ (optionally, STDIN can be a pipe or redirected from a file; see below)
+
+Look for potential problems in ssh keys.
+
+sshkeys-lint expects:
+ - the contents of an authorized_keys file via STDIN, otherwise it uses
+ \$HOME/.ssh/authorized_keys
+ - one or more pubkey filenames as arguments, otherwise it uses all the keys
+ found (recursively) in \$HOME/.gitolite/keydir
+
+The '-q' option will print only warnings instead of all mappings.
+
+Note that this runs ssh-keygen -l for each line in the authkeys file and each
+pubkey in the argument list, so be wary of running it on something huge. This
+is meant for troubleshooting.
+
+=cut