git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@3358 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
		
			
				
	
	
		
			234 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
			
		
		
	
	
			234 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| # IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
 | |
| #-------------------------------------------------------
 | |
| 
 | |
| =head1
 | |
|    Plugin to handle credentials with good old fashioned priveleged port/host based authentication
 | |
|    May also include xCAT state-sensitive denial/allow
 | |
|    Also controlled by policy table (SECURITY: must document how to harden and make more convenient
 | |
|    through policy table).
 | |
| 
 | |
|    This sounds horrible and most of the time it would be.  However, when dealing with unattended
 | |
|    installs, it is better than nothing.  Apache does not appear to be able to give credence to
 | |
|    privileged ports vs. non-privileged ports on the client, so simple nfs-style authentication is 
 | |
|    not possible.
 | |
| 
 | |
|    The problem with more secure methods and unattended installs is that all rely upon the client to
 | |
|    have a blessed credential, and giving that credential or blessing a credential I can't think of a 
 | |
|    way to feasibly do unattended truly securely, so here we try to mitigate the exposure and 
 | |
|    implement nfs-like security (with the plus of encryption, hopefully)
 | |
| 
 | |
|    Supported command:
 | |
|       getcredentials
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------
 | |
| package xCAT_plugin::credentials;
 | |
| use xCAT::Table;
 | |
| use Data::Dumper;
 | |
| use xCAT::NodeRange;
 | |
| use IO::Socket::INET;
 | |
| use Time::HiRes qw(sleep);
 | |
| 
 | |
| use xCAT::Utils;
 | |
| 
 | |
| use xCAT::MsgUtils;
 | |
| use Getopt::Long;
 | |
| 
 | |
| #-------------------------------------------------------
 | |
| 
 | |
| =head3  handled_commands
 | |
| 
 | |
| Return list of commands handled by this plugin
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------
 | |
| 
 | |
| sub handled_commands
 | |
| {
 | |
|     return {getcredentials => "credentials" };
 | |
| }
 | |
| 
 | |
| #-------------------------------------------------------
 | |
| 
 | |
| =head3  process_request
 | |
| 
 | |
|   Process the command
 | |
| 
 | |
| =cut
 | |
| 
 | |
| #-------------------------------------------------------
 | |
| my $callback;
 | |
| sub process_request
 | |
