#!/usr/local/bin/python import sys import os import pwd from fedd_services import * from M2Crypto import SSL, X509 from M2Crypto.m2xmlrpclib import SSL_Transport import M2Crypto.httpslib from xmlrpclib import ServerProxy, Error, dumps, loads from ZSI import SoapWriter from ZSI.TC import QName, String, URI, AnyElement, UNBOUNDED, Any from ZSI.wstools.Namespaces import SOAP from ZSI.fault import FaultType, Detail import xmlrpclib from fedd_util import fedid, fedd_ssl_context, pack_soap, unpack_soap, \ pack_id, unpack_id from optparse import OptionParser, OptionValueError import parse_detail # 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, transport="soap", 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("-x","--transport", action="store", type="choice", choices=("xmlrpc", "soap"), help="Transport for request (xmlrpc|soap) (Default: %default)") self.add_option("--trace", action="store_const", dest="tracefile", const=sys.stderr, help="Print SOAP exchange to stderr") def print_response_as_testbed(resp, label, out=sys.stdout): """Print the response as input to the splitter script""" e = resp['emulab'] p = e['project'] fields = { "Boss": e['boss'], "OpsNode": e['ops'], "Domain": e['domain'], "FileServer": e['fileServer'], "EventServer": e['eventServer'], "Project": unpack_id(p['name']) } if (label != None): print >> out, "[%s]" % label for l, v in fields.iteritems(): print >>out, "%s: %s" % (l, v) for u in p['user']: print >>out, "User: %s" % unpack_id(u['userID']) for a in e['fedAttr']: print >>out, "%s: %s" % (a['attribute'], a['value']) def exit_with_fault(dict, out=sys.stderr): """ Print an error message and exit. The dictionary contains the RequestAccessFaultBody elements.""" codestr = "" if dict.has_key('errstr'): codestr = "Error: %s" % dict['errstr'] if dict.has_key('code'): if len(codestr) > 0 : codestr += " (%d)" % dict['code'] else: codestr = "Error Code: %d" % dict['code'] print>>out, codestr print>>out, "Description: %s" % dict['desc'] sys.exit(dict.get('code', 20)) 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 parser = fedd_client_opts() (opts, args) = parser.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 str(e) == "bad decrypt": print >>sys.stderr, "Bad Passphrase given." else: raise 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'] = []; if opts.debug > 1: print >>sys.stderr, msg if opts.transport == "soap": loc = feddServiceLocator(); port = loc.getfeddPortType(opts.url, transport=M2Crypto.httpslib.HTTPSConnection, transdict={ 'ssl_context' : context }, tracefile=opts.tracefile) req = RequestAccessRequestMessage() req.set_element_RequestAccessRequestBody( pack_soap(req, "RequestAccessRequestBody", msg)) if opts.serialize_only: sw = SoapWriter() sw.serialize(req) print str(sw) sys.exit(0) try: resp = port.RequestAccess(req) except ZSI.ParseException, e: sys.exit("Malformed response (XMLPRC?): %s" % e) except ZSI.FaultException, e: resp = e.fault.detail[0] if (resp != None): if 'get_element_RequestAccessResponseBody' in dir(resp): resp_body = resp.get_element_RequestAccessResponseBody() if ( resp_body != None): try: resp_dict = unpack_soap(resp_body) if opts.debug > 1: print >>sys.stderr, resp_dict print_response_as_testbed(resp_dict, opts.label) except RuntimeError, e: sys.exit("Bad response. %s" % e.message) elif 'get_element_RequestAccessFaultBody' in dir(resp): resp_body = resp.get_element_RequestAccessFaultBody() if resp_body != None: exit_with_fault(unpack_soap(resp_body)) else: sys.exit("No body in response!?") else: sys.exit("No response?!?") elif opts.transport == "xmlrpc": if opts.serialize_only: ser = dumps((msg,)) sys.exit(0) transport = SSL_Transport(context) port = ServerProxy(opts.url, transport=transport) try: resp = port.RequestAccess({ 'RequestAccessRequestBody': msg}) resp, method = loads(resp) except Error, e: resp = { 'RequestAccessFaultBody': \ { 'errstr' : e.faultCode, 'desc' : e.faultString } } if (resp != None): if resp.has_key('RequestAccessReponseBody'): try: resp_dict = resp[0]['RequestAccessResponseBody'] if opts.debug > 1: print >>sys.stderr, resp_dict print_response_as_testbed(resp_dict, opts.label) except RuntimeError, e: sys.exit("Bad response. %s" % e.messgae) elif resp.has_key('RequestAccessFaultBody'): exit_with_fault(resp['RequestAccessFaultBody']) else: sys.exit("No body in response!?") else: sys.exit("No response?!?")