source: fedd/fedd_create.py @ 187a8f9

Last change on this file since 187a8f9 was c259a77, checked in by Ted Faber <faber@…>, 10 years ago

Service info in xml files.

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