Aufgeraeumt
authorMaximilian Wilhelm <max@rfc2324.org>
Fri, 9 Sep 2005 13:46:38 +0000 (13:46 +0000)
committerMaximilian Wilhelm <max@rfc2324.org>
Fri, 9 Sep 2005 13:46:38 +0000 (13:46 +0000)
files/server/RBM/sshKeySync/Merge.pm [new file with mode: 0644]
files/server/RBM/sshKeySync/Merge/Domain.pm [new file with mode: 0644]
files/server/man1/ssh-keysync-merge.1 [new file with mode: 0644]
files/server/man1/upgrade_sshkeysync.1 [new file with mode: 0644]

diff --git a/files/server/RBM/sshKeySync/Merge.pm b/files/server/RBM/sshKeySync/Merge.pm
new file mode 100644 (file)
index 0000000..4f7ada1
--- /dev/null
@@ -0,0 +1,481 @@
+#!/usr/bin/perl -w
+#
+# RBM::sshKeySync::Merge
+#
+# The programm is part of the RBM library, the officiall programms
+# of the 'Rechnerbetrieb Mathematik' (Computersupportcenter at the
+# institute of mathematics) at the Univiersity of Paderborn.
+#
+#   Exit status:
+#    1 => Configuration related problems
+#    2 => Runtime problems
+#    3 => Filesystemstructure related problems
+#    
+#
+#   Copyright (C) 2005, Maximilian Wilhelm <max@rfc2324.org>
+# 
+#   RBM::sshKeySync::Merge is free software; you can redistribute it and/or
+#   modify it under the terms of the GNU General Public License as published
+#   by the Free Software Foundation; either version 2, or (at your option) 
+#   any later version.
+#   
+#   On Debian GNU/Linux systems you can find a copy of the GPL in
+#   /usr/share/common-licenses/GPL
+#
+
+package RBM::sshKeySync::Merge;
+
+use strict;
+use Config::IniFiles;
+use File::Basename;
+use File::Path;
+use Net::DNS;
+use RBM::sshKeySync::Merge::Domain;
+
+# Net::DNS::Resolver
+my $resolver = Net::DNS::Resolver->new;
+
+
+
+##
+# Little big of magic
+sub _options
+{
+       my %args = @_;
+       
+       if ( $args{debug} )
+       {
+               foreach my $arg ( keys %args )
+               {
+                       print STDERR ("RBM::sshKeySync::Merge->_options: $arg => $args{$arg}\n");
+               }
+       }
+       
+       return \%args;
+}
+
+
+##
+# Create new instance
+sub new {
+       my $class = shift;
+
+       my $args = &_options;
+
+       # Default configfile if unset.
+       $args->{configfile} = "/etc/rbm/ssh-keysync-server.conf" unless ( $args->{configfile} );
+
+       if ( $args->{debug} && $args->{quiet} ) {
+               $args->{quiet} = 0;
+               print STDERR "Reenabling verbosity as you requested debug information...\n";
+       }
+       
+       # Parse the configfile
+       my ( $config, $domains ) = init( $args  );
+
+       # Default values if unset
+       $config->{debug} = 0 unless ( $config->{debug} );
+       $config->{verbose} = 1 unless ( $args->{quiet} );
+       $config->{base_dir} = "/var/cache/ssh-keysync" unless ( $config->{base_dir} );
+
+       bless {
+               configfile => $args->{configfile},
+               config => $config,
+               domains => $domains,
+               fh_common_of => undef
+       }, $class;
+}
+
+
+##
+# Check some things
+sub init() {
+       my $args = shift;
+       my $configfile = $args->{configfile};
+
+       my $config;
+       my $domains;
+       
+       # Check/load config file
+       if ( -f $configfile ) {
+               ( $config, $domains ) = loadConfig( $configfile, $args );
+
+               unless( $config ) {
+                       print STDERR "Failed to load config file \"$configfile\", exiting!\n";
+                       exit 1;
+               }
+       } else {
+               print STDERR "Unable to load config file \"$configfile\".\n";
+               print STDERR "File does not exist or is not accessable, exiting\n";
+               exit 1;
+       }
+
+       # Check working directories
+       unless ( -d $config->{base_dir} ) {
+               print STDERR "The directory $config->{base_dir} does not exist, but is neccessary for this tool to work.\n";
+               print STDERR "Please create $config->{base_dir} and allow user 'skeysync' to write there.\n";
+               exit 3;
+       }
+
+       # Check domain config/dirs
+       if ( %{$domains} ) {
+               foreach my $domain ( keys %{$domains} ) {
+                       my $keydir="$config->{base_dir}/$domain/keys";
+                       unless ( -d $keydir ) {
+                               print STDERR "Key directory for domain $domain ($keydir) does not exist.\n";
+                               exit 3;
+                       }
+               }
+       } else {
+               print STDERR "You have to specify at least one domain in $configfile.\n";
+               exit 1
+       }
+
+       return ( $config, $domains );
+}
+
+
+##
+# Parse the config file (using Config::IniFiles) and return a reference
+# to the config-hash and the domains-hash.
+sub loadConfig {
+       my $configfile = shift;
+       my $args = shift;
+
+       my %config;
+       my %domains;
+
+       print "Loading configuration from $configfile...\n" unless ( $args->{quiet} );
+       my $cfg = new Config::IniFiles( -file => "$configfile" );
+
+       unless( $cfg ) {
+               return 0;
+       }
+
+       my @sections = $cfg->Sections();
+
+       foreach my $section ( @sections ) {
+
+               unless ( $section eq "general" ) {
+                       # Initialize/clear values
+                       my $domain = "$section";
+                       my @alt_domains = ();
+                       my %domain_opt;
+                       $domain_opt{separate_outfile} = 1;
+                       $domain_opt{commin_outfile} = 0;
+
+                       my @params = $cfg->Parameters( $section );
+                       foreach my $param ( @params ) {
+                               # Read the value for this parameter.
+                               my $value = $cfg->val( $section, $param );
+
+                               # Are the hosts known in more domains?
+                               if ( $param eq "alt_domains" ) {
+                                       $value =~ s/"//g;
+                                       @alt_domains = split( /[, ]/, "$value" );
+                                       print "Found alternativ domains in domain $domain : $value\n" if ( $args->{debug} );
+                               }
+
+                               # Where to put the known_host information of this domain?
+                               elsif ( $param eq "separate_outfile" or $param eq "common_outfile" ) {
+                                       if ( $value eq "yes" ) {
+                                               $domain_opt{$param} = 1;
+                                       }
+                                       elsif ( $value eq "no" ) {
+                                               $domain_opt{$param} = 0;
+                                       }
+                                       else {
+                                               print STDERR "Value of $param has to be \"yes\" or \"no\", but $value was found, defauting\n" if ( $args->{debug} );
+                                       }
+                               }
+                       }
+
+                       # Save domain data
+                       $domains{$domain} = RBM::sshKeySync::Merge::Domain->new( $domain, \@alt_domains, $domain_opt{separate_outfile}, $domain_opt{common_outfile} );
+                       print "Domain information for $domain successfully loaded...\n" unless ( $args->{quiet} );
+               } 
+
+               else {
+                       my @params = $cfg->Parameters( $section );
+
+                       foreach my $param ( @params ) {
+                               # Read the value for this parameters
+                               my $value = $cfg->val( $section, $param );
+
+                               if ( $param eq "base_dir" )
+                               {
+                                       $config{$param} = $value;
+                                       print "Setting $param = $value\n" if ( $config{debug} );
+                               } else {
+                                       print STDERR "Unkown configuration paramter $param\n" if ( $config{debug} );
+                               }
+                       }
+               } # general
+       }
+
+       return (\%config, \%domains);
+}
+
+
+##
+# Merge all keys within the given domain 
+#
+# merge_domain <domain>
+sub merge_domain {
+       my $self = shift;
+
+       my $config = $self->{config};
+       my $domains = $self->{domains};
+
+       my $domain = shift;
+
+       ##
+       # Check if this domain is valid.
+       unless ( $domains->{$domain} ) {
+               print STDERR "merge_domain: Domain $domain does not exist.\n";
+               return 0;
+       }
+
+       my @alt_domains = @{$domains->{$domain}->get_alt_domains()};
+
+       my $keydir = "$config->{base_dir}/$domain/keys";
+       my $sep_outfile = "$config->{base_dir}/$domain/ssh_known_hosts";
+
+       ##
+       # Get the file names
+       unless ( opendir(KEYDIR, $keydir) ) {
+               print STDERR "Cannot open key directory for domain $domain, skipping this domain... \n";
+               return 0;
+       }
+       my @files= grep {!/^\.{1,2}$/} readdir(KEYDIR);         # weed out "." and ".."
+       closedir(KEYDIR);
+
+       print "========\n Domain: $domain\n" if ( $config->{verbose} );
+       print "  * Found " . scalar(@files) . " key files...\n" if ( $config->{verbose} );
+
+       ##
+       # Check if there are keys
+       unless( scalar(@files) ) {
+               print STDERR " Nothing to do for domain $domain...\n" if ( $config->{verbose} );
+               return 1;
+       }
+
+       ##
+       # Check if the output should be put into a separate outfile for this domain,
+       # open the file is neccessary
+       if ( $domains->{$domain}->get_separate_outfile() ) {
+               unless( open ( SEPARATE_OF, "> $sep_outfile") ) {
+                       print STDERR "Cannot open outfile for domain $domain, skipping this domain...\n";
+                       return 0;
+               }
+       }
+
+       ##
+       # Check if the output should be put into the common outfile
+       # Open the file is neccessary and not allready done.
+       my $fh_common_of = undef;
+       if ( $domains->{$domain}->get_common_outfile() ) {
+               unless( $self->{fh_common_of} ) {
+                       unless( open( $self->{fh_common_of}, "> $config->{base_dir}/ssh_known_hosts" ) ) {
+                               print STDERR "Cannot open common outfile $config->{base_dir}/ssh_known_hosts for writing.";
+                               exit 1;
+                       }
+               }
+               $fh_common_of = $self->{fh_common_of};
+       }
+
+
+       ##
+       # Check if neither a separate outfile or the common outfile should be used
+       unless ( $domains->{$domain}->get_separate_outfile() or
+                       $domains->{$domain}->get_common_outfile() ) {
+               print STDERR "Error: The merged keys of domain $domain should neither be written to a\nseparate file, nor the common outfile. Please check the configuration!\n";
+               return 1;
+       }
+
+       ##
+       # Ok, let's go
+       my $date=`date +"%d.%m.%Y at %H:%M"`;
+       if ( $domains->{$domain}->get_separate_outfile() ) {
+               print SEPARATE_OF "# ssh_known_hosts generated by RBM::sshKeySync::Merge on " . $date . "#\n";
+       }
+
+       ##
+       # Merge all found keys.
+       print "  * Merging keys for domain $domain...\n" if ( $config->{verbose} );
+       foreach my $file (sort @files) {
+               if ( $file =~ m/(\w+).(\w+).key/ ) {
+                       my $keyfile = "$keydir/$file";
+                       my $hostname = $1;
+
+                       ##
+                       # Try to open the file
+                       unless ( open (KEYFILE, "< $keyfile" ) ) {
+                               warn "Cannot read file $file, skipping...\n";
+                               next;
+                       }
+
+                       ##
+                       # Read the first line, if there are more, something is wrong...
+                       my $linecounter = 0;
+                       my $keyline = "";
+                       while ( <KEYFILE> ) {
+                               if ( $linecounter++ eq 0 ) {
+                                       $keyline = $_;
+                               } 
+                       } 
+                       close ( KEYFILE );
+
+                       if ( $linecounter gt 1 ) {
+                               print STDERR "Error: File $domain / $file contains more that one line, skipping...\n";
+                               next;
+                       }
+
+                       ##
+                       # Generate/get everything needed for a ssh_known_hosts entry and check if
+                               # this host exists in the DNS.
+                       # Print out the keyline if, move the key away, if not.
+                       my $hostpart = gen_hostpart( $domain, $hostname, \@alt_domains );
+                       my $ip_string = get_ip_string( "$hostname.$domain", $config );
+
+                       unless ( $ip_string ) {
+                               print STDERR "Warning: No DNS entry found for host $hostname in domain $domain.\n";
+                               print STDERR "Moving this key into the ATTIC...\n";
+                               $self->move_key_to_ATTIC( $domain, $keyfile );
+                               next;
+                       }
+
+                       my $outline = $hostpart . $ip_string . " " . $keyline;
+
+                       if ( $domains->{$domain}->get_separate_outfile() ) {
+                               print SEPARATE_OF $outline;
+                       }
+
+                       # Pre-arranged above.
+                       if ( $fh_common_of ) {
+                               print $fh_common_of $outline;
+                       }
+               } else {
+                       print STDERR "File $file does not have the format <hostname>.<type>.key, skipping...\n";
+               }
+
+       }
+       print "  * finished.\n" if ( $config->{verbose} );
+       close ( SEPARATE_OF );
+}
+
+
+##
+# Loop through all doamins an merge them
+sub merge_all_domains() {
+       my $self = shift;
+       my $config = $self->{config};
+       my $domains = $self->{domains};
+
+       
+       unless ( $self->{fh_common_of} ) {
+               unless( open( $self->{fh_common_of}, "> $config->{base_dir}/ssh_known_hosts" ) ) {
+                       print STDERR "Cannot open common outfile $config->{base_dir}/ssh_known_hosts for writing.";
+                       exit 1;
+               }
+       }
+
+       my $date=`date +"%d.%m.%Y at %H:%M"`;
+
+       my $fh_c_of = $self->{fh_common_of};
+       print $fh_c_of "# ssh_known_hosts generated by RBM::sshKeySync::Merge on " . $date . "#\n";
+
+       foreach my $domain (keys %$domains) {
+               $self->merge_domain( $domain );
+       }
+
+       close( $self->{fh_common_of} );
+}
+
+
+##
+# Generate and return a well formated line for key to fit to ssh_known_hosts
+#
+# Usage: gen_entry( domain, hostname, reference to the alt_domains_array )
+# Format: hostname,hostname.domain,hostname.alt_domain1...hostname.alt_domainN,IP1,IPn key
+sub gen_hostpart {
+       my ( $domain, $hostname, $alt_domain_ref ) = @_;
+       my @alt_domains = @$alt_domain_ref;
+
+       my $hostpart = "$hostname,$hostname.$domain";
+       foreach my $alt_domain (@alt_domains) {
+               $hostpart .= ",$hostname.$alt_domain";
+       }
+
+       return $hostpart;
+}
+
+
+##
+# Return a comma separeted list of ip adress(es) for this host, formated to
+# be added to the $hostpart string in gen_entry.
+#
+# Format: ",IP1,IP2"
+sub get_ip_string {
+       my ( $host, $config ) = shift;
+
+       unless( $host ) {
+               print STDERR "get_ip: No hostname given..." if ( $config->{debug} );
+               return "";
+       }
+
+       my $ipstring = "";
+       my $query = $resolver->search( $host );
+
+       unless ( $query ) {
+               print STDERR "No DNS entry found for host $host\n" if ( $config->{debug} );
+               return "";
+       }
+
+       foreach my $entry ( $query->answer ) {
+               if ( $entry->type eq "A" ) {
+                       $ipstring .= "," . $entry->address;
+               }
+       }
+
+       return $ipstring;
+}
+
+##
+# Move the given keyfile into the domains ATTIC dir
+sub move_key_to_ATTIC {
+       my $self = shift;
+       my $domain = shift;
+       my $keyfile = shift;
+       
+       # Check if $domain exists
+       unless ( $self->{domains}->{$domain} ) {
+               print STDERR "Error: BM::sshKeySync::Merge::move_key_to_ATTIC called with invalid domain $domain\n";
+               return 1;
+       }
+
+       # Check $keyfile
+       unless ( $keyfile ) {
+               print STDERR "Error: RBM::sshKeySync::Merge::move_key_to_ATTIC called without a key to move...\n";
+               return 1;
+       }
+
+       # Check if $keyfile exists
+       unless( -f $keyfile ) {
+               print STDERR "Error: RBM::sshKeySync::Merge::move_key_to_ATTIC: Given keyfile does not exist...\n";
+               return 1;
+       }
+       
+       # Check if the ATTIC directory for this domain exists, create it if not.
+       unless ( -d "$self->{config}->{base_dir}/$domain/ATTIC" ) {
+               print STDERR "ATTIC directory for domain $domain does not exist, creating it.\n" if ( $self->{config}->{verbose} );
+               mkpath( [ "$self->{config}->{base_dir}/$domain/ATTIC" ], 0, 0755 ) 
+                       or die "Cannot create ATTIC dir for domain $domain";
+       }
+
+       rename $keyfile, "$self->{config}->{base_dir}/$domain/ATTIC/" . basename( $keyfile )
+               or print STDERR "Cannot move " . basename( $keyfile ) . " from domain $domain into the ATTIC...\n";
+}
+
+# return true on startup
+1;
diff --git a/files/server/RBM/sshKeySync/Merge/Domain.pm b/files/server/RBM/sshKeySync/Merge/Domain.pm
new file mode 100644 (file)
index 0000000..76f7217
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/perl -w
+#
+# RBM::sshKeySync::Merge::Domain
+#
+# The programm is part of the RBM library, the officiall programms
+# of the 'Rechnerbetrieb Mathematik' (Computersupportcenter at the
+# institute of mathematics) at the Univiersity of Paderborn.
+#
+#   Copyright (C) 2005, Maximilian Wilhelm <max@barbarossa.name>
+# 
+#   RBM::sshKeySync::Merge::Domain is free software; you can redistribute it
+#   and/or modify it under the terms of the GNU General Public License as 
+#   published by the Free Software Foundation; either version 2, or (at your
+#   option) any later version.
+#   
+#   On Debian GNU/Linux systems you can find a copy of the GPL in
+#   /usr/share/common-licenses/GPL
+#
+
+package RBM::sshKeySync::Merge::Domain;
+
+use strict;
+
+##
+# Create no domain object
+sub new {
+       my $this = shift;
+       my $class = ref($this) || $this;
+
+       my ($name, $ref_alt_domains, $separate_of, $common_of )= @_;
+
+       bless { name => $name,
+               alt_domains => $ref_alt_domains,
+               separate_of => $separate_of,
+               common_of => $common_of
+               }, $class;
+}
+
+
+##
+# Get the array with the alternativ domains
+sub get_alt_domains{
+       my $self = shift;
+       my $ref_alt_dom = $self->{alt_domains};
+       
+       return $ref_alt_dom;
+}
+
+
+##
+# Should the output be put into a separate outfile?
+sub get_separate_outfile {
+       my $self = shift;
+       my $s_of = $self->{separate_of};
+
+       return $s_of;
+}
+
+
+##
+# Should the output be put into a separate outfile?
+sub get_common_outfile {
+       my $self = shift;
+       my $c_of = $self->{common_of};
+
+       return $c_of;
+}
+
+
+##
+# Return true on startup
+1;
diff --git a/files/server/man1/ssh-keysync-merge.1 b/files/server/man1/ssh-keysync-merge.1
new file mode 100644 (file)
index 0000000..5fa3e01
--- /dev/null
@@ -0,0 +1,77 @@
+.TH ssh-keysync-merge 1 "2005-09-07" "1.0" 
+
+.\"""
+.\" Short term description
+.SH NAME
+ssh-keysync-merge \- Merge ssh host pub keys to a ssh_known_hosts file
+
+.\"""
+.\" The general command line
+.SH SYNOPSIS
+.B ssh-keysync-merge
+.RB [\| \-c
+.IR configfile \|]
+.RB [\| \-d \||\| \-debug \|]
+.RB [\| \-q \||\| \-quiet \|]
+
+.SH DESCRIPTION
+.B ssh-keysync-merge
+is a tool to merge the ssh public keys of your hosts to one ssh_known_hosts
+file to be distributed to your clients as
+.IR /etc/rbm/ssh_known_hosts .
+
+This mechanism can protect you / your users from connecting to a 
+compromised host, because
+.BR ssh
+will print out a warning if the public key of the host, you're connecting
+to and the saved copy in
+.IR /etc/rbm/ssh_known_hosts
+don't match.
+
+If you're running an automated installation system like Thomas Langes
+.BR FAI
+you can integrate an
+.BR automagic-save-ssh-pub-keys-hook
+into the installation, to help your ssh_known_hosts file to be up to date.
+
+
+.\"""
+.\" Programm options
+.SH OPTIONS
+
+.TP
+.BI \-c \ configfile
+Specify an alternae configuration file to use.
+By default,
+.B ssh-keysync-merge
+uses
+.B /etc/rbm/ssh-keysync-server.conf
+
+.TP
+.B \-d, \-debug
+Activate 'debug' mode.
+
+.TP
+.B \-q, \-quiet
+Let ssh-keysync-merge shut up about informational messages.
+Only errors will be print out in quiet mode.
+
+.\"""
+.\" Files used by this utility
+.SH FILES
+.TP
+.I /etc/rbm/ssh-keysync-server.conf
+Configuration file used by ssh-keysync-merge
+.TP
+.I /var/cache/ssh-keysync
+The domains and ssh public key storage hierachy
+
+.\"""
+.\" You may want to have a look at this:
+.SH "SEE ALSO"
+.BR ssh-keysync (1)
+
+
+.\" The perl monger...
+.SH AUTHOR
+Written by Maximilian Wilhelm <max@rfc2324.org>
diff --git a/files/server/man1/upgrade_sshkeysync.1 b/files/server/man1/upgrade_sshkeysync.1
new file mode 100644 (file)
index 0000000..3222edc
--- /dev/null
@@ -0,0 +1,40 @@
+.TH ssh-keysync-merge 1 "2005-09-08" "1.0" 
+
+.\"""
+.\" Short term description
+.SH NAME
+upgrade_sshkeysync \- Upgrade ssh-keysync config/file system structure
+
+.\"""
+.\" The general command line
+.SH SYNOPSIS
+.B upgrade_sshkeysync
+
+.SH DESCRIPTION
+.B ssh-keysync-merge
+is used to upgrade the configuration and file system infrastructure under
+.IR /var/cache/ssh-keysync
+from a
+.BR ssh-keysync
+version.
+
+.\" Files used by this utility
+.SH FILES
+.TP
+.I /etc/rbm/ssh-keysync-server.conf
+Configuration file upgraded by
+.BR upgrade_sshkeysync
+
+.TP
+.I /var/cache/ssh-keysync
+The domains and ssh public key storage hierachy to be upgraded.
+
+.\"""
+.\" You may want to have a look at this:
+.SH "SEE ALSO"
+.BR ssh-keysync (1),
+.BR ssh-keysync-merge (1)
+
+.\" The perl monger...
+.SH AUTHOR
+Written by Maximilian Wilhelm <max@rfc2324.org>