| {
 | |
| 
 | |
|     my $request  = shift;
 | |
|     $callback = shift;
 | |
|     my $command  = $request->{command}->[0];
 | |
|     my $args     = $request->{arg};
 | |
|     my $envs     = $request->{env};
 | |
|     my $client;
 | |
|     #Because clients may be stuck with stunnel, we cannot presume they 
 | |
|     #can explicitly bind to a low port number as a client
 | |
|     #unless ($request and $request->{'_xcat_clientport'} and $request->{'_xcat_clientport'}->[0] and  $request->{'_xcat_clientport'}->[0] < 1000) {
 | |
|     #   print Dumper($request);
 | |
|     #   return; #only accept requests from privileged ports
 | |
|     #}
 | |
|     if ($request->{'_xcat_clienthost'}) {
 | |
|        $client = $request->{'_xcat_clienthost'}->[0];
 | |
|     }
 | |
|     my %rsp;
 | |
|     # do your processing here
 | |
|     # return info
 | |
| 
 | |
|     if ($client) { ($client) = noderange($client) };
 | |
|     unless ($client) { #Not able to do host authentication, abort
 | |
|        return;
 | |
|     }
 | |
|     unless ($request->{'callback_port'} and $request->{'callback_port'}->[0] and $request->{'callback_port'}->[0] < 1024) {
 | |
|        print "WT\n";
 | |
|        return;
 | |
|     }
 | |
|     unless (ok_with_node($client,$request->{'callback_port'}->[0])) {
 | |
|        return;
 | |
|     }
 | |
| 
 | |
|     my @params_to_return = @{$request->{arg}};
 | |
|     $rsp->{data}=[];
 | |
|     my $tmpfile;
 | |
|     my @filecontent;
 | |
|     my $retdata;
 | |
|     my $tfilename;
 | |
| 
 | |
| 	my $root;
 | |
|     if (xCAT::Utils->isAIX()) {
 | |
|         $root = "";
 | |
|     } else {
 | |
|         $root = "/root";
 | |
|     }
 | |
| 
 | |
|     foreach (@params_to_return) {
 | |
| 
 | |
|        if (/ssh_root_key/) { 
 | |
|           unless (-r "$root/.ssh/id_rsa") {
 | |
|             push @{$rsp->{'error'}},"Unable to read root's private ssh key";
 | |
|             next;
 | |
|           }
 | |
|           $tfilename = "$root/.ssh/id_rsa";
 | |
| 
 | |
|        } elsif (/xcat_server_cred/) {
 | |
|           unless (-r "/etc/xcat/cert/server-cred.pem") {
 | |
|             push @{$rsp->{'error'}},"Unable to read root's private xCAT key";
 | |
|             next;
 | |
|           }
 | |
|           $tfilename = "/etc/xcat/cert/server-cred.pem";
 | |
| 
 | |
|        } elsif (/xcat_client_cred/ or /xcat_root_cred/) {
 | |
|           unless (-r "$root/.xcat/client-cred.pem") {
 | |
|             push @{$rsp->{'error'}},"Unable to read root's private xCAT key";
 | |
|             next;
 | |
|           }
 | |
|           $tfilename = "$root/.xcat/client-cred.pem";
 | |
| 
 | |
|        } elsif (/ssh_dsa_hostkey/) {
 | |
|           unless (-r "/etc/xcat/hostkeys/ssh_host_dsa_key") {
 | |
|              push @{$rsp->{'error'}},"Unable to read private DSA key from /etc/xcat/hostkeys";
 | |
|              next;
 | |
|           }
 | |
|           $tfilename="/etc/xcat/hostkeys/ssh_host_dsa_key";
 | |
| 
 | |
|        } elsif (/ssh_rsa_hostkey/) {
 | |
|           unless (-r "/etc/xcat/hostkeys/ssh_host_rsa_key") {
 | |
|              push @{$rsp->{'error'}},"Unable to read private RSA key from /etc/xcat/hostkeys";
 | |
|              next;
 | |
|           }
 | |
|           $tfilename="/etc/xcat/hostkeys/ssh_host_rsa_key";
 | |
| 
 | |
|        } elsif (/xcat_cfgloc/) {
 | |
|           unless (-r "/etc/xcat/cfgloc") {
 | |
|             push @{$rsp->{'error'}},"Unable to read xCAT database location";
 | |
|             next;
 | |
|           }
 | |
|           $tfilename = "/etc/xcat/cfgloc";
 | |
| 
 | |
|        } elsif (/krb5_keytab/) { #TODO: MUST RELAY TO MASTER
 | |
|            my $princsuffix=$request->{'_xcat_clientfqdn'}->[0];
 | |
|            $ENV{KRB5CCNAME}="/tmp/xcat/krb5cc_xcat_$$";
 | |
|            system('kinit -S kadmin/admin -k -t /etc/xcat/krb5_pass xcat/admin');
 | |
|            system("kadmin -p xcat/admin -c /tmp/xcat/krb5cc_xcat_$$ -q 'delprinc -force host/$princsuffix'");
 | |
|            system("kadmin -p xcat/admin -c /tmp/xcat/krb5cc_xcat_$$ -q 'delprinc -force nfs/$princsuffix'");
 | |
|            system("kadmin -p xcat/admin -c /tmp/xcat/krb5cc_xcat_$$ -q 'addprinc -randkey host/$princsuffix'");
 | |
|            system("kadmin -p xcat/admin -c /tmp/xcat/krb5cc_xcat_$$ -q 'addprinc -randkey nfs/$princsuffix'");
 | |
|            unlink "/tmp/xcat/keytab.$$";
 | |
|            system("kadmin -p xcat/admin -c /tmp/xcat/krb5cc_xcat_$$ -q 'ktadd -k /tmp/xcat/keytab.$$ nfs/$princsuffix'");
 | |
|            system("kadmin -p xcat/admin -c /tmp/xcat/krb5cc_xcat_$$ -q 'ktadd -k /tmp/xcat/keytab.$$ host/$princsuffix'");
 | |
|            system("kdestroy -c /tmp/xcat/krb5cc_xcat_$$");
 | |
|            unlink("/tmp/xcat/krb5cc_xcat_$$");
 | |
|            my $keytab;
 | |
|            open($keytab, "/tmp/xcat/keytab.$$");
 | |
|            my $tabdata="\n";
 | |
|            my $buf;
 | |
|            require MIME::Base64;
 | |
|            while (read($keytab,$buf,1140)) {
 | |
|                $tabdata.=MIME::Base64::encode_base64($buf);
 | |
|            }
 | |
|            push @{$rsp->{'data'}},{content=>[$tabdata],desc=>[$_]};
 | |
|            unlink "/tmp/xcat/keytab.$$";
 | |
|            next;
 | |
|        } else {
 | |
|           next;
 | |
|        }
 | |
| 	#check if the file exists or not
 | |
|        if (defined $tfilename && -r $tfilename) {
 | |
|            open($tmpfile,$tfilename);
 | |
|            @filecontent=<$tmpfile>;
 | |
|            close($tmpfile);
 | |
|            $retdata = "\n".join('',@filecontent);
 | |
|            push @{$rsp->{'data'}},{content=>[$retdata],desc=>[$_]};
 | |
|            $retdata="";
 | |
|            @filecontent=();
 | |
|        }
 | |
|     }
 | |
|     if (defined $rsp->{data}->[0]) {
 | |
| 	#if we got the data from the file, send the data message to the client
 | |
|         xCAT::MsgUtils->message("D", $rsp, $callback, 0);
 | |
|     }else {
 | |
| 	#if the file doesn't exist, send the error message to the client
 | |
|         delete $rsp->{'data'};
 | |
|         xCAT::MsgUtils->message("E", $rsp, $callback, 0);
 | |
|     }
 | |
|     return;
 | |
| }
 | |
