source: fedd/fedd_create.py @ 353db8c

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

Vairous ABAC tweaks, mostly concerned with making key splitting less visible.

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