#!/usr/bin/env python import sys, os import re import subprocess import ABAC import Creddy from string import join, ascii_letters from random import choice from deter import topdl from deter import fedid, generate_fedid from federation.proof import proof from federation.remote_service import service_caller from federation.client_lib import client_opts, exit_with_fault, RPCException, \ wrangle_standard_options, do_rpc, get_experiment_names, save_certfile,\ get_abac_certs, log_authentication from federation.util import abac_split_cert, abac_context_to_creds from xml.parsers.expat import ExpatError class fedd_create_opts(client_opts): """ Extra options that create needs. Help entries should explain them. """ def __init__(self): client_opts.__init__(self) self.add_option("--experiment_cert", dest="out_certfile", action="callback", callback=self.expand_file, type="string", help="output certificate file") self.add_option("--experiment_name", dest="exp_name", type="string", help="Suggested experiment name") self.add_option("--file", dest="file", action="callback", callback=self.expand_file, type="str", help="experiment description file") self.add_option("--project", action="store", dest="project", type="string", help="Project to export from master") self.add_option("--master", dest="master", help="Master testbed in the federation (pseudo project export)") self.add_option("--service", dest="service", action="append", type="string", default=[], help="Service description name:exporters:importers:attrs") self.add_option('--active', action='append', default=[], type='string', help='a testbed that prefers active protal nodes in the map') self.add_option("--map", dest="map", action="append", type="string", default=[], help="Explicit map from testbed label to URI - " + \ "deter:https://users.isi.deterlab/net:13232") self.add_option('--gen_cert', action='store_true', dest='gen_cert', default=False, help='generate a cert to which to delegate rights') self.add_option('--delegate', action='store_true', dest='generate', help='Delegate rights to a generated cert (default)') self.add_option('--no-delegate', action='store_true', dest='generate', help='Do not delegate rights to a generated cert') self.set_defaults(delegate=True) def parse_service(svc): """ Pasre a service entry into a hash representing a service entry in a message. The format is: svc_name:exporter(s):importer(s):attr=val,attr=val The svc_name is teh service name, exporter is the exporting testbeds (comma-separated) importer is the importing testbeds (if any) and the rest are attr=val pairs that will become attributes of the service. These include parameters and other such stuff. """ terms = svc.split(':') svcd = { } if len(terms) < 2 or len(terms[0]) == 0 or len(terms[1]) == 0: sys.exit("Bad service description '%s': Not enough terms" % svc) svcd['name'] = terms[0] svcd['export'] = terms[1].split(",") if len(terms) > 2 and len(terms[2]) > 0: svcd['import'] = terms[2].split(",") if len(terms) > 3 and len(terms[3]) > 0: svcd['fedAttr'] = [ ] for t in terms[3].split(";"): i = t.find("=") if i != -1 : svcd['fedAttr'].append( {'attribute': t[0:i], 'value': t[i+1:]}) else: sys.exit("Bad service attribute '%s': no equals sign" % t) return svcd def project_export_service(master, project): """ Output the dict representation of a project_export service for this project and master. """ return { 'name': 'project_export', 'export': [master], 'importall': True, 'fedAttr': [ { 'attribute': 'project', 'value': project }, ], } def delegate(fedid, cert, dir, name=None, debug=False): ''' Make the creddy call to create an attribute delegating rights to the new experiment. The cert parameter points to a conventional cert & key combo, which we split out into tempfiles, which we delete on return. The return value if the filename in which the certificate was stored. ''' certfile = keyfile = None expid = "%s" % fedid # Trim the "fedid:" if expid.startswith("fedid:"): expid = expid[6:] try: keyfile, certfile = abac_split_cert(cert) rv = 0 if name: fn ='%s/%s_attr.der' % (dir, name) id_fn = '%s/%s_id.pem' % (dir, name) else: fn = '%s/%s_attr.der' % (dir, expid) id_fn = '%s/%s_id.pem' % (dir, expid) try: cid = Creddy.ID(certfile) cid.load_privkey(keyfile) cattr = Creddy.Attribute(cid, 'acting_for', 3600 * 24 * 365 * 10) cattr.principal("%s" % expid) cattr.bake() cattr.write_name(fn) except RuntimeError: print >>sys.stderr, "Cannot create ABAC delegation. " + \ "Did you run cert_to_fedid.py on your X.509 cert?" return [] context = ABAC.Context() if context.load_id_file(certfile) != ABAC.ABAC_CERT_SUCCESS or \ context.load_attribute_file(fn) != ABAC.ABAC_CERT_SUCCESS: print >>sys.stderr, "Cannot load delegation into ABAC context. " + \ "Did you run cert_to_fedid.py on your X.509 cert?" return [] ids, attrs = abac_context_to_creds(context) return ids + attrs finally: if keyfile: os.unlink(keyfile) if certfile: os.unlink(certfile) # Main line service_re = re.compile('^\\s*#\\s*SERVICE:\\s*([\\S]+)') parser = fedd_create_opts() (opts, args) = parser.parse_args() svcs = [] # Option processing try: cert, fid, url = wrangle_standard_options(opts) except RuntimeError, e: sys.exit("%s" %e) if opts.file: top = None # Try the file as a topdl description try: top = topdl.topology_from_xml(filename=opts.file, top='experiment') except EnvironmentError: # Can't read the file, fail now sys.exit("Cannot read description file (%s)" %opts.file) except ExpatError: # The file is not topdl, fall and assume it's ns2 pass if top is None: try: lines = [ line for line in open(opts.file, 'r')] exp_desc = "".join(lines) # Parse all the strings that we can pull out of the file using the # service_re. svcs.extend([parse_service(service_re.match(l).group(1)) \ for l in lines if service_re.match(l)]) except EnvironmentError: sys.exit("Cannot read description file (%s)" %opts.file) else: sys.exit("Must specify an experiment description (--file)") out_certfile = opts.out_certfile # If there is no abac directory in which to store a delegated credential, don't # delegate. if not opts.abac_dir and opts.delegate: opts.delegate = False # Load ABAC certs if opts.abac_dir: try: acerts = get_abac_certs(opts.abac_dir) except EnvironmentError, e: sys.exit('%s: %s' % (e.filename, e.strerror)) else: acerts = None # Fill in services if opts.master and opts.project: svcs.append(project_export_service(opts.master, opts.project)) svcs.extend([ parse_service(s) for s in opts.service]) # Create a testbed map if one is specified tbmap = { } for m in opts.map: i = m.find(":") if i != -1: tbmap[m[0:i]] = m[i+1:] else: sys.exit("Bad mapping argument: %s" %m ) if not svcs: print >>sys.stderr, "Warning:Neither master/project nor services requested" # Construct the New experiment request msg = { } # Generate a certificate if requested and put it into the message if opts.gen_cert: expid, expcert = generate_fedid(opts.exp_name or 'dummy') msg['experimentAccess'] = { 'X509': expcert } else: expid = expcert = None if opts.exp_name: msg['experimentID'] = { 'localname': opts.exp_name } if acerts: msg['credential'] = acerts # ZSI will not properly construct an empty message. If nothing has been added # to msg, pick a random localname to ensure the message is created if not msg: msg['experimentID'] = { 'localname': join([choice(ascii_letters) for i in range(0,8)],""), } if opts.debug > 1: print >>sys.stderr, msg # The New call try: resp_dict = do_rpc(msg, url, opts.transport, cert, opts.trusted, serialize_only=opts.serialize_only, tracefile=opts.tracefile, caller=service_caller('New'), responseBody="NewResponseBody") except RPCException, e: exit_with_fault(e, 'New (create)', opts) except RuntimeError, e: sys.exit("Error processing RPC: %s" % e) if opts.debug > 1: print >>sys.stderr, resp_dict p = proof.from_dict(resp_dict.get('proof', {})) if p and opts.auth_log: log_authentication(opts.auth_log, 'New (create)', 'succeeded', p) # Save the experiment ID certificate if we need it try: save_certfile(opts.out_certfile, resp_dict.get('experimentAccess', None)) except EnvironmentError, e: sys.exit('Could not write to %s: %s' % (opts.out_certfile, e)) e_fedid, e_local = get_experiment_names(resp_dict.get('experimentID', None)) if not e_fedid and not e_local and opts.serialize_only: e_local = "serialize" # If delegation is requested and we have a target, make the delegation, and add # the credential to acerts. if e_fedid and opts.delegate: try: creds = delegate(e_fedid, cert, opts.abac_dir, name=opts.exp_name) if creds: acerts.extend(creds) except EnvironmentError, e: sys.exit("Cannot delegate rights %s: %s" % (e.filename, e.strerror)); # Construct the Create message if top: msg = { 'experimentdescription' : { 'topdldescription': top.to_dict() }, } else: msg = { 'experimentdescription': { 'ns2description': exp_desc }, } if svcs: msg['service'] = svcs if e_fedid: msg['experimentID'] = { 'fedid': e_fedid } elif e_local: msg['experimentID'] = { 'localname': e_local } else: sys.exit("New did not return an experiment ID??") if acerts: msg['credential'] = acerts if tbmap: msg['testbedmap'] = [ { 'testbed': t, 'uri': u, 'active': t in opts.active, } for t, u in tbmap.items() ] if opts.debug > 1: print >>sys.stderr, msg # make the call try: resp_dict = do_rpc(msg, url, opts.transport, cert, opts.trusted, serialize_only=opts.serialize_only, tracefile=opts.tracefile, caller=service_caller('Create', max_retries=1), responseBody="CreateResponseBody") except RPCException, e: exit_with_fault(e, 'Create', opts) except RuntimeError, e: sys.exit("Error processing RPC: %s" % e) if opts.debug > 1: print >>sys.stderr, resp_dict # output e_fedid, e_local = get_experiment_names(resp_dict.get('experimentID', None)) st = resp_dict.get('experimentStatus', None) if e_local: print "localname: %s" % e_local if e_fedid: print "fedid: %s" % e_fedid if st: print "status: %s" % st #proof = proof.from_dict(resp_dict.get('proof', {})) p_list = resp_dict.get('proof', {}) if p_list and opts.auth_log: for p in p_list: log_authentication(opts.auth_log, 'Create', 'succeeded', proof.from_dict(p))