diff --git a/account/mkusercert.in b/account/mkusercert.in index 3797f9a4517d3387024cd09259aef6ceb19b4937..60ab32a5c2c4c07a8431273d996f0c8e9c9c923e 100644 --- a/account/mkusercert.in +++ b/account/mkusercert.in @@ -1,13 +1,12 @@ #!/usr/bin/perl -wT - # # EMULAB-COPYRIGHT -# Copyright (c) 2000-2004, 2006, 2007 University of Utah and the Flux Group. +# Copyright (c) 2000-2008 University of Utah and the Flux Group. # All rights reserved. # +use strict; use English; use Getopt::Std; -use Fcntl ':flock'; # # Load the Testbed support stuff. @@ -23,13 +22,14 @@ use User; # sub usage() { - print("Usage: mkusercert [-d] [-o] [-p password] \n"); + print("Usage: mkusercert [-d] [-o] [-g] [-p password] \n"); exit(-1); } -my $optlist = "dp:o"; -my $debug = 0; -my $output = 0; -my $password; +my $optlist = "dp:og"; +my $debug = 0; +my $output = 0; +my $password = ""; +my $geniflag = 0; # # Configure variables @@ -40,6 +40,7 @@ my $TBLOGS = "@TBLOGSEMAIL@"; my $OURDOMAIN = "@OURDOMAIN@"; my $CONTROL = "@USERNODE@"; my $BOSSNODE = "@BOSSNODE@"; +my $OU = "sslxmlrpc"; # orgunit # Locals my $USERDIR = USERROOT(); @@ -52,6 +53,11 @@ my $OPENSSL = "/usr/bin/openssl"; my $WORKDIR = "$TB/ssl"; my $SAVEUID = $UID; +# Locals +my $encrypted = 0; +my $db_password = "''"; +my $sh_password = ""; + # # We don't want to run this script unless its the real version. # @@ -89,7 +95,7 @@ sub fatal($); # Rewrite audit version of ARGV to prevent password in mail logs. # my @NEWARGV = @ARGV; -for ($i = 0; $i < scalar(@NEWARGV); $i++) { +for (my $i = 0; $i < scalar(@NEWARGV); $i++) { if ($NEWARGV[$i] eq "-p") { $NEWARGV[$i + 1] = "**********"; } @@ -100,13 +106,17 @@ AuditSetARGV(@NEWARGV); # Parse command arguments. Once we return from getopts, all that should be # left are the required arguments. # -%options = (); +my %options = (); if (! getopts($optlist, \%options)) { usage(); } if (defined($options{"d"})) { $debug = 1; } +if (defined($options{"g"})) { + $OU = "geni"; + $geniflag = 1; +} if (defined($options{"p"})) { $password = $options{"p"}; @@ -119,12 +129,18 @@ if (defined($options{"p"})) { else { die("Tainted argument: $password\n"); } - $password =~ s/\'/\'\\\'\'/g; - $password = "'$password'"; + $db_password = DBQuoteSpecial($password); + $sh_password = $password; + $sh_password =~ s/\'/\'\\\'\'/g; + $sh_password = "$sh_password"; + $encrypted = 1; } if (@ARGV != 1) { usage(); } +if ($geniflag && !$encrypted) { + fatal("GENI certs must be encrypted (use -p password)."); +} my $user = $ARGV[0]; # @@ -172,9 +188,7 @@ TBScriptLock("mkusercert") == 0 or # # Get the user info (the user being operated on). # -my $fullname = $target_user->name(); -my $user_email = $target_user->email(); -my $usr_admin = $target_user->admin(); +my $user_uuid = $target_user->uuid(); my $user_number = $target_user->unix_uid(); my $user_uid = $target_user->uid(); my $user_dbid = $target_user->dbid(); @@ -200,6 +214,27 @@ else { (undef,undef,$default_groupgid,undef) = getgrnam("guest"); } +# +# Need an index file, which is the openssl version of the DB. +# +if (! -e "index.txt") { + open(IND, ">index.txt") + or fatal("Could not create index.txt"); + close(IND); +} + +# +# We have to figure out what the next serial number will be and write +# that into the file. We could let "ca' keep track, but with devel +# trees, we might end up with duplicate serial numbers. +# +my $serial = TBGetUniqueIndex("user_sslcerts"); + +open(SER, ">serial") + or fatal("Could not create new serial file"); +printf SER "%08x\n", $serial; +close(SER); + # # Create a template conf file. We tack on the DN record based on the # user particulars. @@ -210,9 +245,8 @@ system("cp -f $TEMPLATE usercert.cnf") == 0 open(TEMP, ">>usercert.cnf") or fatal("Could not open $TEMPLATE for append: $!"); -print TEMP "OU\t\t= sslxmlrpc\n"; -print TEMP "CN\t\t= $user_uid\n"; -print TEMP "emailAddress\t= $user_uid" . "\@" . "$OURDOMAIN\n"; +print TEMP "OU\t\t= $OU\n"; +print TEMP "CN\t\t= $user_uuid\n"; close(TEMP) or fatal("Could not close usercert.cnf: $!"); @@ -220,53 +254,35 @@ close(TEMP) # Create a client side private key and certificate request. # system("$OPENSSL req -new -config usercert.cnf ". - (defined($password) ? " -passout pass:${password} " : " -nodes ") . + ($encrypted ? " -passout 'pass:${sh_password}' " : " -nodes ") . " -keyout usercert_key.pem -out usercert_req.pem") == 0 or fatal("Could not create certificate request"); -# -# Remove the index file. We keep track of things ourselves. We also have to -# figure out what the next serial number will be and write that into the -# file. We could let "ca' keep track, but with devel trees, we might end -# up with duplicate serial numbers. -# -open(IND, ">index.txt") - or fatal("Could not clear index.txt"); -close(IND); - -my $curidx = TBGetUniqueIndex("user_sslcerts"); - -open(SER, ">serial") - or fatal("Could not create new serial file"); -printf SER "%08x\n", $curidx; -close(SER); - # # Sign the client cert request, creating a client certificate. # $UID = 0; -system("$OPENSSL ca -batch -policy policy_sslxmlrpc -config $CACONFIG ". - " -name CA_usercerts ". +system("$OPENSSL ca -batch -policy policy_sslxmlrpc ". + " -name CA_usercerts -config $CACONFIG ". " -out usercert_cert.pem -cert $EMULAB_CERT -keyfile $EMULAB_KEY ". " -infiles usercert_req.pem") == 0 or fatal("Could not sign certificate request"); $UID = $SAVEUID; # -# For now, there can be just one cert of each kind (encrypted, and -# unencrypted). Might change that at some point. +# We save all of the certs in the DB, but we are not worrying about +# revocation yet. By saving them, we can eventually do that. But we do +# have to set the status bit to revoked though or else we will not know +# later (okay, we can probably figure it out if we had to). # -DBQueryFatal("delete from user_sslcerts ". - "where uid_idx='$user_dbid' and ". - " encrypted=" . (defined($password) ? 1 : 0)); +DBQueryFatal("update user_sslcerts set ". + " status='revoked',revoked=now() ". + "where uid_idx='$user_dbid' and encrypted=$encrypted"); -# -# Create a new entry in the table. -# DBQueryFatal("insert into user_sslcerts ". - "(uid, uid_idx, idx, created, encrypted) values ". - "('$user_uid', '$user_dbid', $curidx, now(), ". - (defined($password) ? 1 : 0) . ")"); + "(uid,uid_idx,idx,created,encrypted,orgunit,status,password) ". + "values ('$user_uid', '$user_dbid', $serial, now(), ". + " $encrypted, '$OU', 'valid', $db_password)"); # # Grab the cert path and strip off the header goo, then insert into @@ -301,12 +317,11 @@ close(PKEY); $pkeystring = DBQuoteSpecial($pkeystring); $certstring = DBQuoteSpecial($certstring); DBQueryFatal("update user_sslcerts set cert=$certstring,privkey=$pkeystring ". - "where uid_idx='$user_dbid' and idx=$curidx"); + "where uid_idx='$user_dbid' and idx=$serial"); # -# Combine the key and the certificate into one file which is installed -# on each remote node and used by tmcc. Installed on boss too so -# we can test tmcc there. +# Combine the key and the certificate into one file which is +# installed in the users home directory. # system("cat usercert_key.pem usercert_cert.pem > usercert.pem") == 0 or fatal("Could not combine cert and key into one file"); @@ -325,7 +340,7 @@ if (! -d $ssldir) { my $target; -if (defined($password)) { +if ($encrypted) { $target = "$ssldir/encrypted.pem"; } else { @@ -338,6 +353,25 @@ system("cp -f usercert.pem $target") == 0 chown($user_number, $default_groupgid, "$target") or fatal("Could not chown $target: $!"); +if ($encrypted) { + # + # Convert to pkcs12 format, strictly for the geni xmlrpc code, whichs + # does not provide a way to give the passphrase for encrypted x509 keys. + # + system("$OPENSSL pkcs12 -export -in usercert.pem -des ". + "-passin 'pass:${sh_password}' -passout 'pass:${sh_password}' ". + "-out usercert.p12 -rand ./.rnd") + == 0 or fatal("Could not create usercert.p12"); + + $target = "$ssldir/encrypted.p12"; + + system("cp -f usercert.p12 $target") == 0 + or fatal("Could not copy usercert.p12 to $target"); + + chown($user_number, $default_groupgid, "$target") + or fatal("Could not chown $target: $!"); +} + TBScriptUnlock(); exit(0); diff --git a/xmlrpc/sslxmlrpc_server.py.in b/xmlrpc/sslxmlrpc_server.py.in index f44ccff7cb538a9460a06ee9768c02af6cc41e9f..9bd39e83ce56c5ee839c92809362846023bc612d 100755 --- a/xmlrpc/sslxmlrpc_server.py.in +++ b/xmlrpc/sslxmlrpc_server.py.in @@ -1,6 +1,5 @@ #!/usr/local/bin/python # -# EMULAB-COPYRIGHT # Copyright (c) 2005-2008 University of Utah and the Flux Group. # All rights reserved. # @@ -221,24 +220,26 @@ class MyServer(SSL.ForkingSSLServer, SimpleXMLRPCDispatcher): # # Get the unix_uid for the user. User must be active. # - def getuserid(self, uid): - userQuery = DBQueryFatal("select unix_uid,status from users " - "where uid=%s", - (uid,)) + def getuserid(self, uuid): + userQuery = DBQueryFatal("select uid,uid_idx,unix_uid,status " + " from users " + "where uid_uuid=%s or uid=%s", + (uuid, uuid)) if len(userQuery) == 0: - return 0 + return (None, None, 0); - if (userQuery[0][1] != "active"): - return -1 + if (userQuery[0][3] != "active"): + return (None, None, -1); - return int(userQuery[0][0]) + return (userQuery[0][0], int(userQuery[0][1]), int(userQuery[0][2])) # # Check if the user is an stud. # - def isstuduser(self, uid): - res = DBQueryFatal("select stud from users where uid=%s", (uid,)) + def isstuduser(self, uid_idx): + res = DBQueryFatal("select stud from users where uid_idx=%s", + (str(uid_idx),)) if len(res) == 0: return 0 @@ -248,26 +249,26 @@ class MyServer(SSL.ForkingSSLServer, SimpleXMLRPCDispatcher): # # Check the certificate serial number. # - def checkcert(self, uid, serial): + def checkcert(self, uid_idx, serial): res = DBQueryFatal("select idx from user_sslcerts " - "where uid=%s and idx=%s", - (uid, serial)) + "where uid_idx=%s and idx=%s and status='valid'", + (str(uid_idx), serial)) return len(res) # # Get the group list for the user. # - def getusergroups(self, user): + def getusergroups(self, uid_idx): result = [] res = DBQueryFatal("select distinct g.gid,g.unix_gid " " from group_membership as m " "left join groups as g on " - " g.pid=m.pid and g.gid=m.gid " - "where m.uid=%s " + " g.pid_idx=m.pid_idx and g.gid_idx=m.gid_idx " + "where m.uid_idx=%s " "order by date_approved asc ", - (user,)) + (str(uid_idx),)) for group in res: result.append(int(group[1])); @@ -283,22 +284,28 @@ class MyServer(SSL.ForkingSSLServer, SimpleXMLRPCDispatcher): if self.debug: self.logit(str(subject)) pass + + # + # The CN might look like UUID,serial so split it up. + # + cnwords = getattr(subject, "CN").split(",") + self.uuid = cnwords[0] - self.user = getattr(subject, "CN"); # # Must be a valid and non-zero unix_uid from the DB. # - self.uid = self.getuserid(self.user) + (self.uid,self.uid_idx,self.unix_uid) = self.getuserid(self.uuid) - if self.uid == 0: - self.logit('No such user: "%s"' % self.user) - raise Exception('No such user: "%s"' % self.user) + if self.unix_uid == 0: + self.logit('No such user: "%s"' % self.uuid) + raise Exception('No such user: "%s"' % self.uuid) - if self.uid == -1: - self.logit('User "%s" is not active' % self.user) - raise Exception('User "%s" is not active' % self.user) + if self.unix_uid == -1: + self.logit('User "%s,%d" is not active' % (self.uid,self.uid_idx)) + raise Exception('User "%s,%d" is not active' % + (self.uid,self.uid_idx)) - self.stud = self.isstuduser(self.user) + self.stud = self.isstuduser(self.uid_idx) if self.stud: try: ALLOWED_PATHS.extend(map(lambda x: @@ -309,13 +316,14 @@ class MyServer(SSL.ForkingSSLServer, SimpleXMLRPCDispatcher): pass pass - self.glist = self.getusergroups(self.user); + self.glist = self.getusergroups(self.uid_idx); if len(self.glist) == 0: - self.logit('No groups for user: "%s"' % self.user) - raise Exception('No groups for user: "%s"' % self.user) + self.logit('No groups for user: "%s,%d"' % (self.uid,self.uid_idx)) + raise Exception('No groups for user: "%s,%d"' % + (self.uid,self.uid_idx)) - self.logit("Connect from %s: %s %s" % - (client[0], self.user, str(self.glist))) + self.logit("Connect from %s: %s,%d %s" % + (client[0], self.uid, self.uid_idx, str(self.glist))) # # Check the certificate serial number. At the moment, the serial @@ -324,19 +332,19 @@ class MyServer(SSL.ForkingSSLServer, SimpleXMLRPCDispatcher): # serial = request.get_peer_cert().get_serial_number() - if self.checkcert(self.user, serial) == 0: + if self.checkcert(self.uid_idx, serial) == 0: self.logit('No such cert with serial "%s"' % serial) raise Exception('No such cert with serial "%s"' % serial) try: os.setgid(self.glist[0]) os.setgroups(self.glist) - os.setuid(self.uid) - pwddb = pwd.getpwuid(self.uid); + os.setuid(self.unix_uid) + pwddb = pwd.getpwuid(self.unix_uid); os.environ["HOME"] = pwddb[5]; - os.environ["USER"] = self.user; - os.environ["LOGNAME"] = self.user; + os.environ["USER"] = self.uid; + os.environ["LOGNAME"] = self.uid; pass except: traceback.print_exc()