source: fedd/fedd_create.py @ e83f2f2

axis_examplecompt_changesinfo-ops
Last change on this file since e83f2f2 was e83f2f2, checked in by Ted Faber <faber@…>, 13 years ago

Move proofs around. Lots of changes, including fault handling.

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