| 
 | |
| sub ok_with_node {
 | |
|    my $node = shift;
 | |
|    #Here we connect to the node on a privileged port (in the clear) and ask the
 | |
|    #node if it just asked us for credential.  It's convoluted, but it is 
 | |
|    #a convenient way to see if root on the ip has approved requests for
 | |
|    #credential retrieval.  Given the nature of the situation, it is only ok
 | |
|    #to assent to such requests before users can log in.  During postscripts
 | |
|    #stage in stateful nodes and during the rc scripts of stateless boot
 | |
|    my $select = new IO::Select;
 | |
|    #sleep 0.5; # gawk script race condition might exist, try to lose just in case
 | |
|    my $sock = new IO::Socket::INET(PeerAddr=>$node,
 | |
|                                      Proto => "tcp",
 | |
|                                      PeerPort => shift);
 | |
|    my $rsp;
 | |
|    unless ($sock) {return 0};
 | |
|    $select->add($sock);
 | |
|    print $sock "CREDOKBYYOU?\n";
 | |
|    unless ($select->can_read(5)) { #wait for data for up to five seconds
 | |
|       return 0;
 | |
|    }
 | |
|    my $response = <$sock>;
 | |
|    chomp($response);
 | |
|    if ($response eq "CREDOKBYME") {
 | |
|       return 1;
 | |
|    }
 | |
|    return 0;
 | |
| }
 | |
|                                     
 | |
|    
 | |
| 1;
 |