#!/usr/local/bin/python import sys import os import pwd from fedd_services import * from M2Crypto import SSL, X509 import M2Crypto.httpslib from ZSI import SoapWriter from fedd_util import fedid, fedd_ssl_context, pack_soap, pack_id from optparse import OptionParser, OptionValueError # Turn off the matching of hostname to certificate ID SSL.Connection.clientPostConnectionCheck = None class IDFormatException(RuntimeError): pass class access_method: """Encapsulates an access method generically.""" (type_ssh, type_x509, type_pgp) = ('sshPubkey', 'X509', 'pgpPubkey') default_type = type_ssh def __init__(self, buf=None, type=None, file=None): self.buf = buf if type != None: self.type = type else: self.type = access_method.default_type if file != None: self.readfile(file) def readfile(self, file, type=None): f = open(file, "r") self.buf = f.read(); f.close() if type == None: if self.type == None: self.type = access_method.default_type else: self.type = type; class node_desc: def __init__(self, image, hardware, count=1): if getattr(image, "__iter__", None) == None: if image == None: self.image = [ ] else: self.image = [ image ] else: self.image = image if getattr(hardware, "__iter__", None) == None: if hardware == None: self.hardware = [ ] else: self.hardware = [ hardware ] else: self.hardware = hardware if count != None: self.count = int(count) else: self.count = 1 class fedd_client_opts(OptionParser): """Encapsulate option processing in this class, rather than in main""" def __init__(self): OptionParser.__init__(self, usage="%prog [opts] (--help for details)", version="0.1") self.set_defaults(url="https://localhost:23235", anonymous=False, serialize_only=False, use_fedid=False, debug=0) self.add_option("-a","--anonymous", action="store_true", dest="anonymous", help="Do not include a user in the request") self.add_option("-c","--cert", action="store", dest="cert", type="string", help="my certificate file") self.add_option("-d", "--debug", action="count", dest="debug", help="Set debug. Repeat for more information") self.add_option("-f","--useFedid", action="store_true", dest="use_fedid", help="Use a fedid derived from my certificate as user identity") self.add_option("-k", "--sshKey", action="callback", type="string", callback=add_ssh_key, callback_args=(access_keys,), help="ssh key for access (can be supplied more than once") self.add_option("-K", "--x509Key", action="callback", type="string", callback=add_x509_cert, callback_args=(access_keys,), help="X509 certificate for access " + \ "(can be supplied more than once") self.add_option("-l","--label", action="store", dest="label", type="string", help="Label for output") self.add_option("-n", "--node", action="callback", type="string", callback=add_node_desc, callback_args=(node_descs,), help="Node description: image:hardware[:count]") self.add_option("-p", "--project", action="store", dest="project", type="string", help="Use a project request with this project name") self.add_option("-s", "--serializeOnly", action="store_true", dest="serialize_only", help="Print the SOAP request that would be sent and exit") self.add_option("-t", "--testbed", action="store", dest="testbed", type="string", help="Testbed identifier (URI) to contact (required)") self.add_option("-T","--trusted", action="store", dest="trusted", type="string", help="Trusted certificates (required)") self.add_option("-u", "--url", action="store", dest="url", type="string", help="URL to connect to (default %default)") self.add_option("-U", "--username", action="store", dest="user", type="string", help="Use this username instead of the uid") self.add_option("--trace", action="store_const", dest="tracefile", const=sys.stderr, help="Print SOAP exchange to stderr") def print_ID(id, out=sys.stdout, prefix=None, no_nl=False): """Print an ID element (which may have many types) to as a string If out is given, that is the output destination. If prefix is given that string is prepended. If no_nl is given, the newline is not printed. """ # This comprehension selects all accessors from id of the form # get_element_*, calls those in turn and returns a list of non-None ones. # For valid ID elements that will contain exactly one element, which we # print. Anything else is an error. names = [ getattr(id, method)() for method in dir(id) \ if method.startswith("get_element_") \ and callable(getattr(id, method)) and getattr(id,method)() != None ] if len(names) == 1: if prefix == None: print >>out, names[0], else: print >>out, "%s%s" % (prefix,names[0]), if not no_nl: print >>out else: raise IDFormatException() def print_users(ul, out=sys.stdout): """ Print a list of users, with "User: " prepended, one to a line on out. @param ul sequence of user elements @param out output object. Defaults to sys.stdout """ if ul != None: for u in ul: print_ID(u.get_element_userID(), out, "User: ") def print_response_as_testbed(resp, label, out=sys.stdout): """Print the response as input to the splitter script""" emulab_data = { "Boss: ": "get_element_boss", "OpsNode: ": "get_element_ops", "Domain: ": "get_element_domain", "FileServer: ": "get_element_fileServer", "EventServer: ": "get_element_eventServer", } if (label != None): print >> out, "[%s]" % label e = resp.get_element_emulab() if ( e != None): p = e.get_element_project() if ( p != None ): print_ID(p.get_element_name(), out, "Project: ") print_users(p.get_element_user(), out) for k, m in emulab_data.iteritems(): method = getattr(e, m); if method != None and callable(method): print >> out, "%s%s" % (k, method()) for a in e.get_element_fedAttr(): print >>out, "%s: %s" % \ (a.get_element_attribute(), a.get_element_value()) def add_ssh_key(option, opt_str, value, parser, access_keys): try: access_keys.append(access_method(file=value, type=access_method.type_ssh)) except IOError, (errno, strerror): raise OptionValueError("Cannot generate sshPubkey from %s: %s (%d)" % (value,strerror,errno)) def add_x509_cert(option, opt_str, value, parser, access_keys): try: access_keys.append(access_method(file=value, type=access_method.type_x509)) except IOError, (errno, strerror): raise OptionValueError("Cannot read x509 cert from %s: %s (%d)" % (value,strerror,errno)) def add_node_desc(option, opt_str, value, parser, node_descs): def none_if_zero(x): if len(x) > 0: return x else: return None params = map(none_if_zero, value.split(":")); if len(params) < 4 and len(params) > 1: node_descs.append(node_desc(*params)) else: raise OptionValueError("Bad node description: %s" % value) def get_user_info(access_keys): pw = pwd.getpwuid(os.getuid()); try_cert=None user = None if pw != None: user = pw[0] try_cert = "%s/.ssl/emulab.pem" % pw[5]; if not os.access(try_cert, os.R_OK): try_cert = None if len(access_keys) == 0: for k in ["%s/.ssh/id_rsa.pub", "%s/.ssh/id_dsa.pub", "%s/.ssh/identity.pub"]: try_key = k % pw[5]; if os.access(try_key, os.R_OK): access_keys.append(access_method(file=try_key, type=access_method.type_ssh)) break return (user, try_cert) access_keys = [] node_descs = [] proj = None # Process the options using the customized option parser defined above (opts, args) = fedd_client_opts().parse_args() if opts.testbed == None: parser.error("--testbed is required") if opts.trusted != None: if ( not os.access(opts.trusted, os.R_OK) ) : sys.exit("Cannot read trusted certificates (%s)" % opts.trusted) else: parser.error("--trusted is required") if opts.debug > 0: opts.tracefile=sys.stderr (user, cert) = get_user_info(access_keys) if opts.user: user = opts.user if opts.cert != None: cert = opts.cert if cert == None: sys.exit("No certificate given (--cert) or found") if os.access(cert, os.R_OK): fid = fedid(file=cert) if opts.use_fedid == True: user = fid else: sys.exit("Cannot read certificate (%s)" % cert) context = None while context == None: try: context = fedd_ssl_context(cert, opts.trusted) except SSL.SSLError, e: # Yes, doing this on message type is not ideal. The string comes from # OpenSSL, so check there is this stops working. if e.message == "bad decrypt": print >>sys.stderr, "Bad Passphrase given." else: raise loc = feddServiceLocator(); port = loc.getfeddPortType(opts.url, transport=M2Crypto.httpslib.HTTPSConnection, transdict={ 'ssl_context' : context }, tracefile=opts.tracefile) req = RequestAccessRequestMessage() msg = { 'allocID': pack_id('test alloc'), 'destinationTestbed': pack_id(opts.testbed), 'access' : [ { a.type: a.buf } for a in access_keys ], } if len(node_descs) > 0: msg['resources'] = { 'node': [ { 'image': n.image , 'hardware': n.hardware, 'count': n.count, } for n in node_descs], } if opts.project != None: if not opts.anonymous and user != None: msg['project'] = { 'name': pack_id(opts.project), 'user': [ { 'userID': pack_id(user) } ], } else: msg['project'] = { 'name': pack_id(opts.project) } else: if not opts.anonymous and user != None: msg['user'] = [ { 'userID': pack_id(user) } ] else: msg['user'] = []; req.set_element_RequestAccessRequestBody( pack_soap(req, "RequestAccessRequestBody", msg)) if opts.debug > 1: print msg if opts.serialize_only: sw = SoapWriter() sw.serialize(req) print str(sw) sys.exit(0) try: resp = port.RequestAccess(req) except ZSI.FaultException, e: sys.exit("Fault: %s" % e) if (resp != None): resp_body = resp.get_element_RequestAccessResponseBody() if ( resp_body != None): try: print_response_as_testbed(resp_body, opts.label) except RuntimeError, e: sys.exit("Bad response. %s" % e.messgae) else: sys.exit("No body in resonpse!?") else: sys.exit("No response?!?")