source: fedd/federation/access.py @ e48d8eb

Last change on this file since e48d8eb was 4ffa6f8, checked in by Ted Faber <faber@…>, 12 years ago

Add support for nat_portal parameter. Remove old half-assed active
endpoints

  • Property mode set to 100644
File size: 27.2 KB
RevLine 
[f771e2f]1#!/usr/local/bin/python
2
3import os,sys
4import stat # for chmod constants
5import re
6import random
7import string
8import copy
9import pickle
10import logging
11import subprocess
[2303ca1]12import shlex
[f771e2f]13
14from threading import *
15from M2Crypto.SSL import SSLError
16
17from util import *
[6bedbdba]18from deter import fedid, generate_fedid
[f771e2f]19from authorizer import authorizer
20from service_error import service_error
21from remote_service import xmlrpc_handler, soap_handler, service_caller
22
23import httplib
24import tempfile
25from urlparse import urlparse
26
[6bedbdba]27from deter import topdl
[f771e2f]28import list_log
29
30
31# Make log messages disappear if noone configures a fedd logger
32class nullHandler(logging.Handler):
33    def emit(self, record): pass
34
35fl = logging.getLogger("fedd.access")
36fl.addHandler(nullHandler())
37
38class access_base:
39    """
40    The implementation of access control based on mapping users to projects.
41
42    Users can be mapped to existing projects or have projects created
43    dynamically.  This implements both direct requests and proxies.
44    """
45
46    class parse_error(RuntimeError): pass
47
[c002cb2]48    class access_attribute:
49        def __init__(self, attr, value, pri=1):
50            self.attr = attr
51            self.value = value
52            self.priority = pri
[1f6a573]53        def __str__(self):
54            return "%s: %s (%d)" % (self.attr, self.value, self.priority)
[c002cb2]55
[f771e2f]56    def __init__(self, config=None, auth=None):
57        """
58        Initializer.  Pulls parameters out of the ConfigParser's access section.
59        """
60
61        # Make sure that the configuration is in place
62        if not config: 
63            raise RunTimeError("No config to fedd.access")
64
65        self.project_priority = config.getboolean("access", "project_priority")
66
67        self.certdir = config.get("access","certdir")
68        self.create_debug = config.getboolean("access", "create_debug")
69        self.cleanup = not config.getboolean("access", "leave_tmpfiles")
70        self.access_type = config.get("access", "type")
71        self.log = logging.getLogger("fedd.access")
72        set_log_level(config, "access", self.log)
73        self.state_lock = Lock()
74        self.state = { }
[623a2c9]75        # subclasses fill with what and how they export.
76        self.exports = { }
[f771e2f]77        # XXX: Configurable
78        self.imports = set(('SMB', 'seer', 'userconfig', 'seer_master',
79            'hide_hosts'))
80
81        if auth: self.auth = auth
82        else:
83            self.log.error(\
84                    "[access]: No authorizer initialized, creating local one.")
85            auth = authorizer()
86
87        self.state_filename = config.get("access", "access_state")
88        self.read_state()
89
90        # Keep cert_file and cert_pwd coming from the same place
91        self.cert_file = config.get("access", "cert_file")
92        if self.cert_file:
[1027cf7]93            self.cert_pwd = config.get("access", "cert_pw")
[f771e2f]94        else:
95            self.cert_file = config.get("globals", "cert_file")
96            self.sert_pwd = config.get("globals", "cert_pw")
97
[4ffa6f8]98        self.nat_portal = None
99
[f771e2f]100        self.trusted_certs = config.get("access", "trusted_certs") or \
101                config.get("globals", "trusted_certs")
102
103
104    @staticmethod
105    def software_list(v):
106        """
107        From a string containing a sequence of space separated pairs, return a
108        list of tuples with pairs of location and file.
109        """
110        l = [ ]
111        if v:
112            ps = v.split(" ")
113            while len(ps):
114                loc, file = ps[0:2]
115                del ps[0:2]
116                l.append((loc, file))
117        return l
118
119    @staticmethod
120    def add_kit(e, kit):
121        """
122        Add a Software object created from the list of (install, location)
123        tuples passed as kit  to the software attribute of an object e.  We
124        do this enough to break out the code, but it's kind of a hack to
125        avoid changing the old tuple rep.
126        """
127
128        s = [ topdl.Software(install=i, location=l) for i, l in kit]
129
130        if isinstance(e.software, list): e.software.extend(s)
131        else: e.software = s
132
133
[dee164e]134    def read_access(self, fn, access_obj=None, default=[]):
[6e63513]135        """
136        Read an access DB of the form
137            abac.attribute -> local_auth_data
138        The access dict is filled with mappings from the abac attributes (as
139        strings) to the access objects.  The objects are strings by default,
140        but the class constructor is called with the string following the ->
141        and whitespace in the file.
142        """
143
[1f6a573]144        map_re = re.compile("(\S+)\s+->\s+(.*)")
145        priority_re = re.compile("([^,]+),\s*(\d+)")
146
[6e63513]147        if access_obj is None:
148            access_obj = lambda(x): "%s" % x
149
[c002cb2]150        self.access = []
[1f6a573]151        priorities = { }
[c002cb2]152
[6e63513]153        f = open(fn, 'r')
154        try:
155            lineno = 0
156            for line in f:
157                lineno += 1
158                line = line.strip();
159                if len(line) == 0 or line.startswith('#'):
160                    continue
161                m = map_re.match(line)
162                if m != None:
[c002cb2]163                    self.access.append(access_base.access_attribute(m.group(1),
164                        access_obj(m.group(2))))
[6e63513]165                    continue
166
[1f6a573]167                # If a priority is found, collect them
168                m = priority_re.match(line)
169                if m:
170                    try:
171                        priorities[m.group(1)] = int(m.group(2))
172                    except ValueError, e:
173                        if self.log:
174                            self.log.debug("Bad priority in %s line %d" % \
175                                    (fn, lineno))
176                    continue
177
[6e63513]178                # Nothing matched to here: unknown line - raise exception
179                # (finally will close f)
180                raise self.parse_error(
181                        "Unknown statement at line %d of %s" % \
182                        (lineno, fn))
183        finally:
184            if f: f.close()
185
[1f6a573]186        # Set priorities
187        for a in self.access:
188            if a.attr in priorities:
189                a.priority = priorities[a.attr]
190
[dee164e]191        # default access mappings
192        for a, v in default:
193            self.access.append(
194                    access_base.access_attribute(attr=a, value=v, pri=0))
195
196
197
[f771e2f]198    def write_state(self):
199        if self.state_filename:
200            try:
201                f = open(self.state_filename, 'w')
202                pickle.dump(self.state, f)
[06cc65b]203                self.log.debug("Wrote state to %s" % self.state_filename)
[f771e2f]204            except EnvironmentError, e:
205                self.log.error("Can't write file %s: %s" % \
206                        (self.state_filename, e))
207            except pickle.PicklingError, e:
208                self.log.error("Pickling problem: %s" % e)
209            except TypeError, e:
210                self.log.error("Pickling problem (TypeError): %s" % e)
211
212
213    def read_state(self):
214        """
215        Read a new copy of access state.  Old state is overwritten.
216
217        State format is a simple pickling of the state dictionary.
218        """
219        if self.state_filename:
220            try:
221                f = open(self.state_filename, "r")
222                self.state = pickle.load(f)
[06cc65b]223                self.log.debug("[read_state]: Read state from %s" % \
224                        self.state_filename)
[f771e2f]225            except EnvironmentError, e:
226                self.log.warning(("[read_state]: No saved state: " +\
227                        "Can't open %s: %s") % (self.state_filename, e))
228            except EOFError, e:
229                self.log.warning(("[read_state]: " +\
230                        "Empty or damaged state file: %s:") % \
231                        self.state_filename)
232            except pickle.UnpicklingError, e:
233                self.log.warning(("[read_state]: No saved state: " + \
234                        "Unpickling failed: %s") % e)
235
[c65b7e4]236    def append_allocation_authorization(self, aid, attrs, 
237            need_state_lock=False, write_state_file=False, state_attr='state'):
238        """
239        Append the authorization information to system state.  By default we
240        assume this is called with the state lock and with a write of the state
241        file in the near future, need_state_lock and write_state_file can
242        override this.  The state_attr is the attribute in the access class
243        that holds the per allocation information.  Some complex classes use
244        different names for the dict.
245        """
246
247        for p, a in attrs:
248            self.auth.set_attribute(p, a)
249        self.auth.save()
250
251        if need_state_lock: self.state_lock.acquire()
252        d = getattr(self, state_attr)
253        if aid in d and 'auth' in d[aid]:
254            d[aid]['auth'].update(attrs)
255        if write_state_file: self.write_state()
256        if need_state_lock: self.state_lock.release()
257
258    def clear_allocation_authorization(self, aid, need_state_lock=False,
259            write_state_file=False, state_attr='state'):
260        """
261        Attrs is a set of attribute principal pairs that need to be removed
262        from the authenticator.  Remove them and save the authenticator.  See
263        append_allocation_authorization for the various overrides.
264        """
265
266        if need_state_lock: self.state_lock.acquire()
267        d = getattr(self, state_attr)
268        if aid in d and 'auth' in d[aid]:
269            for p, a in d[aid]['auth']:
270                self.auth.unset_attribute(p, a)
271            d[aid]['auth'] = set()
272        if write_state_file: self.write_state()
273        if need_state_lock: self.state_lock.release()
274        self.auth.save()
275
[dee164e]276    def lookup_access(self, req, fid, filter=None, compare=None): 
277        """
278        Check all the attributes that this controller knows how to map and see
279        if the requester is allowed to use any of them.  If so return one.
280        Filter defined the objects to check - it's a function that returns true
281        for the objects to check - and cmp defines the order to check them in
282        as the cmp field of sorted().  If filter is None, all possibilities are
283        checked.  If cmp is None, the choices are sorted by priority.
284        """
285
286        # Import request credentials into this (clone later??)
287        if self.auth.import_credentials(
288                data_list=req.get('abac_credential', [])):
289            self.auth.save()
290
291        # NB: in the default case (the else), the comparison order is reversed
292        # so numerically larger priorities are checked first.
293        if compare: c = compare
[de86b35]294        else: c = lambda a, b: cmp(b,a)
[dee164e]295
296        if filter: f = filter
297        else: f = lambda(x): True
298
299        check = sorted([ a for a in self.access if f(a)], cmp=c)
300
301        # Check every attribute that we know how to map and take the first
302        # success.
[e83f2f2]303        fail_proofs = [ ]
[dee164e]304        for attr in check:
[e83f2f2]305            access_ok, proof = self.auth.check_attribute(fid, attr.attr,
306                    with_proof=True)
307            if access_ok:
[dee164e]308                self.log.debug("Access succeeded for %s %s" % (attr.attr, fid))
[ee950c2]309                return copy.copy(attr.value), [ fid ], proof
[dee164e]310            else:
[e83f2f2]311                fail_proofs.append(proof)
[dee164e]312                self.log.debug("Access failed for %s %s" % (attr.attr, fid))
313        else:
[8d6f204]314            self.log.debug("Access denied for for %s" % fid)
315            # We only return one fail proof because returning hundreds (which
316            # is easy to do) locks up the fault mechanism.
[b15ecc6]317            if len(fail_proofs) == 0:
318                self.log.debug('No choices either: %s' % check)
319                raise service_error(service_error.access, 
320                        "Access denied - no way to grant requested access")
321            raise service_error(service_error.access, "Access denied",
[8d6f204]322                    proof=fail_proofs[0])
[dee164e]323
324
[f771e2f]325
326    def get_handler(self, path, fid):
[78f2668]327        """
328        This function is somewhat oddly named.  It doesn't get a handler, it
329        handles GETs.  Specifically, it handls https GETs for retrieving data
330        from the repository exported by the access server.
331        """
[f771e2f]332        self.log.info("Get handler %s %s" % (path, fid))
[bcc6fd6]333        if len("%s" % fid) == 0:
334            return (None, None)
[f771e2f]335        if self.auth.check_attribute(fid, path) and self.userconfdir:
336            return ("%s/%s" % (self.userconfdir, path), "application/binary")
337        else:
338            return (None, None)
339
340    def export_userconf(self, project):
341        dev_null = None
342        confid, confcert = generate_fedid("test", dir=self.userconfdir, 
343                log=self.log)
344        conffilename = "%s/%s" % (self.userconfdir, str(confid))
345        cf = None
346        try:
347            cf = open(conffilename, "w")
348            os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR)
349        except EnvironmentError, e:
350            raise service_error(service_error.internal, 
351                    "Cannot create user configuration data")
352
353        try:
354            dev_null = open("/dev/null", "a")
355        except EnvironmentError, e:
356            self.log.error("export_userconf: can't open /dev/null: %s" % e)
357
358        cmd = "%s %s" % (self.userconfcmd, project)
[2303ca1]359        conf = subprocess.call(shlex.split(cmd),
[f771e2f]360                stdout=cf, stderr=dev_null, close_fds=True)
361
362        self.auth.set_attribute(confid, "/%s" % str(confid))
363
364        return confid, confcert
365
366    def export_SMB(self, id, state, project, user, attrs):
[623a2c9]367        if project and user:
368            return [{ 
369                    'id': id,
370                    'name': 'SMB',
371                    'visibility': 'export',
372                    'server': 'http://fs:139',
373                    'fedAttr': [
374                            { 'attribute': 'SMBSHARE', 'value': 'USERS' },
375                            { 'attribute': 'SMBUSER', 'value': user },
376                            { 'attribute': 'SMBPROJ', 'value': project },
377                        ]
378                    }]
379        else:
380            self.log.warn("Cannot export SMB w/o user and project")
381            return [ ]
[f771e2f]382
383    def export_seer(self, id, state, project, user, attrs):
[623a2c9]384        return [{ 
[f771e2f]385                'id': id,
386                'name': 'seer',
387                'visibility': 'export',
388                'server': 'http://control:16606',
[623a2c9]389                }]
[f771e2f]390
391    def export_local_seer(self, id, state, project, user, attrs):
[623a2c9]392        return [{ 
[f771e2f]393                'id': id,
394                'name': 'local_seer_control',
395                'visibility': 'export',
396                'server': 'http://control:16606',
[623a2c9]397                }]
[f771e2f]398
399    def export_seer_master(self, id, state, project, user, attrs):
[623a2c9]400        return [{ 
[f771e2f]401                'id': id,
402                'name': 'seer_master',
403                'visibility': 'export',
404                'server': 'http://seer-master:17707',
[623a2c9]405                }]
[f771e2f]406
407    def export_tmcd(self, id, state, project, user, attrs):
[623a2c9]408        return [{ 
[f771e2f]409                'id': id,
410                'name': 'seer',
411                'visibility': 'export',
412                'server': 'http://boss:7777',
[623a2c9]413                }]
[f771e2f]414
415    def export_userconfig(self, id, state, project, user, attrs):
416        if self.userconfdir and self.userconfcmd \
417                and self.userconfurl:
418            cid, cert = self.export_userconf(project)
419            state['userconfig'] = unicode(cid)
[623a2c9]420            return [{
[f771e2f]421                    'id': id,
422                    'name': 'userconfig',
423                    'visibility': 'export',
424                    'server': "%s/%s" % (self.userconfurl, str(cid)),
425                    'fedAttr': [
426                        { 'attribute': 'cert', 'value': cert },
427                    ]
[623a2c9]428                    }]
[f771e2f]429        else:
[623a2c9]430            return [ ]
[f771e2f]431
432    def export_hide_hosts(self, id, state, project, user, attrs):
[623a2c9]433        return [{
[f771e2f]434                'id': id, 
435                'name': 'hide_hosts',
436                'visibility': 'export',
437                'fedAttr': [ x for x in attrs \
438                        if x.get('attribute', "") == 'hosts'],
[623a2c9]439                }]
[f771e2f]440
[623a2c9]441    def export_project_export(self, id, state, project, user, attrs):
442        rv = [ ]
443        rv.extend(self.export_SMB(id, state, project, user, attrs))
444        rv.extend(self.export_userconfig(id, state, project, user, attrs))
445        return rv
446
447    def export_services(self, sreq, project=None, user=None):
[f771e2f]448        exp = [ ]
449        state = { }
450        for s in sreq:
451            sname = s.get('name', '')
452            svis = s.get('visibility', '')
453            sattrs = s.get('fedAttr', [])
454            if svis == 'export':
455                if sname in self.exports:
456                    id = s.get('id', 'no_id')
[623a2c9]457                    exp.extend(self.exports[sname](id, state, project, user,
[f771e2f]458                            sattrs))
[623a2c9]459
[f771e2f]460        return (exp, state)
461
[ee950c2]462    def build_access_response(self, alloc_id, pname, services, proof):
[f771e2f]463        """
464        Create the SOAP response.
465
466        Build the dictionary description of the response and use
467        fedd_utils.pack_soap to create the soap message.  ap is the allocate
468        project message returned from a remote project allocation (even if that
469        allocation was done locally).
470        """
471        # Because alloc_id is already a fedd_services_types.IDType_Holder,
472        # there's no need to repack it
473        msg = { 
474                'allocID': alloc_id,
[e83f2f2]475                'proof': proof.to_dict(),
[f771e2f]476                'fedAttr': [
477                    { 'attribute': 'domain', 'value': self.domain } , 
478                ]
479            }
480
[ee950c2]481        if pname:
482            msg['fedAttr'].append({ 'attribute': 'project', 'value': pname })
[f771e2f]483        if self.dragon_endpoint:
484            msg['fedAttr'].append({'attribute': 'dragon',
485                'value': self.dragon_endpoint})
486        if self.deter_internal:
487            msg['fedAttr'].append({'attribute': 'deter_internal',
488                'value': self.deter_internal})
489        #XXX: ??
490        if self.dragon_vlans:
491            msg['fedAttr'].append({'attribute': 'vlans',
492                'value': self.dragon_vlans})
493
[4ffa6f8]494        if self.nat_portal is not None:
495            msg['fedAttr'].append({'attribute': 'nat_portals', 'value': True})
496
[f771e2f]497        if services:
498            msg['service'] = services
499        return msg
500
501    def generate_portal_configs(self, topo, pubkey_base, secretkey_base, 
[d6990a4]502            tmpdir, lproj, leid, connInfo, services):
[f771e2f]503
504        def conninfo_to_dict(key, info):
505            """
506            Make a cpoy of the connection information about key, and flatten it
507            into a single dict by parsing out any feddAttrs.
508            """
509
510            rv = None
511            for i in info:
512                if key == i.get('portal', "") or \
513                        key in [e.get('element', "") \
514                        for e in i.get('member', [])]:
515                    rv = i.copy()
516                    break
517
518            else:
519                return rv
520
521            if 'fedAttr' in rv:
522                for a in rv['fedAttr']:
523                    attr = a.get('attribute', "")
524                    val = a.get('value', "")
525                    if attr and attr not in rv:
526                        rv[attr] = val
527                del rv['fedAttr']
528            return rv
529
530        # XXX: un hardcode this
531        def client_null(f, s):
532            print >>f, "Service: %s" % s['name']
533
534        def client_seer_master(f, s):
[703859f]535            print >>f, 'PortalAlias: seer-master'
[f771e2f]536
537        def client_smb(f, s):
538            print >>f, "Service: %s" % s['name']
539            smbshare = None
540            smbuser = None
541            smbproj = None
542            for a in s.get('fedAttr', []):
543                if a.get('attribute', '') == 'SMBSHARE':
544                    smbshare = a.get('value', None)
545                elif a.get('attribute', '') == 'SMBUSER':
546                    smbuser = a.get('value', None)
547                elif a.get('attribute', '') == 'SMBPROJ':
548                    smbproj = a.get('value', None)
549
550            if all((smbshare, smbuser, smbproj)):
551                print >>f, "SMBshare: %s" % smbshare
552                print >>f, "ProjectUser: %s" % smbuser
553                print >>f, "ProjectName: %s" % smbproj
554
555        def client_hide_hosts(f, s):
556            for a in s.get('fedAttr', [ ]):
557                if a.get('attribute', "") == 'hosts':
558                    print >>f, "Hide: %s" % a.get('value', "")
559
560        client_service_out = {
561                'SMB': client_smb,
562                'tmcd': client_null,
563                'seer': client_null,
564                'userconfig': client_null,
565                'project_export': client_null,
566                'seer_master': client_seer_master,
567                'hide_hosts': client_hide_hosts,
568            }
569
570        def client_seer_master_export(f, s):
571            print >>f, "AddedNode: seer-master"
572
573        def client_seer_local_export(f, s):
574            print >>f, "AddedNode: control"
575
576        client_export_service_out = {
577                'seer_master': client_seer_master_export,
578                'local_seer_control': client_seer_local_export,
579            }
580
581        def server_port(f, s):
582            p = urlparse(s.get('server', 'http://localhost'))
583            print >>f, 'port: remote:%s:%s:%s' % (p.port, p.hostname, p.port) 
584
585        def server_null(f,s): pass
586
587        def server_seer(f, s):
588            print >>f, 'seer: True'
589
590        server_service_out = {
591                'SMB': server_port,
592                'tmcd': server_port,
593                'userconfig': server_null,
594                'project_export': server_null,
595                'seer': server_seer,
596                'seer_master': server_port,
597                'hide_hosts': server_null,
598            }
599        # XXX: end un hardcode this
600
601
602        seer_out = False
603        client_out = False
604        mproj = None
605        mexp = None
606        control_gw = None
607        testbed = ""
608        # Create configuration files for the portals
609        for e in [ e for e in topo.elements \
610                if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
611            myname = e.name
612            type = e.get_attribute('portal_type')
613
614            info = conninfo_to_dict(myname, connInfo)
615
616            if not info:
617                raise service_error(service_error.req,
618                        "No connectivity info for %s" % myname)
619
620            peer = info.get('peer', "")
[703859f]621            ldomain = self.domain
[f771e2f]622            ssh_port = info.get('ssh_port', 22)
623
624            # Collect this for the client.conf file
625            if 'masterexperiment' in info:
626                mproj, meid = info['masterexperiment'].split("/", 1)
627
628            if type in ('control', 'both'):
629                testbed = e.get_attribute('testbed')
630                control_gw = myname
631
632            active = info.get('active', 'False')
[4ffa6f8]633            nat_partner = info.get('nat_partner', 'False')
[f771e2f]634
635            cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
636            tunnelconfig = self.tunnel_config
637            try:
638                f = open(cfn, "w")
639                if active == 'True':
640                    print >>f, "active: True"
641                    print >>f, "ssh_port: %s" % ssh_port
642                    if type in ('control', 'both'):
643                        for s in [s for s in services \
644                                if s.get('name', "") in self.imports]:
645                            server_service_out[s['name']](f, s)
646
[4ffa6f8]647                if nat_partner == 'True':
648                    print >>f, "nat_partner: True"
649
[f771e2f]650                if tunnelconfig:
651                    print >>f, "tunnelip: %s" % tunnelconfig
652                print >>f, "peer: %s" % peer.lower()
653                print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \
654                        (lproj, leid, pubkey_base)
655                print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \
656                        (lproj, leid, secretkey_base)
657                f.close()
658            except EnvironmentError, e:
659                raise service_error(service_error.internal,
660                        "Can't write protal config %s: %s" % (cfn, e))
661
662        # Done with portals, write the client config file.
663        try:
664            f = open("%s/client.conf" % tmpdir, "w")
665            if control_gw:
666                print >>f, "ControlGateway: %s.%s.%s%s" % \
667                    (myname.lower(), leid.lower(), lproj.lower(),
668                            ldomain.lower())
669            for s in services:
670                if s.get('name',"") in self.imports and \
671                        s.get('visibility','') == 'import':
672                    client_service_out[s['name']](f, s)
673                if s.get('name', '') in self.exports and \
674                        s.get('visibility', '') == 'export' and \
675                        s['name'] in client_export_service_out:
676                    client_export_service_out[s['name']](f, s)
677            # Seer uses this.
678            if mproj and meid:
679                print >>f, "ExperimentID: %s/%s" % (mproj, meid)
680            f.close()
681        except EnvironmentError, e:
682            raise service_error(service_error.internal,
683                    "Cannot write client.conf: %s" %s)
684
[623a2c9]685    def configure_userconf(self, services, tmpdir):
686        """
687        If the userconf service was imported, collect the configuration data.
688        """
689        for s in services:
690            s_name = s.get('name', '')
691            s_vis = s.get('visibility','')
692            if s_name  == 'userconfig' and s_vis == 'import':
693                # Collect ther server and certificate info.
694                u = s.get('server', None)
695                for a in s.get('fedAttr', []):
696                    if a.get('attribute',"") == 'cert':
697                        cert = a.get('value', None)
698                        break
699                else:
700                    cert = None
701
702                if cert:
703                    # Make a temporary certificate file for get_url.  The
704                    # finally clause removes it whether something goes
705                    # wrong (including an exception from get_url) or not.
706                    try:
707                        tfos, tn = tempfile.mkstemp(suffix=".pem")
708                        tf = os.fdopen(tfos, 'w')
709                        print >>tf, cert
710                        tf.close()
711                        self.log.debug("Getting userconf info: %s" % u)
712                        get_url(u, tn, tmpdir, "userconf")
713                        self.log.debug("Got userconf info: %s" % u)
714                    except EnvironmentError, e:
715                        raise service_error(service.error.internal, 
716                                "Cannot create temp file for " + 
717                                "userconfig certificates: %s" % e)
718                    except:
719                        t, v, st = sys.exc_info()
720                        raise service_error(service_error.internal,
721                                "Error retrieving %s: %s" % (u, v))
722                    finally:
723                        if tn: os.remove(tn)
724                else:
725                    raise service_error(service_error.req,
726                            "No certificate for retreiving userconfig")
727                break
728
[f771e2f]729    def import_store_info(self, cf, connInfo):
730        """
731        Pull any import parameters in connInfo in.  We translate them either
732        into known member names or fedAddrs.
733        """
734
735        for c in connInfo:
736            for p in [ p for p in c.get('parameter', []) \
737                    if p.get('type', '') == 'input']:
738                name = p.get('name', None)
739                key = p.get('key', None)
740                store = p.get('store', None)
741
742                if name and key and store :
743                    req = { 'name': key, 'wait': True }
744                    self.log.debug("Waiting for %s (%s) from %s" % \
745                            (name, key, store))
746                    r = self.call_GetValue(store, req, cf)
747                    r = r.get('GetValueResponseBody', None)
748                    if r :
749                        if r.get('name', '') == key:
750                            v = r.get('value', None)
751                            if v is not None:
752                                if name == 'peer':
753                                    self.log.debug("Got peer %s" % v)
754                                    c['peer'] = v
755                                else:
756                                    self.log.debug("Got %s %s" % (name, v))
757                                    if c.has_key('fedAttr'):
758                                        c['fedAttr'].append({
759                                            'attribute': name, 'value': v})
760                                    else:
761                                        c['fedAttr']= [{
762                                            'attribute': name, 'value': v}]
763                            else:
764                                raise service_error(service_error.internal, 
765                                        'None value exported for %s'  % key)
766                        else:
767                            raise service_error(service_error.internal, 
768                                    'Different name returned for %s: %s' \
769                                            % (key, r.get('name','')))
770                    else:
771                        raise service_error(service_error.internal, 
772                            'Badly formatted response: no GetValueResponseBody')
773                else:
774                    raise service_error(service_error.internal, 
775                        'Bad Services missing info for import %s' % c)
[623a2c9]776
777    def remove_dirs(self, dir):
778        """
779        Remove the directory tree and all files rooted at dir.  Log any errors,
780        but continue.
781        """
782        self.log.debug("[removedirs]: removing %s" % dir)
783        try:
784            for path, dirs, files in os.walk(dir, topdown=False):
785                for f in files:
786                    os.remove(os.path.join(path, f))
787                for d in dirs:
788                    os.rmdir(os.path.join(path, d))
789            os.rmdir(dir)
790        except EnvironmentError, e:
791            self.log.error("Error deleting directory tree in %s" % e);
[9973d57]792
793    def RequestAccess(self, req, fid):
794        """
795        Handle an access request.  Success here maps the requester into the
796        local access control space and establishes state about that user keyed
797        to a fedid.  We also save a copy of the certificate underlying that
798        fedid so this allocation can access configuration information and
799        shared parameters on the experiment controller.
800        """
801
[8cab4c2]802        self.log.info("RequestAccess called by %s" % fid)
[9973d57]803        # The dance to get into the request body
804        if req.has_key('RequestAccessRequestBody'):
805            req = req['RequestAccessRequestBody']
806        else:
807            raise service_error(service_error.req, "No request!?")
808
809        # Base class lookup routine.  If this fails, it throws a service
810        # exception denying access that triggers a fault response back to the
811        # caller.
[923984c]812        found,  owners, proof = self.lookup_access(req, fid)
[9973d57]813        self.log.info(
[923984c]814                "[RequestAccess] Access granted local creds %s" % found)
[9973d57]815        # Make a fedid for this allocation
816        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
817        aid = unicode(allocID)
818
819        # Store the data about this allocation:
820        self.state_lock.acquire()
821        self.state[aid] = { }
822        self.state[aid]['user'] = found
823        self.state[aid]['owners'] = owners
824        self.state[aid]['auth'] = set()
825        # Authorize the creating fedid and the principal representing the
826        # allocation to manipulate it.
827        self.append_allocation_authorization(aid, 
828                ((fid, allocID), (allocID, allocID)))
829        self.write_state()
830        self.state_lock.release()
831
832        # Create a directory to stash the certificate in, ans stash it.
833        try:
834            f = open("%s/%s.pem" % (self.certdir, aid), "w")
835            print >>f, alloc_cert
836            f.close()
837        except EnvironmentError, e:
838            raise service_error(service_error.internal, 
839                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
840        self.log.debug('[RequestAccess] Returning allocation ID: %s' % allocID)
[e83f2f2]841        return { 'allocID': { 'fedid': allocID }, 'proof': proof.to_dict() }
[9973d57]842
843    def ReleaseAccess(self, req, fid):
844        """
845        Release the allocation granted earlier.  Access to the allocation is
846        checked and if valid, the state and cached certificate are destroyed.
847        """
[8cab4c2]848        self.log.info("ReleaseAccess called by %s" % fid)
[9973d57]849        # The dance to get into the request body
850        if req.has_key('ReleaseAccessRequestBody'):
851            req = req['ReleaseAccessRequestBody']
852        else:
853            raise service_error(service_error.req, "No request!?")
854
855        # Pull a key out of the request.  One can request to delete an
856        # allocation by a local human readable name or by a fedid.  This finds
857        # both choices.
858        try:
859            if 'localname' in req['allocID']:
860                auth_attr = aid = req['allocID']['localname']
861            elif 'fedid' in req['allocID']:
862                aid = unicode(req['allocID']['fedid'])
863                auth_attr = req['allocID']['fedid']
864            else:
865                raise service_error(service_error.req,
866                        "Only localnames and fedids are understood")
867        except KeyError:
868            raise service_error(service_error.req, "Badly formed request")
869
870        self.log.debug("[ReleaseAccess] deallocation requested for %s", aid)
871        #  Confirm access
[e83f2f2]872        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
873                with_proof=True)
874        if not access_ok:
[9973d57]875            self.log.debug("[ReleaseAccess] deallocation denied for %s", aid)
[e83f2f2]876            raise service_error(service_error.access, "Access Denied",
877                    proof=proof)
[9973d57]878
879        # If there is an allocation in the state, delete it.  Note the locking.
880        self.state_lock.acquire()
881        if aid in self.state:
882            self.log.debug("[ReleaseAccess] Found allocation for %s" %aid)
883            self.clear_allocation_authorization(aid)
884            del self.state[aid]
885            self.write_state()
886            self.state_lock.release()
887            # And remove the access cert
888            cf = "%s/%s.pem" % (self.certdir, aid)
889            self.log.debug("[ReleaseAccess] Removing %s" % cf)
890            os.remove(cf)
[8cab4c2]891            self.log.info("ReleaseAccess succeeded for %s" % fid)
[e83f2f2]892            return { 'allocID': req['allocID'], 'proof': proof.to_dict() } 
[9973d57]893        else:
894            self.state_lock.release()
895            raise service_error(service_error.req, "No such allocation")
Note: See TracBrowser for help on using the repository browser.