source: fedd/fedd_create.py @ d58ee5e

compt_changes
Last change on this file since d58ee5e was a11eda5, checked in by Ted Faber <faber@…>, 12 years ago

Add support for testbeds to indicate preference for outgoing portal
connections.

  • Property mode set to 100755
File size: 10.6 KB
Line 
1#!/usr/bin/env python
2
3import sys, os
4import re
5import subprocess
6
7import ABAC
8import Creddy
9
10from string import join, ascii_letters
11from random import choice
12
13from deter import topdl
14from deter import fedid, generate_fedid
15
16from federation.proof import proof
17from federation.remote_service import service_caller
18from federation.client_lib import client_opts, exit_with_fault, RPCException, \
19        wrangle_standard_options, do_rpc, get_experiment_names, save_certfile,\
20        get_abac_certs, log_authentication
21from federation.util import abac_split_cert, abac_context_to_creds
22
23from xml.parsers.expat import ExpatError
24
25class fedd_create_opts(client_opts):
26    """
27    Extra options that create needs.  Help entries should explain them.
28    """
29    def __init__(self):
30        client_opts.__init__(self)
31        self.add_option("--experiment_cert", dest="out_certfile",
32                action="callback", callback=self.expand_file,
33                type="string", help="output certificate file")
34        self.add_option("--experiment_name", dest="exp_name",
35                type="string", help="Suggested experiment name")
36        self.add_option("--file", dest="file", action="callback",
37                callback=self.expand_file, type="str",
38                help="experiment description file")
39        self.add_option("--project", action="store", dest="project", 
40                type="string",
41                help="Project to export from master")
42        self.add_option("--master", dest="master",
43                help="Master testbed in the federation (pseudo project export)")
44        self.add_option("--service", dest="service", action="append",
45                type="string", default=[],
46                help="Service description name:exporters:importers:attrs")
47        self.add_option('--active', action='append', default=[], type='string',
48                help='a testbed that prefers active protal nodes in the map')
49        self.add_option("--map", dest="map", action="append",
50                type="string", default=[],
51                help="Explicit map from testbed label to URI - " + \
52                        "deter:https://users.isi.deterlab/net:13232")
53        self.add_option('--gen_cert', action='store_true', dest='gen_cert',
54                default=False,
55                help='generate a cert to which to delegate rights')
56        self.add_option('--delegate', action='store_true', dest='generate',
57                help='Delegate rights to a generated cert (default)')
58        self.add_option('--no-delegate', action='store_true', dest='generate',
59                help='Do not delegate rights to a generated cert')
60
61        self.set_defaults(delegate=True)
62
63def parse_service(svc):
64    """
65    Pasre a service entry into a hash representing a service entry in a
66    message.  The format is:
67        svc_name:exporter(s):importer(s):attr=val,attr=val
68    The svc_name is teh service name, exporter is the exporting testbeds
69    (comma-separated) importer is the importing testbeds (if any) and the rest
70    are attr=val pairs that will become attributes of the service.  These
71    include parameters and other such stuff.
72    """
73
74    terms = svc.split(':')
75    svcd = { }
76    if len(terms) < 2 or len(terms[0]) == 0 or len(terms[1]) == 0:
77        sys.exit("Bad service description '%s': Not enough terms" % svc)
78   
79    svcd['name'] = terms[0]
80    svcd['export'] = terms[1].split(",")
81    if len(terms) > 2 and len(terms[2]) > 0:
82        svcd['import'] = terms[2].split(",")
83    if len(terms) > 3 and len(terms[3]) > 0:
84        svcd['fedAttr'] = [ ]
85        for t in terms[3].split(";"):
86            i = t.find("=")
87            if i != -1 :
88                svcd['fedAttr'].append(
89                        {'attribute': t[0:i], 'value': t[i+1:]})
90            else:
91                sys.exit("Bad service attribute '%s': no equals sign" % t)
92    return svcd
93
94def project_export_service(master, project):
95    """
96    Output the dict representation of a project_export service for this project
97    and master.
98    """
99    return {
100            'name': 'project_export', 
101            'export': [master], 
102            'importall': True,
103            'fedAttr': [ 
104                { 'attribute': 'project', 'value': project },
105                ],
106            }
107
108def delegate(fedid, cert, dir, name=None, debug=False):
109    '''
110    Make the creddy call to create an attribute delegating rights to the new
111    experiment.  The cert parameter points to a conventional cert & key combo,
112    which we split out into tempfiles, which we delete on return.  The return
113    value if the filename in which the certificate was stored.
114    '''
115    certfile = keyfile = None
116    expid = "%s" % fedid
117
118    # Trim the "fedid:"
119    if expid.startswith("fedid:"): expid = expid[6:]
120
121    try:
122        keyfile, certfile = abac_split_cert(cert)
123
124        rv = 0
125        if name: 
126            fn ='%s/%s_attr.der' % (dir, name) 
127            id_fn = '%s/%s_id.pem' % (dir, name)
128        else: 
129            fn = '%s/%s_attr.der' % (dir, expid)
130            id_fn = '%s/%s_id.pem' % (dir, expid)
131
132        try:
133            cid = Creddy.ID(certfile)
134            cid.load_privkey(keyfile)
135            cattr = Creddy.Attribute(cid, 'acting_for', 3600 * 24 * 365 * 10)
136            cattr.principal("%s" % expid)
137            cattr.bake()
138            cattr.write_name(fn)
139        except RuntimeError:
140            print >>sys.stderr, "Cannot create ABAC delegation. " + \
141                    "Did you run cert_to_fedid.py on your X.509 cert?"
142            return []
143
144        context = ABAC.Context()
145        if context.load_id_file(certfile) != ABAC.ABAC_CERT_SUCCESS or \
146                context.load_attribute_file(fn) != ABAC.ABAC_CERT_SUCCESS:
147            print >>sys.stderr, "Cannot load delegation into ABAC context. " + \
148                    "Did you run cert_to_fedid.py on your X.509 cert?"
149            return []
150        ids, attrs = abac_context_to_creds(context)
151
152        return ids + attrs
153
154
155    finally:
156        if keyfile: os.unlink(keyfile)
157        if certfile: os.unlink(certfile)
158
159
160# Main line
161service_re = re.compile('^\\s*#\\s*SERVICE:\\s*([\\S]+)')
162parser = fedd_create_opts()
163(opts, args) = parser.parse_args()
164
165svcs = []
166# Option processing
167try:
168    cert, fid, url = wrangle_standard_options(opts)
169except RuntimeError, e:
170    sys.exit("%s" %e)
171
172if opts.file:
173
174    top = None
175    # Try the file as a topdl description
176    try:
177        top = topdl.topology_from_xml(filename=opts.file, top='experiment')
178    except EnvironmentError:
179        # Can't read the file, fail now
180        sys.exit("Cannot read description file (%s)" %opts.file)
181    except ExpatError:
182        # The file is not topdl, fall and assume it's ns2
183        pass
184
185    if top is None:
186        try:
187            lines = [ line for line in open(opts.file, 'r')]
188            exp_desc = "".join(lines)
189            # Parse all the strings that we can pull out of the file using the
190            # service_re.
191            svcs.extend([parse_service(service_re.match(l).group(1)) \
192                    for l in lines if service_re.match(l)])
193        except EnvironmentError:
194            sys.exit("Cannot read description file (%s)" %opts.file)
195else:
196    sys.exit("Must specify an experiment description (--file)")
197
198out_certfile = opts.out_certfile
199
200# If there is no abac directory in which to store a delegated credential, don't
201# delegate.
202if not opts.abac_dir and opts.delegate:
203    opts.delegate = False
204
205# Load ABAC certs
206if opts.abac_dir:
207    try:
208        acerts = get_abac_certs(opts.abac_dir)
209    except EnvironmentError, e:
210        sys.exit('%s: %s' % (e.filename, e.strerror))
211else:
212    acerts = None
213
214# Fill in services
215if opts.master and opts.project:
216    svcs.append(project_export_service(opts.master, opts.project))
217svcs.extend([ parse_service(s) for s in opts.service])
218
219# Create a testbed map if one is specified
220tbmap = { }
221for m in opts.map:
222    i = m.find(":")
223    if i != -1: tbmap[m[0:i]] = m[i+1:]
224    else: sys.exit("Bad mapping argument: %s" %m )
225
226
227if not svcs:
228    print >>sys.stderr, "Warning:Neither master/project nor services requested"
229
230# Construct the New experiment request
231msg = { }
232
233
234# Generate a certificate if requested and put it into the message
235if opts.gen_cert:
236    expid, expcert = generate_fedid(opts.exp_name or 'dummy')
237    msg['experimentAccess'] = { 'X509': expcert }
238else:
239    expid = expcert = None
240
241if opts.exp_name:
242    msg['experimentID'] = { 'localname': opts.exp_name }
243
244if acerts:
245    msg['credential'] = acerts
246
247# ZSI will not properly construct an empty message.  If nothing has been added
248# to msg, pick a random localname to ensure the message is created
249if not msg:
250    msg['experimentID'] = { 
251            'localname': join([choice(ascii_letters) for i in range(0,8)],""),
252            }
253
254
255if opts.debug > 1: print >>sys.stderr, msg
256
257# The New call
258try:
259    resp_dict = do_rpc(msg, 
260            url, opts.transport, cert, opts.trusted, 
261            serialize_only=opts.serialize_only,
262            tracefile=opts.tracefile,
263            caller=service_caller('New'), responseBody="NewResponseBody")
264except RPCException, e:
265    exit_with_fault(e, 'New (create)', opts)
266except RuntimeError, e:
267    sys.exit("Error processing RPC: %s" % e)
268
269if opts.debug > 1: print >>sys.stderr, resp_dict
270
271proof = proof.from_dict(resp_dict.get('proof', {}))
272if proof and opts.auth_log:
273    log_authentication(opts.auth_log, 'New (create)', 'succeeded', proof)
274# Save the experiment ID certificate if we need it
275try:
276    save_certfile(opts.out_certfile, resp_dict.get('experimentAccess', None))
277except EnvironmentError, e:
278    sys.exit('Could not write to %s: %s' %  (opts.out_certfile, e))
279
280e_fedid, e_local = get_experiment_names(resp_dict.get('experimentID', None))
281if not e_fedid and not e_local and opts.serialize_only:
282    e_local = "serialize"
283
284# If delegation is requested and we have a target, make the delegation, and add
285# the credential to acerts.
286if e_fedid and opts.delegate:
287    try:
288        creds = delegate(e_fedid, cert, opts.abac_dir, name=opts.exp_name)
289        if creds:
290            acerts.extend(creds)
291    except EnvironmentError, e:
292        sys.exit("Cannot delegate rights %s: %s" % (e.filename, e.strerror));
293
294# Construct the Create message
295if top: 
296    msg = { 'experimentdescription' : { 'topdldescription': top.to_dict() }, }
297else: 
298    msg = { 'experimentdescription': { 'ns2description': exp_desc }, }
299
300if svcs:
301    msg['service'] = svcs
302
303if e_fedid:
304    msg['experimentID'] = { 'fedid': e_fedid }
305elif e_local:
306    msg['experimentID'] = { 'localname': e_local }
307else:
308    sys.exit("New did not return an experiment ID??")
309
310if acerts:
311    msg['credential'] = acerts
312
313if tbmap:
314    msg['testbedmap'] = [ 
315            { 
316                'testbed': t, 
317                'uri': u,
318                'active': t in opts.active,
319            } for t, u in tbmap.items()
320        ]
321
322if opts.debug > 1: print >>sys.stderr, msg
323
324# make the call
325try:
326    resp_dict = do_rpc(msg, 
327            url, opts.transport, cert, opts.trusted, 
328            serialize_only=opts.serialize_only,
329            tracefile=opts.tracefile,
330            caller=service_caller('Create', max_retries=1), responseBody="CreateResponseBody")
331except RPCException, e:
332    exit_with_fault(e, 'Create', opts)
333except RuntimeError, e:
334    sys.exit("Error processing RPC: %s" % e)
335
336if opts.debug > 1: print >>sys.stderr, resp_dict
337
338# output
339e_fedid, e_local = get_experiment_names(resp_dict.get('experimentID', None))
340st = resp_dict.get('experimentStatus', None)
341
342if e_local: print "localname: %s" % e_local
343if e_fedid: print "fedid: %s" % e_fedid
344if st: print "status: %s" % st
345proof = proof.from_dict(resp_dict.get('proof', {}))
346if proof and opts.auth_log:
347    log_authentication(opts.auth_log, 'Create', 'succeeded', proof)
Note: See TracBrowser for help on using the repository browser.