source: fedd/fedd_create.py @ 2f45140

Last change on this file since 2f45140 was 5e0b7dc, checked in by Ted Faber <faber@…>, 12 years ago

Active stuff that got missed.

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