source: fedd/fedd_access.py @ 0ea5050

axis_examplecompt_changesinfo-opsversion-1.30version-2.00version-3.01version-3.02
Last change on this file since 0ea5050 was 40eab39, checked in by Ted Faber <faber@…>, 16 years ago

Better debugging logging for acess decisions. Rename some attributes to a more general naming convention.

  • Property mode set to 100644
File size: 25.0 KB
RevLine 
[19cc408]1#!/usr/local/bin/python
2
3import os,sys
4import re
5import string
6import copy
[d81971a]7import pickle
[c971895]8import logging
[19cc408]9
[f8582c9]10from threading import *
11
[19cc408]12from fedd_services import *
13from fedd_util import *
14from fedd_allocate_project import *
[c971895]15from fedd_access_project import access_project
[51cc9df]16from fedid import fedid, generate_fedid
[3f6bc5f]17from authorizer import authorizer
[19cc408]18import parse_detail
19from service_error import *
[9460b1e]20from remote_service import xmlrpc_handler, soap_handler, service_caller
[11a08b0]21
[0ea11af]22
23# Make log messages disappear if noone configures a fedd logger
[11a08b0]24class nullHandler(logging.Handler):
25    def emit(self, record): pass
26
27fl = logging.getLogger("fedd.access")
28fl.addHandler(nullHandler())
[19cc408]29
30class fedd_access:
31    """
32    The implementation of access control based on mapping users to projects.
33
34    Users can be mapped to existing projects or have projects created
35    dynamically.  This implements both direct requests and proxies.
36    """
37
[4ed10ae]38    class parse_error(RuntimeError): pass
39
[40eab39]40    bool_attrs = ("project_priority", "allow_proxy")
[72ed6e4]41    emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver")
42    id_attrs = ("testbed", "proxy",
43            "proxy_cert_file", "proxy_cert_pwd", "proxy_trusted_certs",
[40eab39]44            "project_allocation_uri", "project_allocation_cert_file", 
45            "project_allocation_cert_pwd", "project_allocation_trusted_certs")
[72ed6e4]46    id_list_attrs = ("restricted",)
47
[058f58e]48    proxy_RequestAccess= \
49            service_caller('RequestAccess', 'getfeddPortType', 
50                    feddServiceLocator, RequestAccessRequestMessage,
51                    'RequestAccessRequestBody')
[c922f23]52
[c35207d]53    proxy_ReleaseAccess= \
54            service_caller('ReleaseAccess', 'getfeddPortType', 
55                    feddServiceLocator, ReleaseAccessRequestMessage,
56                    'ReleaseAccessRequestBody')
57
[3f6bc5f]58    def __init__(self, config=None, auth=None):
[f8582c9]59        """
60        Initializer.  Pulls parameters out of the ConfigParser's access section.
61        """
62
63        # Make sure that the configuration is in place
64        if config: 
65            if not config.has_section("access"):
66                config.add_section("access")
67            if not config.has_section("globals"):
68                config.add_section("globals")
69        else:
70            raise RunTimeError("No config to fedd_access")
71
72        # Create instance attributes from the static lists
73        for a in fedd_access.bool_attrs:
74            if config.has_option("access", a):
75                setattr(self, a, config.get("access", a))
76            else:
77                setattr(self, a, False)
78
79        for a in fedd_access.emulab_attrs + fedd_access.id_attrs:
80            if config.has_option("access", a):
81                setattr(self, a, config.get("access",a))
82            else:
83                setattr(self, a, None)
84
85        self.attrs = { }
86        self.access = { }
87        self.restricted = [ ]
88        self.fedid_category = { }
89        self.projects = { }
90        self.keys = { }
91        self.allocation = { }
92        self.state = { 
93            'projects': self.projects,
94            'allocation' : self.allocation,
95            'keys' : self.keys
96        }
[3f6bc5f]97        self.log = logging.getLogger("fedd.access")
98        set_log_level(config, "access", self.log)
[f8582c9]99        self.state_lock = Lock()
[3f6bc5f]100
101        if auth: self.auth = auth
102        else:
103            self.log.error(\
104                    "[access]: No authorizer initialized, creating local one.")
105            auth = authorizer()
106
[f8582c9]107        self.fedid_default = "testbed"
108        if config.has_option("access", "accessdb"):
109            self.read_access(config.get("access", "accessdb"))
110
111        self.state_filename = config.get("access", "access_state", "")
112        self.read_state()
113        # Certs are promoted from the generic to the specific, so without a
114        # specific proxy certificate, the main certificates are used for
115        # proxy interactions. If no dynamic project certificates, then
116        # proxy certs are used, and if none of those the main certs.
117
118        if config.has_option("globals", "proxy_cert_file"):
[40eab39]119            if not self.project_allocation_cert_file:
120                self.project_allocation_cert_file = \
[f8582c9]121                        config.get("globals", "proxy_cert_file")
[1653a08]122                if config.has_option("globals", "proxy_cert_pwd"):
[40eab39]123                    self.project_allocation_cert_pwd = \
[f8582c9]124                            config.get("globals", "proxy_cert_pwd")
125
126        if config.has_option("globals", "proxy_trusted_certs"):
[40eab39]127            if not self.project_allocation_trusted_certs:
128                self.project_allocation_trusted_certs =\
[f8582c9]129                        config.get("globals", proxy_trusted_certs)
130
131        if config.has_option("globals", "cert_file"):
132            has_pwd = config.has_option("globals", "cert_pwd")
[40eab39]133            if not self.project_allocation_cert_file:
134                self.project_allocation_cert_file = \
[f8582c9]135                        config.get("globals", "cert_file")
136                if has_pwd: 
[40eab39]137                    self.project_allocation_cert_pwd = \
[f8582c9]138                            config.get("globals", "cert_pwd")
139            if not self.proxy_cert_file:
140                self.proxy_cert_file = config.get("globals", "cert_file")
141                if has_pwd:
142                    self.proxy_cert_pwd = config.get("globals", "cert_pwd")
143
144        if config.get("globals", "trusted_certs"):
145            if not self.proxy_trusted_certs:
146                self.proxy_trusted_certs = \
147                        config.get("globals", "trusted_certs")
[40eab39]148            if not self.project_allocation_trusted_certs:
149                self.project_allocation_trusted_certs = \
[f8582c9]150                        config.get("globals", "trusted_certs")
151
[40eab39]152        proj_certs = (self.project_allocation_cert_file, 
153                self.project_allocation_trusted_certs,
154                self.project_allocation_cert_pwd)
[f8582c9]155
156        self.soap_services = {\
[058f58e]157            'RequestAccess': soap_handler(\
[f8582c9]158                RequestAccessRequestMessage.typecode,\
159                self.RequestAccess, RequestAccessResponseMessage,\
160                "RequestAccessResponseBody"), \
[058f58e]161            'ReleaseAccess': soap_handler(\
[f8582c9]162                ReleaseAccessRequestMessage.typecode,\
163                self.ReleaseAccess, ReleaseAccessResponseMessage,\
164                "ReleaseAccessResponseBody")\
165            }
166        self.xmlrpc_services =  {\
[058f58e]167            'RequestAccess': xmlrpc_handler(\
[f8582c9]168                self.RequestAccess, "RequestAccessResponseBody"),\
[058f58e]169            'ReleaseAccess': xmlrpc_handler(\
[f8582c9]170                self.ReleaseAccess, "ReleaseAccessResponseBody")\
171            }
172
173
[40eab39]174        if not config.has_option("access", "project_allocation_uri"):
[f8582c9]175            self.allocate_project = \
[3f6bc5f]176                fedd_allocate_project_local(config, auth)
[f8582c9]177        else:
178            self.allocate_project = \
[3f6bc5f]179                fedd_allocate_project_remote(config, auth)
[f8582c9]180
181        # If the project allocator exports services, put them in this object's
182        # maps so that classes that instantiate this can call the services.
183        self.soap_services.update(self.allocate_project.soap_services)
184        self.xmlrpc_services.update(self.allocate_project.xmlrpc_services)
185
[72ed6e4]186
187    def read_access(self, config):
188        """
189        Read a configuration file and set internal parameters.
190
191        The format is more complex than one might hope.  The basic format is
192        attribute value pairs separated by colons(:) on a signle line.  The
193        attributes in bool_attrs, emulab_attrs and id_attrs can all be set
194        directly using the name: value syntax.  E.g.
195        boss: hostname
196        sets self.boss to hostname.  In addition, there are access lines of the
197        form (tb, proj, user) -> (aproj, auser) that map the first tuple of
198        names to the second for access purposes.  Names in the key (left side)
199        can include "<NONE> or <ANY>" to act as wildcards or to require the
200        fields to be empty.  Similarly aproj or auser can be <SAME> or
201        <DYNAMIC> indicating that either the matching key is to be used or a
202        dynamic user or project will be created.  These names can also be
203        federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
204        can be followed with a colon-separated list of node types to which that
205        project has access (or will have access if dynamic).
206        Testbed attributes outside the forms above can be given using the
207        format attribute: name value: value.  The name is a single word and the
208        value continues to the end of the line.  Empty lines and lines startin
209        with a # are ignored.
210
[4ed10ae]211        Parsing errors result in a self.parse_error exception being raised.
[72ed6e4]212        """
213        lineno=0
214        name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
215        fedid_expr = "fedid:[" + string.hexdigits + "]+"
216        key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
217        access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
218                "<SAME>" + "(?::" + name_expr + ")*|" + \
219                fedid_expr + "(?::" + name_expr + ")*|" + \
220                name_expr + "(?::" + name_expr + ")*)"
221        access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
222
223        restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
224        attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)',
225                re.IGNORECASE)
226        access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
227                key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + 
[4ed10ae]228                access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
[72ed6e4]229
230        def parse_name(n):
[3f6bc5f]231            if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
[72ed6e4]232            else: return n
[3f6bc5f]233       
234        def auth_name(n):
235            if isinstance(n, basestring):
236                if n =='<any>' or n =='<none>': return None
237                else: return unicode(n)
238            else:
239                return n
[72ed6e4]240
241        f = open(config, "r");
242        for line in f:
243            lineno += 1
244            line = line.strip();
245            if len(line) == 0 or line.startswith('#'):
246                continue
247
248            # Extended (attribute: x value: y) attribute line
249            m = attr_re.match(line)
250            if m != None:
251                attr, val = m.group(1,2)
252                self.attrs[attr] = val
253                continue
254
255            # Restricted entry
256            m = restricted_re.match(line)
257            if m != None:
258                val = m.group(1)
259                self.restricted.append(val)
260                continue
261
[4ed10ae]262            # Access line (t, p, u) -> (ap, cu, su) line
[72ed6e4]263            m = access_re.match(line)
264            if m != None:
265                access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
[3f6bc5f]266                auth_key = tuple([ auth_name(x) for x in access_key])
[72ed6e4]267                aps = m.group(4).split(":");
268                if aps[0] == 'fedid:':
269                    del aps[0]
270                    aps[0] = fedid(hexstr=aps[0])
271
[4ed10ae]272                cu = parse_name(m.group(5))
273                su = parse_name(m.group(6))
[72ed6e4]274
[4ed10ae]275                access_val = (access_project(aps[0], aps[1:]),
276                        parse_name(m.group(5)), parse_name(m.group(6)))
[72ed6e4]277
278                self.access[access_key] = access_val
[3f6bc5f]279                self.auth.set_attribute(auth_key, "access")
[72ed6e4]280                continue
281
282            # Nothing matched to here: unknown line - raise exception
283            f.close()
[4ed10ae]284            raise self.parse_error("Unknown statement at line %d of %s" % \
[72ed6e4]285                    (lineno, config))
286        f.close()
287
288
[19cc408]289    def dump_state(self):
290        """
291        Dump the state read from a configuration file.  Mostly for debugging.
292        """
[72ed6e4]293        for a in fedd_access.bool_attrs:
[19cc408]294            print "%s: %s" % (a, getattr(self, a ))
[72ed6e4]295        for a in fedd_access.emulab_attrs + fedd_access.id_attrs:
[19cc408]296            print "%s: %s" % (a, getattr(self, a))
297        for k, v in self.attrs.iteritems():
298            print "%s %s" % (k, v)
299        print "Access DB:"
300        for k, v in self.access.iteritems():
301            print "%s %s" % (k, v)
302        print "Trust DB:"
303        for k, v in self.fedid_category.iteritems():
304            print "%s %s" % (k, v)
305        print "Restricted: %s" % str(',').join(sorted(self.restricted))
306
307    def get_users(self, obj):
308        """
309        Return a list of the IDs of the users in dict
310        """
311        if obj.has_key('user'):
312            return [ unpack_id(u['userID']) \
313                    for u in obj['user'] if u.has_key('userID') ]
314        else:
315            return None
316
[d81971a]317    def write_state(self):
318        try:
319            f = open(self.state_filename, 'w')
320            pickle.dump(self.state, f)
321        except IOError, e:
322            self.log.error("Can't write file %s: %s" % \
323                    (self.state_filename, e))
324        except pickle.PicklingError, e:
325            self.log.error("Pickling problem: %s" % e)
326        except TypeError, e:
327            self.log.error("Pickling problem (TypeError): %s" % e)
328
329
330    def read_state(self):
331        """
332        Read a new copy of access state.  Old state is overwritten.
333
334        State format is a simple pickling of the state dictionary.
335        """
336        try:
337            f = open(self.state_filename, "r")
338            self.state = pickle.load(f)
339
340            self.allocation = self.state['allocation']
341            self.projects = self.state['projects']
342            self.keys = self.state['keys']
343
344            self.log.debug("[read_state]: Read state from %s" % \
345                    self.state_filename)
346        except IOError, e:
347            self.log.warning("[read_state]: No saved state: Can't open %s: %s"\
348                    % (self.state_filename, e))
349        except EOFError, e:
350            self.log.warning("[read_state]: Empty or damaged state file: %s:"\
351                    % self.state_filename)
352        except pickle.UnpicklingError, e:
353            self.log.warning(("[read_state]: No saved state: " + \
354                    "Unpickling failed: %s") % e)
355
[ac54ef3]356        # Add the ownership attributes to the authorizer.  Note that the
357        # indices of the allocation dict are strings, but the attributes are
358        # fedids, so there is a conversion.
[3f6bc5f]359        for k in self.allocation.keys():
360            for o in self.allocation[k].get('owners', []):
361                self.auth.set_attribute(o, fedid(hexstr=k))
362
363
[19cc408]364    def permute_wildcards(self, a, p):
365        """Return a copy of a with various fields wildcarded.
366
367        The bits of p control the wildcards.  A set bit is a wildcard
368        replacement with the lowest bit being user then project then testbed.
369        """
370        if p & 1: user = ["<any>"]
371        else: user = a[2]
372        if p & 2: proj = "<any>"
373        else: proj = a[1]
374        if p & 4: tb = "<any>"
375        else: tb = a[0]
376
377        return (tb, proj, user)
378
379    def find_access(self, search):
380        """
381        Search the access DB for a match on this tuple.  Return the matching
382        access tuple and the user that matched.
383       
384        NB, if the initial tuple fails to match we start inserting wildcards in
385        an order determined by self.project_priority.  Try the list of users in
386        order (when wildcarded, there's only one user in the list).
387        """
388        if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
389        else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
390
391        for p in perm: 
392            s = self.permute_wildcards(search, p)
393            # s[2] is None on an anonymous, unwildcarded request
394            if s[2] != None:
395                for u in s[2]:
396                    if self.access.has_key((s[0], s[1], u)):
397                        return (self.access[(s[0], s[1], u)], u)
398            else:
399                if self.access.has_key(s):
400                    return (self.access[s], None)
401        return None, None
402
403    def lookup_access(self, req, fid):
404        """
405        Determine the allowed access for this request.  Return the access and
406        which fields are dynamic.
407
408        The fedid is needed to construct the request
409        """
410        # Search keys
411        tb = None
412        project = None
413        user = None
414        # Return values
415        rp = access_project(None, ())
416        ru = None
417
418        if req.has_key('project'):
419            p = req['project']
420            if p.has_key('name'):
421                project = unpack_id(p['name'])
422            user = self.get_users(p)
423        else:
424            user = self.get_users(req)
425
[3f6bc5f]426        user_fedids = [ u for u in user if isinstance(u, fedid)]
427        # Determine how the caller is representing itself.  If its fedid shows
428        # up as a project or a singleton user, let that stand.  If neither the
429        # usernames nor the project name is a fedid, the caller is a testbed.
430        if project and isinstance(project, fedid):
431            if project == fid:
432                # The caller is the project (which is already in the tuple
433                # passed in to the authorizer)
434                owners = user_fedids
435                owners.append(project)
436            else:
437                raise service_error(service_error.req,
438                        "Project asserting different fedid")
439        else:
440            if fid not in user_fedids:
441                tb = fid
442                owners = user_fedids
443                owners.append(fid)
444            else:
[19cc408]445                if len(fedids) > 1:
446                    raise service_error(service_error.req,
447                            "User asserting different fedid")
[3f6bc5f]448                else:
449                    # Which is a singleton
450                    owners = user_fedids
451        # Confirm authorization
452        for u in user:
[40eab39]453            self.log.debug("[lookup_access] Checking access for %s" % \
454                    ((tb, project, u),))
[3f6bc5f]455            if self.auth.check_attribute((tb, project, u), 'access'):
[40eab39]456                self.log.debug("[lookup_access] Access granted")
[3f6bc5f]457                break
[40eab39]458            else:
459                self.log.debug("[lookup_access] Access Denied")
[3f6bc5f]460        else:
461            raise service_error(service_error.access, "Access denied")
[19cc408]462
[3f6bc5f]463        # This maps a valid user to the Emulab projects and users to use
[19cc408]464        found, user_match = self.find_access((tb, project, user))
465       
466        if found == None:
467            raise service_error(service_error.access,
[3f6bc5f]468                    "Access denied - cannot map access")
[19cc408]469
470        # resolve <dynamic> and <same> in found
471        dyn_proj = False
[4ed10ae]472        dyn_create_user = False
473        dyn_service_user = False
[19cc408]474
475        if found[0].name == "<same>":
476            if project != None:
477                rp.name = project
478            else : 
479                raise service_error(\
480                        service_error.server_config,
481                        "Project matched <same> when no project given")
482        elif found[0].name == "<dynamic>":
483            rp.name = None
484            dyn_proj = True
485        else:
486            rp.name = found[0].name
487        rp.node_types = found[0].node_types;
488
489        if found[1] == "<same>":
490            if user_match == "<any>":
[4ed10ae]491                if user != None: rcu = user[0]
[19cc408]492                else: raise service_error(\
493                        service_error.server_config,
494                        "Matched <same> on anonymous request")
495            else:
[4ed10ae]496                rcu = user_match
[19cc408]497        elif found[1] == "<dynamic>":
[4ed10ae]498            rcu = None
499            dyn_create_user = True
[19cc408]500       
[4ed10ae]501        if found[2] == "<same>":
502            if user_match == "<any>":
503                if user != None: rsu = user[0]
504                else: raise service_error(\
505                        service_error.server_config,
506                        "Matched <same> on anonymous request")
507            else:
508                rsu = user_match
509        elif found[2] == "<dynamic>":
510            rsu = None
511            dyn_service_user = True
512
[3f6bc5f]513        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
514                owners
[19cc408]515
516    def build_response(self, alloc_id, ap):
517        """
518        Create the SOAP response.
519
520        Build the dictionary description of the response and use
521        fedd_utils.pack_soap to create the soap message.  NB that alloc_id is
522        a fedd_services_types.IDType_Holder pulled from the incoming message.
523        ap is the allocate project message returned from a remote project
524        allocation (even if that allocation was done locally).
525        """
526        # Because alloc_id is already a fedd_services_types.IDType_Holder,
527        # there's no need to repack it
528        msg = { 
529                'allocID': alloc_id,
530                'emulab': { 
531                    'domain': self.domain,
532                    'boss': self.boss,
533                    'ops': self.ops,
534                    'fileServer': self.fileserver,
535                    'eventServer': self.eventserver,
536                    'project': ap['project']
537                },
538            }
539        if len(self.attrs) > 0:
540            msg['emulab']['fedAttr'] = \
541                [ { 'attribute': x, 'value' : y } \
542                        for x,y in self.attrs.iteritems()]
543        return msg
544
545    def RequestAccess(self, req, fid):
[0ea11af]546        """
547        Handle the access request.  Proxy if not for us.
548
549        Parse out the fields and make the allocations or rejections if for us,
550        otherwise, assuming we're willing to proxy, proxy the request out.
551        """
[19cc408]552
[0ea11af]553        # The dance to get into the request body
[19cc408]554        if req.has_key('RequestAccessRequestBody'):
555            req = req['RequestAccessRequestBody']
556        else:
557            raise service_error(service_error.req, "No request!?")
558
559        if req.has_key('destinationTestbed'):
560            dt = unpack_id(req['destinationTestbed'])
561
562        if dt == None or dt == self.testbed:
563            # Request for this fedd
[3f6bc5f]564            found, dyn, owners = self.lookup_access(req, fid)
[19cc408]565            restricted = None
566            ap = None
567
[5576a47]568            # If this is a request to export a project and the access project
569            # is not the project to export, access denied.
570            if req.has_key('exportProject'):
571                ep = unpack_id(req['exportProject'])
572                if ep != found[0].name:
573                    raise service_error(service_error.access,
574                            "Cannot export %s" % ep)
575
[19cc408]576            # Check for access to restricted nodes
577            if req.has_key('resources') and req['resources'].has_key('node'):
578                resources = req['resources']
579                restricted = [ t for n in resources['node'] \
580                                if n.has_key('hardware') \
581                                    for t in n['hardware'] \
582                                        if t in self.restricted ]
583                inaccessible = [ t for t in restricted \
584                                    if t not in found[0].node_types]
585                if len(inaccessible) > 0:
586                    raise service_error(service_error.access,
587                            "Access denied (nodetypes %s)" % \
588                            str(', ').join(inaccessible))
[40eab39]589            # These collect the keys for the two roles into single sets, one
[4ed10ae]590            # for creation and one for service.  The sets are a simple way to
591            # eliminate duplicates
592            create_ssh = set([ x['sshPubkey'] \
593                    for x in req['createAccess'] \
594                        if x.has_key('sshPubkey')])
595
596            service_ssh = set([ x['sshPubkey'] \
597                    for x in req['serviceAccess'] \
598                        if x.has_key('sshPubkey')])
599
600            if len(create_ssh) > 0 and len(service_ssh) >0: 
[19cc408]601                if dyn[1]: 
602                    # Compose the dynamic project request
603                    # (only dynamic, dynamic currently allowed)
604                    preq = { 'AllocateProjectRequestBody': \
605                                { 'project' : {\
606                                    'user': [ \
[4ed10ae]607                                    { \
608                                        'access': [ { 'sshPubkey': s } \
609                                            for s in service_ssh ], 
610                                         'role': "serviceAccess",\
611                                    }, \
612                                    { \
613                                        'access': [ { 'sshPubkey': s } \
614                                            for s in create_ssh ], 
615                                         'role': "experimentCreation",\
616                                    }, \
617                                    ], \
[19cc408]618                                    }\
619                                }\
620                            }
621                    if restricted != None and len(restricted) > 0:
622                        preq['AllocateProjectRequestBody']['resources'] = \
623                            [ {'node': { 'hardware' :  [ h ] } } \
624                                    for h in restricted ]
625                    ap = self.allocate_project.dynamic_project(preq)
626                else:
[4ed10ae]627                    preq = {'StaticProjectRequestBody' : \
628                            { 'project': \
629                                { 'name' : { 'localname' : found[0].name },\
630                                  'user' : [ \
631                                    {\
632                                        'userID': { 'localname' : found[1] }, \
633                                        'access': [ { 'sshPubkey': s } 
634                                            for s in create_ssh ],
635                                        'role': 'experimentCreation'\
636                                    },\
637                                    {\
638                                        'userID': { 'localname' : found[2] }, \
639                                        'access': [ { 'sshPubkey': s } 
640                                            for s in service_ssh ],
641                                        'role': 'serviceAccess'\
642                                    },\
643                                ]}\
[19cc408]644                            }\
645                    }
[4ed10ae]646                    if restricted != None and len(restricted) > 0:
647                        preq['StaticProjectRequestBody']['resources'] = \
648                            [ {'node': { 'hardware' :  [ h ] } } \
649                                    for h in restricted ]
650                    ap = self.allocate_project.static_project(preq)
[19cc408]651            else:
652                raise service_error(service_error.req, 
653                        "SSH access parameters required")
[d81971a]654            # keep track of what's been added
[f8582c9]655            allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
656            aid = unicode(allocID)
[19cc408]657
[f8582c9]658            self.state_lock.acquire()
[d81971a]659            self.allocation[aid] = { }
660            if dyn[1]:
661                try:
662                    pname = ap['project']['name']['localname']
663                except KeyError:
[f8582c9]664                    self.state_lock.release()
[d81971a]665                    raise service_error(service_error.internal,
666                            "Misformed allocation response?")
667                if self.projects.has_key(pname): self.projects[pname] += 1
668                else: self.projects[pname] = 1
669                self.allocation[aid]['project'] = pname
670
671            self.allocation[aid]['keys'] = [ ]
672
673            try:
674                for u in ap['project']['user']:
675                    uname = u['userID']['localname']
676                    for k in [ k['sshPubkey'] for k in u['access'] \
677                            if k.has_key('sshPubkey') ]:
678                        kv = "%s:%s" % (uname, k)
679                        if self.keys.has_key(kv): self.keys[kv] += 1
680                        else: self.keys[kv] = 1
681                        self.allocation[aid]['keys'].append((uname, k))
682            except KeyError:
[f8582c9]683                self.state_lock.release()
[d81971a]684                raise service_error(service_error.internal,
685                        "Misformed allocation response?")
686
[3f6bc5f]687            self.allocation[aid]['owners'] = owners
[d81971a]688            self.write_state()
[f8582c9]689            self.state_lock.release()
[3f6bc5f]690            for o in owners:
691                self.auth.set_attribute(o, allocID)
[f8582c9]692            resp = self.build_response({ 'fedid': allocID } , ap)
[19cc408]693            return resp
694        else:
[a398ec9]695            if self.allow_proxy:
696                resp = self.proxy_RequestAccess.call_service(dt, req,
697                            self.proxy_cert_file, self.proxy_cert_pwd,
698                            self.proxy_trusted_certs)
699                if resp.has_key('RequestAccessResponseBody'):
700                    return resp['RequestAccessResponseBody']
701                else:
702                    return None
[19cc408]703            else:
[a398ec9]704                raise service_error(service_error.access,
705                        "Access proxying denied")
[d81971a]706
707    def ReleaseAccess(self, req, fid):
708        # The dance to get into the request body
709        if req.has_key('ReleaseAccessRequestBody'):
710            req = req['ReleaseAccessRequestBody']
711        else:
712            raise service_error(service_error.req, "No request!?")
713
[c35207d]714        if req.has_key('destinationTestbed'):
715            dt = unpack_id(req['destinationTestbed'])
[69c015e]716        else:
717            dt = None
[c35207d]718
719        if dt == None or dt == self.testbed:
720            # Local request
721            try:
722                if req['allocID'].has_key('localname'):
723                    auth_attr = aid = req['allocID']['localname']
724                elif req['allocID'].has_key('fedid'):
725                    aid = unicode(req['allocID']['fedid'])
726                    auth_attr = req['allocID']['fedid']
727                else:
728                    raise service_error(service_error.req,
729                            "Only localnames and fedids are understood")
730            except KeyError:
731                raise service_error(service_error.req, "Badly formed request")
732
733            if not self.auth.check_attribute(fid, auth_attr):
734                raise service_error(service_error.access, "Access Denied")
735
736            # If we know this allocation, reduce the reference counts and
737            # remove the local allocations.  Otherwise report an error.  If
738            # there is an allocation to delete, del_users will be a dictonary
739            # of sets where the key is the user that owns the keys in the set.
740            # We use a set to avoid duplicates.  del_project is just the name
741            # of any dynamic project to delete.  We're somewhat lazy about
742            # deleting authorization attributes.  Having access to something
743            # that doesn't exist isn't harmful.
744            del_users = { }
745            del_project = None
746            if self.allocation.has_key(aid):
747                self.state_lock.acquire()
748                for k in self.allocation[aid]['keys']:
749                    kk = "%s:%s" % k
750                    self.keys[kk] -= 1
751                    if self.keys[kk] == 0:
752                        if not del_users.has_key(k[0]):
753                            del_users[k[0]] = set()
754                        del_users[k[0]].add(k[1])
755                        del self.keys[kk]
756
757                if self.allocation[aid].has_key('project'):
758                    pname = self.allocation[aid]['project']
759                    self.projects[pname] -= 1
760                    if self.projects[pname] == 0:
761                        del_project = pname
762                        del self.projects[pname]
763
764                del self.allocation[aid]
765                self.write_state()
766                self.state_lock.release()
767                # If we actually have resources to deallocate, prepare the call.
768                if del_project or del_users:
769                    msg = { 'project': { }}
770                    if del_project:
771                        msg['project']['name']= {'localname': del_project}
772                    users = [ ]
773                    for u in del_users.keys():
774                        users.append({ 'userID': { 'localname': u },\
775                            'access' :  \
776                                    [ {'sshPubkey' : s } for s in del_users[u]]\
777                        })
778                    if users: 
779                        msg['project']['user'] = users
780                    if self.allocate_project.release_project:
781                        msg = { 'ReleaseProjectRequestBody' : msg}
782                        self.allocate_project.release_project(msg)
783                return { 'allocID': req['allocID'] } 
[d81971a]784            else:
[c35207d]785                raise service_error(service_error.req, "No such allocation")
[d81971a]786
[c35207d]787        else:
788            if self.allow_proxy:
789                resp = self.proxy_ReleaseAccess.call_service(dt, req,
790                            self.proxy_cert_file, self.proxy_cert_pwd,
791                            self.proxy_trusted_certs)
792                if resp.has_key('ReleaseAccessResponseBody'):
793                    return resp['ReleaseAccessResponseBody']
794                else:
795                    return None
796            else:
797                raise service_error(service_error.access,
798                        "Access proxying denied")
[d81971a]799
800
Note: See TracBrowser for help on using the repository browser.