#!/usr/local/bin/python import sys import os import pwd import tempfile import traceback import subprocess import re import xml.parsers.expat import time from federation import fedid, service_error from federation.util import fedd_ssl_context, pack_id, unpack_id from federation.abac_remote_service import abac_service_caller #from federation import topdl from optparse import OptionParser, OptionValueError 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; # Base class that will do a the SOAP/XMLRPC exchange for a request. class abac_rpc: class RPCException: def __init__(self, fb): self.desc = fb.get('desc', None) self.code = fb.get('code', -1) self.errstr = fb.get('errstr', None) def __init__(self, pre): """ Specialize the class for the pre method """ self.RequestBody="%sRequestBody" % pre self.ResponseBody="%sResponseBody" % pre self.method = pre self.caller = abac_service_caller(self.method) self.RPCException = abac_rpc.RPCException def add_ssh_key(self, 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(self, 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(self, 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(self, 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) def do_rpc(self, req_dict, url, transport, cert, trusted, tracefile=None, serialize_only=False): """ The work of sending and parsing the RPC as either XMLRPC or SOAP """ context = None while context == None: try: context = fedd_ssl_context(cert, trusted) except Exception, 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 if transport == "soap": if serialize_only: print self.caller.serialize_soap(req_dict) sys.exit(0) else: try: resp = self.caller.call_soap_service(url, req_dict, context=context, tracefile=tracefile) except service_error, e: raise self.RPCException( {\ 'code': e.code, 'desc': e.desc, 'errstr': e.code_string()\ }) elif transport == "xmlrpc": if serialize_only: ser = dumps((req_dict,)) print ser sys.exit(0) else: try: resp = self.caller.call_xmlrpc_service(url, req_dict, context=context, tracefile=tracefile) except service_error, e: raise self.RPCException( {\ 'code': e.code, 'desc': e.desc, 'errstr': e.code_string()\ }) else: raise RuntimeError("Unknown RPC transport: %s" % transport) if resp.has_key(self.ResponseBody): return resp[self.ResponseBody] else: raise RuntimeError("No body in response??") class abac_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.add_option("-c","--cert", action="store", dest="cert", type="string", help="my certificate file") self.add_option("-d", "--debug", action="count", dest="debug", default=0, help="Set debug. Repeat for more information") self.add_option("-s", "--serializeOnly", action="store_true", dest="serialize_only", default=False, help="Print the SOAP request that would be sent and exit") 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",default="https://localhost:23235", help="URL to connect to (default %default)") self.add_option("-n", "--ident", action="store", dest="id", type="string",default="Alice", help="URL to connect to (default %default)") self.add_option("-F","--useFedid", action="store_true", dest="use_fedid", default=False, help="Use a fedid derived from my certificate as user identity") self.add_option("-x","--transport", action="store", type="choice", choices=("xmlrpc", "soap"), default="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") class abac_create_opts(abac_client_opts): def __init__(self, access_keys, add_key_callback=None, add_cert_callback=None): abac_client_opts.__init__(self) self.add_option("-f", "--file", dest="file", type="string", help="negotiation context remote path name") if add_key_callback: self.add_option("-k", "--ssh_key", action="callback", type="string", callback=add_key_callback, callback_args=(access_keys,), help="ssh key for access (can be supplied more than once") if add_cert_callback: self.add_option("-K", "--x509Key", action="callback", type="string", callback=add_cert_callback, callback_args=(access_keys,), help="X509 certificate for access " + \ "(can be supplied more than once") self.add_option("-m", "--master", dest="master", help="Master testbed in the federation") self.add_option("-U", "--username", action="store", dest="user", type="string", help="Use this username instead of the uid") class abac_access_opts(abac_create_opts): def __init__(self, access_keys, node_descs, add_key_callback=None, add_cert_callback=None, add_node_callback=None): abac_create_opts.__init__(self, access_keys, add_key_callback, add_cert_callback) self.add_option("-a","--anonymous", action="store_true", dest="anonymous", default=False, help="Do not include a user in the request") self.add_option("-l","--label", action="store", dest="label", type="string", help="Label for output") self.add_option("-i", "--context_id", action="store", dest="id", type="string", help="Negotiation context identifier (required)") self.add_option("-g", "--goal", action="store", dest="goal", type="string", help="Trust target goal for negotiation (required)") self.add_option("--srcURL", action="store", dest="srcURL", type="string",default="https://localhost:23235", help="Originating URL address (Default: %default)") self.add_option("--dstURL", action="store", dest="dstURL", type="string",default="https://localhost:23235", help="Peer URL address (Default: %default)") self.add_option("--srcContext", action="store", dest="srcContext", type="string",default="Alice", help="Service negotiation context (Default: %default)") self.add_option("--dstContext", action="store", dest="dstContext", type="string",default="Bob", help="Client call back negotiation context (Default: %default)") if add_node_callback: self.add_option("-n", "--node", action="callback", type="string", callback=add_node_callback, callback_args=(node_descs,), help="Node description: image:hardware[:count]") self.add_option("-t", "--testbed", action="store", dest="testbed", type="string", help="Testbed identifier (URI) to contact (required)") class abac_negotiate_opts(abac_create_opts): def __init__(self, access_keys, node_descs, add_key_callback=None, add_cert_callback=None, add_node_callback=None): abac_create_opts.__init__(self, access_keys, add_key_callback, add_cert_callback) self.add_option("-a","--anonymous", action="store_true", dest="anonymous", default=False, help="Do not include a user in the request") self.add_option("-l","--label", action="store", dest="label", type="string", help="Label for output") if add_node_callback: self.add_option("-n", "--node", action="callback", type="string", callback=add_node_callback, callback_args=(node_descs,), help="Node description: image:hardware[:count]") self.add_option("-t", "--testbed", action="store", dest="testbed", type="string", help="Testbed identifier (URI) to contact (required)") class exp_data_base(abac_rpc): def __init__(self, op='Info'): """ Init the various conversions """ abac_rpc.__init__(self, op) # List of things one could ask for and what formatting routine is # called. self.params = { 'vis': ('vis', self.print_xml('vis')), 'vtopo': ('vtopo', self.print_xml('vtopo')), 'federant': ('federant', self.print_xml), 'id': ('experimentID', self.print_id), 'status': ('experimentStatus', self.print_string), 'log': ('allocationLog', self.print_string), 'access': ('experimentAccess', self.print_string), } # Utility functions def print_string(self, d, out=sys.stdout): print >>out, d def print_id(self, d, out=sys.stdout): if d: for id in d: for k in id.keys(): print >>out, "%s: %s" % (k, id[k]) class print_xml: """ Print the retrieved data is a simple xml representation of the dict. """ def __init__(self, top): self.xml = top def __call__(self, d, out=sys.stdout): str = "<%s>\n" % self.xml for t in ('node', 'lan'): if d.has_key(t): for x in d[t]: str += "<%s>" % t for k in x.keys(): str += "<%s>%s" % (k, x[k],k) str += "\n" % t str+= "" % self.xml print >>out, str class context_data(exp_data_base): def __init__(self): exp_data_base.__init__(self, 'MultiInfo') def __call__(self): """ The control flow. Compose the request and print the response. """ # Process the options using the customized option parser defined above parser = abac_multi_exp_data_opts() (opts, args) = parser.parse_args() if opts.trusted: if ( not os.access(opts.trusted, os.R_OK) ) : sys.exit("Cannot read trusted certificates (%s)" % opts.trusted) if opts.debug > 0: opts.tracefile=sys.stderr (user, cert) = self.get_user_info([]) if opts.cert != None: cert = opts.cert if cert == None: sys.exit("No certificate given (--cert) or found") if opts.file == None: sys.exit("No file given (--file)") if os.access(cert, os.R_OK): fid = fedid(file=cert) else: sys.exit("Cannot read certificate (%s)" % cert) req = { 'ContextIn': { 'contextFile': opts.file } } try: resp_dict = self.do_rpc(req, opts.url, opts.transport, cert, opts.trusted, serialize_only=opts.serialize_only, tracefile=opts.tracefile) except self.RPCException, e: exit_with_fault(\ {'desc': e.desc, 'errstr': e.errstr, 'code': e.code}) except RuntimeError, e: sys.exit("Error processing RPC: %s" % e) exps = resp_dict.get('info', []) if exps: print '---' for exp in exps: for d in opts.data: key, output = self.params[d] try: if exp.has_key(key): output(exp[key]) except RuntimeError, e: sys.exit("Bad response. %s" % e.message) print '---' def __call__(self): """ The control flow. Compose the request and print the response. """ # Process the options using the customized option parser defined above parser = fedd_image_opts() (opts, args) = parser.parse_args() if opts.trusted: if ( not os.access(opts.trusted, os.R_OK) ) : sys.exit("Cannot read trusted certificates (%s)" % opts.trusted) if opts.debug > 0: opts.tracefile=sys.stderr (user, cert) = self.get_user_info([]) 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) else: sys.exit("Cannot read certificate (%s)" % cert) if opts.exp_name and opts.exp_certfile: sys.exit("Only one of --experiment_cert and " +\ "--experiment_name permitted"); if opts.exp_certfile: exp_id = { 'fedid': fedid(file=opts.exp_certfile) } if opts.exp_name: exp_id = { 'localname' : opts.exp_name } if opts.format and opts.outfile: fmt = opts.format file = opts.outfile elif not opts.format and opts.outfile: fmt = opts.outfile[-3:] if fmt not in ("png", "jpg", "dot", "svg"): sys.exit("Unexpected file type and no format specified") file = opts.outfile elif opts.format and not opts.outfile: fmt = opts.format file = None else: fmt="dot" file = None req = { 'experiment': exp_id } try: resp_dict = self.do_rpc(req, opts.url, opts.transport, cert, opts.trusted, serialize_only=opts.serialize_only, tracefile=opts.tracefile) except self.RPCException, e: exit_with_fault(\ {'desc': e.desc, 'errstr': e.errstr, 'code': e.code}) except RuntimeError, e: sys.exit("Error processing RPC: %s" % e) if resp_dict.has_key('vtopo'): self.gen_image(resp_dict['vtopo'], len(resp_dict['vtopo'].get('node', [])), file, fmt, opts.neato, opts.labels, opts.pixels) else: sys.exit("Bad response. %s" % e.message) class create(abac_rpc): def __init__(self): abac_rpc.__init__(self, "CreateContext") def __call__(self): access_keys = [] # Process the options using the customized option parser defined above parser = abac_create_opts(access_keys, self.add_ssh_key, self.add_x509_cert) (opts, args) = parser.parse_args() if opts.trusted: if ( not os.access(opts.trusted, os.R_OK) ) : sys.exit("Cannot read trusted certificates (%s)" % opts.trusted) if opts.debug > 0: opts.tracefile=sys.stderr (user, cert) = self.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) # if opts.file: # exp_desc = "" # try: # f = open(opts.file, 'r') # for line in f: # exp_desc += line # f.close() # except IOError: # sys.exit("Cannot read description file (%s)" %opts.file) # else: # sys.exit("Must specify an experiment description (--file)") # # if not opts.master: # sys.exit("Must specify a master testbed (--master)") # # out_certfile = opts.out_certfile print "New context using file: %s" % opts.file # msg = { 'ContextIn': { 'contextFile': opts.file } } msg = { 'contextFile': opts.file } if opts.debug > 1: print >>sys.stderr, msg try: resp_dict = self.do_rpc(msg, opts.url, opts.transport, cert, opts.trusted, serialize_only=opts.serialize_only, tracefile=opts.tracefile) except self.RPCException, e: exit_with_fault(\ {'desc': e.desc, 'errstr': e.errstr, 'code': e.code}) except RuntimeError, e: sys.exit("Error processing RPC: %s" % e) if opts.debug > 1: print >>sys.stderr, resp_dict # ea = resp_dict.get('experimentAccess', None) id = resp_dict.get('contextID', None) print "New context loaded w/id = %s" % id class negotiate(abac_rpc): def __init__(self): abac_rpc.__init__(self, "Negotiate") def __call__(self): access_keys = [] # Process the options using the customized option parser defined above parser = abac_create_opts(access_keys, self.add_ssh_key, self.add_x509_cert) (opts, args) = parser.parse_args() if opts.trusted: if ( not os.access(opts.trusted, os.R_OK) ) : sys.exit("Cannot read trusted certificates (%s)" % opts.trusted) if not opts.project : parser.error('--project is required') if opts.debug > 0: opts.tracefile=sys.stderr (user, cert) = self.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) if opts.file: exp_desc = "" try: f = open(opts.file, 'r') for line in f: exp_desc += line f.close() except IOError: sys.exit("Cannot read description file (%s)" %opts.file) else: sys.exit("Must specify an experiment description (--file)") if not opts.master: sys.exit("Must specify a master testbed (--master)") out_certfile = opts.out_certfile msg = { 'experimentdescription': { 'ns2description': exp_desc }, 'master': opts.master, 'exportProject': { 'localname': opts.project }, 'user' : [ {\ 'userID': pack_id(user), \ 'access': [ { a.type: a.buf } for a in access_keys]\ } ] } if opts.exp_name: msg['experimentID'] = { 'localname': opts.exp_name } if opts.debug > 1: print >>sys.stderr, msg try: resp_dict = self.do_rpc(msg, opts.url, opts.transport, cert, opts.trusted, serialize_only=opts.serialize_only, tracefile=opts.tracefile) except self.RPCException, e: exit_with_fault(\ {'desc': e.desc, 'errstr': e.errstr, 'code': e.code}) except RuntimeError, e: sys.exit("Error processing RPC: %s" % e) if opts.debug > 1: print >>sys.stderr, resp_dict ea = resp_dict.get('goal') if out_certfile and ea and ea.has_key('X509'): try: f = open(out_certfile, "w") print >>f, ea['X509'] f.close() except IOError: sys.exit('Could not write to %s' % out_certfile) eid = resp_dict.get('experimentID', None) if eid: for id in eid: for k in id.keys(): print "%s: %s" % (k, id[k]) st = resp_dict.get('experimentStatus', None) if st: print "status: %s" % st class access(abac_rpc): def __init__(self): abac_rpc.__init__(self, "Access") def __call__(self): access_keys = [] # Process the options using the customized option parser defined above parser = abac_access_opts(access_keys, self.add_ssh_key, self.add_x509_cert) (opts, args) = parser.parse_args() if opts.trusted: if ( not os.access(opts.trusted, os.R_OK) ) : sys.exit("Cannot read trusted certificates (%s)" % opts.trusted) if opts.debug > 0: opts.tracefile=sys.stderr (user, cert) = self.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) if not opts.id: sys.exit("Must specify a negotiator id (--id)") if not opts.goal: sys.exit("Must specify a goal (--goal)") # out_certfile = opts.out_certfile msg = { 'context': { 'contextID': opts.id }, 'goal': opts.goal, 'peerURL': opts.dstURL, 'peerContext' : { 'contextID': opts.dstContext }, 'selfURL': opts.srcURL } if opts.debug > 1: print >>sys.stderr, msg try: resp_dict = self.do_rpc(msg, opts.url, opts.transport, cert, opts.trusted, serialize_only=opts.serialize_only, tracefile=opts.tracefile) except self.RPCException, e: exit_with_fault(\ {'desc': e.desc, 'errstr': e.errstr, 'code': e.code}) except RuntimeError, e: sys.exit("Error processing RPC: %s" % e) if opts.debug > 1: print >>sys.stderr, resp_dict result = resp_dict.get('result', None) goal = resp_dict.get('goal', None) print "%s: %s" % (goal, result) def exit_with_fault(dict, out=sys.stderr): """ Print an error message and exit. The dictionary contains the FeddFaultBody 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'] traceback.print_stack() sys.exit(dict.get('code', 20)) cmds = {\ 'create': create(),\ 'access': access()\ # 'negotiate': negotiate(),\ # 'status': status()\ } operation = cmds.get(sys.argv[1], None) if operation: del sys.argv[1] operation() else: if sys.argv[1] == '--help': sys.exit(\ '''Only context sensitive help is available. For one of the commands: %s type %s command --help to get help, e.g., %s create --help ''' % (", ".join(cmds.keys()), sys.argv[0], sys.argv[0])) else: sys.exit("Bad command: %s. Valid ones are: %s" % \ (sys.argv[1], ", ".join(cmds.keys())))