CentOS监控ssh免密登录
ssh免密登录在带来方便的同时也带来一些问题,那就是不知道什么时间什么人利用ssh免密通道登录服务器了,为此我们需要在sshd的配置文件里设置好详细日志,以便日后回溯。
在CentOS里,sshd的日志文件是保存在/var/log/secure里的,如果不进行特殊设置的话,它只记录一些最简单的信息,比如什么时间哪个账号被人用免密登录的方式登录了,如果这个账号的authorized_keys里有很多key的话,这样的log没有办法告诉你客户到底是用哪个key登录的。
所以,我们需要在sshd的配置文件/etc/ssh/sshd_config文件里找到以下项LogLevel并把它改成LogLevel VERBOSE,这一项的缺省值是INFO,不够详细,所以我们需要把它改成啰嗦模式,这样可以记录更多信息。
改成VERBOSE并重启sshd(service sshd restart)后,我们会在日志文件里看到类似于这样的记录:
Apr 1 10:37:06 hostname sshd[5903]: Found matching RSA key: 83:67:b5:c7:bb:17:4d:06:ca:dc:8b:ca:85:cc:0c:b1
但这样的信息明显不同于我们在authorized_keys里存放的信息,那该怎么办呢?实际上,在sshd的日志文件里存储的只是我们authorized_keys的指纹信息fingerprint,不是真正的key,必须从authorized_keys反算出fingerprint来,才能做对比:
ssh-keygen -E md5 -lf /home/someuser/.ssh/authorized_keys
但是这样依然很麻烦,有没有办法直接告诉我日志里到底是谁登录的呢?为此我们还需要引入一个用Perl写的小程序点击预览。(原作者写的略有问题,在新版的CentOS里必须要求附加md5参数,为此我做了一些小的修改):
#!/usr/bin/perl -w use strict; use diagnostics; use File::Temp; # Matches Fingerprints from sshd logs (sshd on loglevel VERBOSE) against # authorized_keys for the respective user. die "Please specify input file!n" unless ($ARGV[0]); my $fingerprints; my $nav = File::Navigate->new($ARGV[0]); # Store publickey login events my @lines = @{$nav->find(qr/sshd[d+]: Accepted publickey for .+ from .+ port d+/)}; # Process all publickey login events foreach(@lines){ $nav->cursor($_); my $line = $nav->get(); $line =~ /^(.{15}).+sshd[(d+)]: Accepted publickey for (.+) from (.+) port (d+)/; my $date = $1; my $pid = $2; my $user = $3; my $ip = $4; my $port = $5; my $fp = "unknown"; # (yet) # Seek backwards to find matching fingerprint line my $sought = 0; while ((my $seekline = $nav->getprev()) and ($sought++ < 1000)){ if ($seekline =~ /sshd[$pid]: Found matching .+ key: (.+)/){ $fp = $1; last; }elsif($line =~ /sshd[$pid]: Connection from $ip port $port/){ last; } } my $key = get_key($fp, $user); print ""$date";"$user";"$fp";"$key"n"; } sub get_key{ my $fp = shift; $fp = "MD5:" . $fp; my $user = shift; # See if FP is cached if ($fingerprints->{$user}){ if ($fingerprints->{$user}->{$fp}){ return $fingerprints->{$user}->{$fp}; }else{ return "No matching key found."; } } # Else, generate fingerprints from users authorized_keys print STDERR "------> Reading keys for user $usern"; my $home = (getpwnam($user))[7]; open my $fh_in, "<$home/.ssh/authorized_keys" or warn "No such file: $home/.ssh/authorized_keysn"; while (<$fh_in>){ chomp; next unless (/^ssh-/); my $out_fh = File::Temp->new(); print $out_fh "$_n"; close $out_fh; my $fp_raw = `ssh-keygen -E md5 -lf $out_fh`; # Second field of output has the fingerpring my $fp = (split /s+/, $fp_raw)[1]; $fingerprints->{$user}->{$fp} = $_; } if ($fingerprints->{$user}->{$fp}){ return $fingerprints->{$user}->{$fp}; }else{ return "No matching key found."; } } package File::Navigate; use strict; use warnings; =head1 NAME File::Navigate - Navigate freely inside a text file =head1 DESCRIPTION The module is a glorified wrapper for tell() and seek(). It aims to simplify the creation of logfile analysis tools by providing a facility to jump around freely inside the contents of large files without creating the need to slurp excessive amounts of data. =head1 SYNOPSIS use File::Navigate; my $nav = File::Navigate->new('/var/log/messages'); # Read what's below the "cursor": my $first = $nav->get; # Advance the cursor before reading: my $second = $nav->getnext; my $third = $nav->getnext; # Advance the cursor by hand: $nav->next; my $fourth = $nav->get; # Position the cursor onto an arbitrary line: $nav->cursor(10); my $tenth = $nav->get; # Reverse the cursor one line backward: $nav->prev; my $ninth = $nav->get; # Reverse the cursor before reading: my $eigth = $nav->getprev; # Read an arbitrary line: my $sixth = $nav->get(6); =cut our @ISA = qw(Exporter); our @EXPORT_OK = qw(); our $VERSION = '1.0'; =head1 CLASS METHODS =head2 I<new()> Open the file and create an index of the lines inside of it. my $mapper = File::Navigate->new($filename); =cut sub new($){ my $class = shift; my $file; unless ($file = shift){ die "No file specifiedn"; } unless (-e $file){ die "File not found: $filen"; } unless (-r $file){ die "File not readable: $filen"; } my $self = {}; $self->{'cursor'} = 1; $self->{'lineindex'} = {}; $self->{'lineindex'}->{1} = 0; open my $fh, "$file" or die "Can't open $file: $!n"; while (<$fh>){ my $thisline = $.; my $nextline = $thisline + 1; $self->{'lineindex'}->{$nextline} = tell $fh; } $self->{'length'} = scalar(keys %{$self->{'lineindex'}}) - 1 ; $self->{'fh'} = $fh; bless $self; } =head1 OBJECT METHODS =head2 I<count()> Returns the number of lines in the file ("wc -l") my $lines = $nav->count; =cut sub length(){ my $self = shift; return $self->{'length'}; } =head2 I<cursor()> Returns the current cursor position and/or sets the cursor. my $cursor = $nav->cursor(); # Query cursor position. my $cursor = $nav->cursor(10); # Set cursor to line 10 =cut sub cursor($){ my $self = shift; if (my $goto = shift){ $self->{'cursor'} = $goto; } return $self->{'cursor'}; } =head2 I<get()> Gets the line at the cursor position or at the given position. my $line = $nav->get(); # Get line at cursor my $line = $nav->get(10); # Get line 10 =cut sub get($){ my $self = shift; my $fh = $self->{'fh'}; my $getline; $getline = $self->{'cursor'} unless ($getline = shift); if ($getline < 1){ warn "WARNING: Seek before first line."; return undef; }elsif($getline > $self->{'length'}){ warn "WARNING: Seek beyond last line."; return undef; } seek ($fh, $self->{'lineindex'}->{$getline}, 0); my $gotline = <$fh>; chomp $gotline; return $gotline; } =head2 I<next()> Advance the cursor position by one line. Returns the new cursor position. Returns I<undef> if the cursor is already on the last line. my $newcursor = $nav->next(); =cut sub next(){ my $self = shift; if ($self->{'cursor'} == $self->{'length'}){ return undef; } $self->{'cursor'}++; return $self->{'cursor'}; } =head2 Irev()> Reverse the cursor position by one line. Returns the new cursor position. Returns I<undef> if the cursor is already on line 1. my $newcursor = $nav->prev(); =cut sub prev(){ my $self = shift; if ($self->{'cursor'} == 1){ return undef; } $self->{'cursor'}--; return $self->{'cursor'}; } =head2 I<getnext()> Advance to the next line and return it. Returns I<undef> if the cursor is already on the last line. my $newcursor = $nav->getnext(); =cut sub getnext(){ my $self = shift; $self->next or return undef; return $self->get; } =head2 I<getprev()> Reverse to the previous line and return it: Returns I<undef> if the cursor is already on line 1. my $newcursor = $nav->getprev(); =cut sub getprev(){ my $self = shift; $self->prev or return undef; return $self->get; } =head2 I<find()> Find lines containing given regex. Returns array with line numbers. my @lines = @{$nav->find(qr/foo/)}; =cut sub find($){ my $self = shift; my $regex = shift; my @results; for (my $lineno = 1; $lineno <= $self->{'length'}; $lineno++){ my $line = $self->get($lineno); if ($line =~ $regex){ push @results, $lineno; } } return @results; } sub DESTROY(){ my $self = shift; close $self->{'fh'}; } =head1 EXAMPLE I<tac>, the opposite of I<cat>, in Perl using File::Navigate: #!/usr/bin/perl -w use strict; use File::Navigate; foreach my $file (reverse(@ARGV)){ my $nav = File::Navigate->new($file); # Force cursor beyond last line $nav->cursor($nav->length()+1); print $nav->get()."n" while $nav->prev(); } =head1 BUGS Seems to lack proper error handling. =head1 LIMITATIONS Works only on plain text files. Sockets, STDIO etc. are not supported. =head1 PREREQUISITES Tested on Perl 5.6.1. =head1 STATUS Mostly harmless. =head1 AUTHOR Martin Schmitt <mas at scsy dot de> =cut 1;
为此我们给它起名叫match-ssh-keys,赋予它可执行权限(chmod +x match-ssh-keys),然后把它搬到/usr/local/bin里(mv match-ssh-keys /usr/local/bin/),这样我们以后再想查谁通过sshd免密登录过服务器就方便了,我们只需要执行:
match-ssh-keys /var/log/secure
就可以了。
原文出处:segmentfault -> https://segmentfault.com/a/1190000014116009