Changeset f771e2f for fedd


Ignore:
Timestamp:
May 27, 2010 2:43:38 PM (15 years ago)
Author:
Ted Faber <faber@…>
Branches:
axis_example, compt_changes, info-ops, master, version-3.01, version-3.02
Children:
8cf2b90e
Parents:
a65a65a
Message:

Beginning cleanup of access controllers/plugins for general release. Separating function out to a base class and cleaning up some of the code. Removing all the proxy code, and much of the resources stuff in the access requests.

Location:
fedd/federation
Files:
1 added
1 edited

Legend:

Unmodified
Added
Removed
  • fedd/federation/emulab_access.py

    ra65a65a rf771e2f  
    1414from M2Crypto.SSL import SSLError
    1515
     16from access import access_base
     17
    1618from util import *
    1719from allocate_project import allocate_project_local, allocate_project_remote
     
    3941fl.addHandler(nullHandler())
    4042
    41 class access:
     43class access(access_base):
    4244    """
    4345    The implementation of access control based on mapping users to projects.
     
    4648    dynamically.  This implements both direct requests and proxies.
    4749    """
    48 
    49     class parse_error(RuntimeError): pass
    50 
    5150
    5251    proxy_RequestAccess= service_caller('RequestAccess')
     
    5857        """
    5958
    60         def software_list(v):
    61             l = [ ]
    62             if v:
    63                 ps = v.split(" ")
    64                 while len(ps):
    65                     loc, file = ps[0:2]
    66                     del ps[0:2]
    67                     l.append((loc, file))
    68             return l
    69 
    70 
    71         # Make sure that the configuration is in place
    72         if not config:
    73             raise RunTimeError("No config to fedd.access")
    74 
    75         self.project_priority = config.getboolean("access", "project_priority")
     59        access_base.__init__(self, config, auth)
     60
    7661        self.allow_proxy = config.getboolean("access", "allow_proxy")
    7762
     
    8166        self.fileserver = config.get("access", "fileserver")
    8267        self.eventserver = config.get("access", "eventserver")
    83         self.certdir = config.get("access","certdir")
    8468        self.userconfdir = config.get("access","userconfdir")
    8569        self.userconfcmd = config.get("access","userconfcmd")
     
    9478        self.ssh_pubkey_file = config.get("access","ssh_pubkey_file")
    9579        self.ssh_port = config.get("access","ssh_port") or "22"
    96         self.create_debug = config.getboolean("access", "create_debug")
    97         self.cleanup = not config.getboolean("access", "leave_tmpfiles")
    98         self.access_type = config.get("access", "type")
    9980
    10081        self.dragon_endpoint = config.get("access", "dragon")
     
    10990        self.node_startcommand = config.get("access", "node_startcommand")
    11091
    111         self.federation_software = software_list(self.federation_software)
    112         self.portal_software = software_list(self.portal_software)
    113         self.local_seer_software = software_list(self.local_seer_software)
     92        self.federation_software = self.software_list(self.federation_software)
     93        self.portal_software = self.software_list(self.portal_software)
     94        self.local_seer_software = self.software_list(self.local_seer_software)
    11495
    11596        self.access_type = self.access_type.lower()
     
    124105            self.stop_segment = None
    125106
    126         self.access = { }
    127107        self.restricted = [ ]
    128108        self.projects = { }
     
    136116            'types': self.types
    137117        }
    138         self.log = logging.getLogger("fedd.access")
    139         set_log_level(config, "access", self.log)
    140         self.state_lock = Lock()
    141         # XXX: Configurable
    142         self.exports = set(('SMB', 'seer', 'tmcd', 'userconfig',
    143             'project_export', 'local_seer_control', 'seer_master',
    144             'hide_hosts'))
    145         self.imports = set(('SMB', 'seer', 'userconfig', 'seer_master',
    146             'hide_hosts'))
     118        self.access = { }
     119        if config.has_option("access", "accessdb"):
     120            self.read_access(config.get("access", "accessdb"))
    147121
    148122        if not self.local_seer_image or not self.local_seer_software:
     
    156130            self.exports.discard('seer_master')
    157131
    158         if auth: self.auth = auth
    159         else:
    160             self.log.error(\
    161                     "[access]: No authorizer initialized, creating local one.")
    162             auth = authorizer()
    163 
    164132        tb = config.get('access', 'testbed')
    165133        if tb: self.testbed = [ t.strip() for t in tb.split(',') ]
     
    167135
    168136        if config.has_option("access", "accessdb"):
    169             self.read_access(config.get("access", "accessdb"))
    170 
    171         self.state_filename = config.get("access", "access_state")
    172         self.read_state()
    173 
    174         # Keep cert_file and cert_pwd coming from the same place
    175         self.cert_file = config.get("access", "cert_file")
    176         if self.cert_file:
    177             self.sert_pwd = config.get("access", "cert_pw")
    178         else:
    179             self.cert_file = config.get("globals", "cert_file")
    180             self.sert_pwd = config.get("globals", "cert_pw")
    181 
    182         self.trusted_certs = config.get("access", "trusted_certs") or \
    183                 config.get("globals", "trusted_certs")
     137            self.read_access(config.get("access", "accessdb"),
     138                    self.make_access_project)
     139
     140        # read state in the base_class
     141        self.state_lock.acquire()
     142        self.allocation = self.state['allocation']
     143        self.projects = self.state['projects']
     144        self.keys = self.state['keys']
     145        self.types = self.state['types']
     146        # Add the ownership attributes to the authorizer.  Note that the
     147        # indices of the allocation dict are strings, but the attributes are
     148        # fedids, so there is a conversion.
     149        for k in self.allocation.keys():
     150            for o in self.allocation[k].get('owners', []):
     151                self.auth.set_attribute(o, fedid(hexstr=k))
     152            if self.allocation[k].has_key('userconfig'):
     153                sfid = self.allocation[k]['userconfig']
     154                fid = fedid(hexstr=sfid)
     155                self.auth.set_attribute(fid, "/%s" % sfid)
     156        self.state_lock.release()
     157
    184158
    185159        self.soap_services = {\
     
    217191
    218192    @staticmethod
    219     def add_kit(e, kit):
    220         """
    221         Add a Software object created from the list of (install, location)
    222         tuples passed as kit  to the software attribute of an object e.  We
    223         do this enough to break out the code, but it's kind of a hack to
    224         avoid changing the old tuple rep.
    225         """
    226 
    227         s = [ topdl.Software(install=i, location=l) for i, l in kit]
    228 
    229         if isinstance(e.software, list): e.software.extend(s)
    230         else: e.software = s
    231 
    232 
    233     def read_access(self, config):
    234         """
    235         Read a configuration file and set internal parameters.
    236 
    237         The format is more complex than one might hope.  The basic format is
    238         attribute value pairs separated by colons(:) on a signle line.  The
    239         attributes in bool_attrs, emulab_attrs and id_attrs can all be set
    240         directly using the name: value syntax.  E.g.
    241         boss: hostname
    242         sets self.boss to hostname.  In addition, there are access lines of the
    243         form (tb, proj, user) -> (aproj, auser) that map the first tuple of
    244         names to the second for access purposes.  Names in the key (left side)
    245         can include "<NONE> or <ANY>" to act as wildcards or to require the
    246         fields to be empty.  Similarly aproj or auser can be <SAME> or
    247         <DYNAMIC> indicating that either the matching key is to be used or a
    248         dynamic user or project will be created.  These names can also be
    249         federated IDs (fedid's) if prefixed with fedid:.  Finally, the aproj
    250         can be followed with a colon-separated list of node types to which that
    251         project has access (or will have access if dynamic).
    252         Testbed attributes outside the forms above can be given using the
    253         format attribute: name value: value.  The name is a single word and the
    254         value continues to the end of the line.  Empty lines and lines startin
    255         with a # are ignored.
    256 
    257         Parsing errors result in a self.parse_error exception being raised.
    258         """
    259         lineno=0
    260         name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+"
    261         fedid_expr = "fedid:[" + string.hexdigits + "]+"
    262         key_name = "(<ANY>|<NONE>|"+fedid_expr + "|"+ name_expr + ")"
    263         access_proj = "(<DYNAMIC>(?::" + name_expr +")*|"+ \
    264                 "<SAME>" + "(?::" + name_expr + ")*|" + \
    265                 fedid_expr + "(?::" + name_expr + ")*|" + \
    266                 name_expr + "(?::" + name_expr + ")*)"
    267         access_name = "(<DYNAMIC>|<SAME>|" + fedid_expr + "|"+ name_expr + ")"
    268 
    269         restricted_re = re.compile("restricted:\s*(.*)", re.IGNORECASE)
    270         access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+
    271                 key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' +
    272                 access_name + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE)
    273 
     193    def make_access_project(str):
    274194        def parse_name(n):
    275195            if n.startswith('fedid:'): return fedid(hexstr=n[len('fedid:'):])
    276196            else: return n
    277        
    278         def auth_name(n):
    279             if isinstance(n, basestring):
    280                 if n =='<any>' or n =='<none>': return None
    281                 else: return unicode(n)
    282             else:
    283                 return n
    284 
    285         f = open(config, "r");
    286         for line in f:
    287             lineno += 1
    288             line = line.strip();
    289             if len(line) == 0 or line.startswith('#'):
    290                 continue
    291 
    292             # Restricted entry
    293             m = restricted_re.match(line)
    294             if m != None:
    295                 val = m.group(1)
    296                 self.restricted.append(val)
    297                 continue
    298 
    299             # Access line (t, p, u) -> (ap, cu, su) line
    300             m = access_re.match(line)
    301             if m != None:
    302                 access_key = tuple([ parse_name(x) for x in m.group(1,2,3)])
    303                 auth_key = tuple([ auth_name(x) for x in access_key])
    304                 aps = m.group(4).split(":");
    305                 if aps[0] == 'fedid:':
    306                     del aps[0]
    307                     aps[0] = fedid(hexstr=aps[0])
    308 
    309                 cu = parse_name(m.group(5))
    310                 su = parse_name(m.group(6))
    311 
    312                 access_val = (access_project(aps[0], aps[1:]),
    313                         parse_name(m.group(5)), parse_name(m.group(6)))
    314 
    315                 self.access[access_key] = access_val
    316                 self.auth.set_attribute(auth_key, "access")
    317                 continue
    318 
    319             # Nothing matched to here: unknown line - raise exception
    320             f.close()
    321             raise self.parse_error("Unknown statement at line %d of %s" % \
    322                     (lineno, config))
    323         f.close()
    324 
    325     def get_users(self, obj):
    326         """
    327         Return a list of the IDs of the users in dict
    328         """
    329         if obj.has_key('user'):
    330             return [ unpack_id(u['userID']) \
    331                     for u in obj['user'] if u.has_key('userID') ]
    332         else:
    333             return None
    334 
    335     def write_state(self):
    336         if self.state_filename:
    337             try:
    338                 f = open(self.state_filename, 'w')
    339                 pickle.dump(self.state, f)
    340             except EnvironmentError, e:
    341                 self.log.error("Can't write file %s: %s" % \
    342                         (self.state_filename, e))
    343             except pickle.PicklingError, e:
    344                 self.log.error("Pickling problem: %s" % e)
    345             except TypeError, e:
    346                 self.log.error("Pickling problem (TypeError): %s" % e)
    347 
    348 
    349     def read_state(self):
    350         """
    351         Read a new copy of access state.  Old state is overwritten.
    352 
    353         State format is a simple pickling of the state dictionary.
    354         """
    355         if self.state_filename:
    356             try:
    357                 f = open(self.state_filename, "r")
    358                 self.state = pickle.load(f)
    359 
    360                 self.allocation = self.state['allocation']
    361                 self.projects = self.state['projects']
    362                 self.keys = self.state['keys']
    363                 self.types = self.state['types']
    364 
    365                 self.log.debug("[read_state]: Read state from %s" % \
    366                         self.state_filename)
    367             except EnvironmentError, e:
    368                 self.log.warning(("[read_state]: No saved state: " +\
    369                         "Can't open %s: %s") % (self.state_filename, e))
    370             except EOFError, e:
    371                 self.log.warning(("[read_state]: " +\
    372                         "Empty or damaged state file: %s:") % \
    373                         self.state_filename)
    374             except pickle.UnpicklingError, e:
    375                 self.log.warning(("[read_state]: No saved state: " + \
    376                         "Unpickling failed: %s") % e)
    377 
    378             # Add the ownership attributes to the authorizer.  Note that the
    379             # indices of the allocation dict are strings, but the attributes are
    380             # fedids, so there is a conversion.
    381             for k in self.allocation.keys():
    382                 for o in self.allocation[k].get('owners', []):
    383                     self.auth.set_attribute(o, fedid(hexstr=k))
    384                 if self.allocation[k].has_key('userconfig'):
    385                     sfid = self.allocation[k]['userconfig']
    386                     fid = fedid(hexstr=sfid)
    387                     self.auth.set_attribute(fid, "/%s" % sfid)
    388 
    389 
    390     def permute_wildcards(self, a, p):
    391         """Return a copy of a with various fields wildcarded.
    392 
    393         The bits of p control the wildcards.  A set bit is a wildcard
    394         replacement with the lowest bit being user then project then testbed.
    395         """
    396         if p & 1: user = ["<any>"]
    397         else: user = a[2]
    398         if p & 2: proj = "<any>"
    399         else: proj = a[1]
    400         if p & 4: tb = "<any>"
    401         else: tb = a[0]
    402 
    403         return (tb, proj, user)
    404 
    405     def find_access(self, search):
    406         """
    407         Search the access DB for a match on this tuple.  Return the matching
    408         access tuple and the user that matched.
    409        
    410         NB, if the initial tuple fails to match we start inserting wildcards in
    411         an order determined by self.project_priority.  Try the list of users in
    412         order (when wildcarded, there's only one user in the list).
    413         """
    414         if self.project_priority: perm = (0, 1, 2, 3, 4, 5, 6, 7)
    415         else: perm = (0, 2, 1, 3, 4, 6, 5, 7)
    416 
    417         for p in perm:
    418             s = self.permute_wildcards(search, p)
    419             # s[2] is None on an anonymous, unwildcarded request
    420             if s[2] != None:
    421                 for u in s[2]:
    422                     if self.access.has_key((s[0], s[1], u)):
    423                         return (self.access[(s[0], s[1], u)], u)
    424             else:
    425                 if self.access.has_key(s):
    426                     return (self.access[s], None)
    427         return None, None
     197
     198        str = str.strip()
     199        if str.startswith('(') and str.endswith(')'):
     200            str = str[1:-1]
     201            names = [ s.strip() for s in str.split(",")]
     202            if len(names) > 3:
     203                raise self.parse_error("More than three fields in name")
     204            first = names[0].split(":")
     205            if first == 'fedid:':
     206                del first[0]
     207                first[0] = fedid(hexstr=first[0])
     208            names[0] = access_project(first[0], first[1:])
     209
     210            for i in range(1,2):
     211                names[i] = parse_name(names[i])
     212
     213            return tuple(names)
     214        else:
     215            raise self.parse_error('Bad mapping (unbalanced parens)')
     216
    428217
    429218    def lookup_access(self, req, fid):
     
    434223        The fedid is needed to construct the request
    435224        """
    436         user_re = re.compile("user:\s(.*)")
    437         project_re = re.compile("project:\s(.*)")
    438 
    439         # Search keys
    440         tb = None
    441         project = None
    442         user = None
    443225        # Return values
    444226        rp = access_project(None, ())
    445227        ru = None
    446 
    447         user = [ user_re.findall(x)[0] for x in req.get('credential', []) \
    448                 if user_re.match(x)]
    449         project = [ project_re.findall(x)[0] \
    450                 for x in req.get('credential', []) \
    451                     if project_re.match(x)]
    452 
    453         if len(project) == 1: project = project[0]
    454         elif len(project) == 0: project = None
    455         else:
    456             raise service_error(service_error.req,
    457                     "More than one project credential")
    458 
    459 
    460         user_fedids = [ u for u in user if isinstance(u, fedid)]
    461         # Determine how the caller is representing itself.  If its fedid shows
    462         # up as a project or a singleton user, let that stand.  If neither the
    463         # usernames nor the project name is a fedid, the caller is a testbed.
    464         if project and isinstance(project, fedid):
    465             if project == fid:
    466                 # The caller is the project (which is already in the tuple
    467                 # passed in to the authorizer)
    468                 owners = user_fedids
    469                 owners.append(project)
    470             else:
    471                 raise service_error(service_error.req,
    472                         "Project asserting different fedid")
    473         else:
    474             if fid not in user_fedids:
    475                 tb = fid
    476                 owners = user_fedids
    477                 owners.append(fid)
    478             else:
    479                 if len(fedids) > 1:
    480                     raise service_error(service_error.req,
    481                             "User asserting different fedid")
    482                 else:
    483                     # Which is a singleton
    484                     owners = user_fedids
    485         # Confirm authorization
    486 
    487         for u in user:
    488             self.log.debug("[lookup_access] Checking access for %s" % \
    489                     ((tb, project, u),))
    490             if self.auth.check_attribute((tb, project, u), 'access'):
    491                 self.log.debug("[lookup_access] Access granted")
    492                 break
    493             else:
    494                 self.log.debug("[lookup_access] Access Denied")
    495         else:
    496             raise service_error(service_error.access, "Access denied")
    497 
    498228        # This maps a valid user to the Emulab projects and users to use
    499         found, user_match = self.find_access((tb, project, user))
     229        found, match = self.lookup_access_base(req, fid)
     230        tb, project, user = match
    500231       
    501232        if found == None:
     
    551282
    552283        return (rp, rcu, rsu), (dyn_create_user, dyn_service_user, dyn_proj),\
    553                 owners
    554 
    555     def get_handler(self, path, fid):
    556         self.log.info("Get handler %s %s" % (path, fid))
    557         if self.auth.check_attribute(fid, path) and self.userconfdir:
    558             return ("%s/%s" % (self.userconfdir, path), "application/binary")
     284                [ fid ]
     285
     286    def do_project_allocation(self, dyn, project, user):
     287        """
     288        Call the project allocation routines and return the info.
     289        """
     290        if dyn:
     291            # Compose the dynamic project request
     292            # (only dynamic, dynamic currently allowed)
     293            preq = { 'AllocateProjectRequestBody': \
     294                        { 'project' : {\
     295                            'user': [ \
     296                            { \
     297                                'access': [ {
     298                                    'sshPubkey': self.ssh_pubkey_file } ],
     299                                 'role': "serviceAccess",\
     300                            }, \
     301                            { \
     302                                'access': [ {
     303                                    'sshPubkey': self.ssh_pubkey_file } ],
     304                                 'role': "experimentCreation",\
     305                            }, \
     306                            ], \
     307                            }\
     308                        }\
     309                    }
     310            return self.allocate_project.dynamic_project(preq)
    559311        else:
    560             return (None, None)
    561 
    562     def export_userconf(self, project):
    563         dev_null = None
    564         confid, confcert = generate_fedid("test", dir=self.userconfdir,
    565                 log=self.log)
    566         conffilename = "%s/%s" % (self.userconfdir, str(confid))
    567         cf = None
     312            preq = {'StaticProjectRequestBody' : \
     313                    { 'project': \
     314                        { 'name' : { 'localname' : project },\
     315                          'user' : [ \
     316                            {\
     317                                'userID': { 'localname' : user }, \
     318                                'access': [ {
     319                                    'sshPubkey': self.ssh_pubkey_file } ],
     320                                'role': 'experimentCreation'\
     321                            },\
     322                            {\
     323                                'userID': { 'localname' : user}, \
     324                                'access': [ {
     325                                    'sshPubkey': self.ssh_pubkey_file } ],
     326                                'role': 'serviceAccess'\
     327                            },\
     328                        ]}\
     329                    }\
     330            }
     331            return self.allocate_project.static_project(preq)
     332
     333    def save_project_state(self, aid, ap, dyn, owners):
     334        """
     335        Parse out and save the information relevant to the project created for
     336        this experiment.  That info is largely in ap and owners.  dyn indicates
     337        that the project was created dynamically.  Return the user and project
     338        names.
     339        """
     340        self.state_lock.acquire()
     341        self.allocation[aid] = { }
    568342        try:
    569             cf = open(conffilename, "w")
    570             os.chmod(conffilename, stat.S_IRUSR | stat.S_IWUSR)
    571         except EnvironmentError, e:
    572             raise service_error(service_error.internal,
    573                     "Cannot create user configuration data")
     343            pname = ap['project']['name']['localname']
     344        except KeyError:
     345            pname = None
     346
     347        if dyn:
     348            if not pname:
     349                self.state_lock.release()
     350                raise service_error(service_error.internal,
     351                        "Misformed allocation response?")
     352            if pname in self.projects: self.projects[pname] += 1
     353            else: self.projects[pname] = 1
     354            self.allocation[aid]['project'] = pname
     355        else:
     356            # sproject is a static project associated with this allocation.
     357            self.allocation[aid]['sproject'] = pname
     358
     359        self.allocation[aid]['keys'] = [ ]
    574360
    575361        try:
    576             dev_null = open("/dev/null", "a")
    577         except EnvironmentError, e:
    578             self.log.error("export_userconf: can't open /dev/null: %s" % e)
    579 
    580         cmd = "%s %s" % (self.userconfcmd, project)
    581         conf = subprocess.call(cmd.split(" "),
    582                 stdout=cf, stderr=dev_null, close_fds=True)
    583 
    584         self.auth.set_attribute(confid, "/%s" % str(confid))
    585 
    586         return confid, confcert
    587 
    588     def export_SMB(self, id, state, project, user, attrs):
    589         return {
    590                 'id': id,
    591                 'name': 'SMB',
    592                 'visibility': 'export',
    593                 'server': 'http://fs:139',
    594                 'fedAttr': [
    595                         { 'attribute': 'SMBSHARE', 'value': 'USERS' },
    596                         { 'attribute': 'SMBUSER', 'value': user },
    597                         { 'attribute': 'SMBPROJ', 'value': project },
    598                     ]
    599                 }
    600 
    601     def export_seer(self, id, state, project, user, attrs):
    602         return {
    603                 'id': id,
    604                 'name': 'seer',
    605                 'visibility': 'export',
    606                 'server': 'http://control:16606',
    607                 }
    608 
    609     def export_local_seer(self, id, state, project, user, attrs):
    610         return {
    611                 'id': id,
    612                 'name': 'local_seer_control',
    613                 'visibility': 'export',
    614                 'server': 'http://control:16606',
    615                 }
    616 
    617     def export_seer_master(self, id, state, project, user, attrs):
    618         return {
    619                 'id': id,
    620                 'name': 'seer_master',
    621                 'visibility': 'export',
    622                 'server': 'http://seer-master:17707',
    623                 }
    624 
    625     def export_tmcd(self, id, state, project, user, attrs):
    626         return {
    627                 'id': id,
    628                 'name': 'seer',
    629                 'visibility': 'export',
    630                 'server': 'http://boss:7777',
    631                 }
    632 
    633     def export_userconfig(self, id, state, project, user, attrs):
    634         if self.userconfdir and self.userconfcmd \
    635                 and self.userconfurl:
    636             cid, cert = self.export_userconf(project)
    637             state['userconfig'] = unicode(cid)
    638             return {
    639                     'id': id,
    640                     'name': 'userconfig',
    641                     'visibility': 'export',
    642                     'server': "%s/%s" % (self.userconfurl, str(cid)),
    643                     'fedAttr': [
    644                         { 'attribute': 'cert', 'value': cert },
    645                     ]
    646                     }
    647         else:
    648             return None
    649 
    650     def export_hide_hosts(self, id, state, project, user, attrs):
    651         return {
    652                 'id': id,
    653                 'name': 'hide_hosts',
    654                 'visibility': 'export',
    655                 'fedAttr': [ x for x in attrs \
    656                         if x.get('attribute', "") == 'hosts'],
    657                 }
    658 
    659     def export_services(self, sreq, project, user):
    660         exp = [ ]
    661         state = { }
    662         # XXX: Filthy shortcut here using http: so urlparse will give the right
    663         # answers.
    664         for s in sreq:
    665             sname = s.get('name', '')
    666             svis = s.get('visibility', '')
    667             sattrs = s.get('fedAttr', [])
    668             if svis == 'export':
    669                 if sname in self.exports:
    670                     id = s.get('id', 'no_id')
    671                     if sname == 'SMB':
    672                         exp.append(self.export_SMB(id, state, project, user,
    673                             sattrs))
    674                     elif sname == 'seer':
    675                         exp.append(self.export_seer(id, state, project, user,
    676                             sattrs))
    677                     elif sname == 'tmcd':
    678                         exp.append(self.export_tmcd(id, state, project, user,
    679                             sattrs))
    680                     elif sname == 'userconfig':
    681                         exp.append(self.export_userconfig(id, state,
    682                             project, user, sattrs))
    683                     elif sname == 'project_export':
    684                         exp.append(self.export_SMB(id, state, project, user,
    685                             sattrs))
    686                         #exp.append(self.export_seer(id, state, project, user,
    687                         #sattrs))
    688                         exp.append(self.export_userconfig(id, state,
    689                             project, user, sattrs))
    690                     elif sname == 'local_seer_control':
    691                         exp.append(self.export_local_seer(id, state, project,
    692                             user, sattrs))
    693                     elif sname == 'seer_master':
    694                         exp.append(self.export_seer_master(id, state, project,
    695                             user, sattrs))
    696                     elif sname == 'hide_hosts':
    697                         exp.append(self.export_hide_hosts(id, state, project,
    698                             user, sattrs))
    699         return (exp, state)
    700 
    701     def build_response(self, alloc_id, ap, services):
    702         """
    703         Create the SOAP response.
    704 
    705         Build the dictionary description of the response and use
    706         fedd_utils.pack_soap to create the soap message.  ap is the allocate
    707         project message returned from a remote project allocation (even if that
    708         allocation was done locally).
    709         """
    710         # Because alloc_id is already a fedd_services_types.IDType_Holder,
    711         # there's no need to repack it
    712         msg = {
    713                 'allocID': alloc_id,
    714                 'fedAttr': [
    715                     { 'attribute': 'domain', 'value': self.domain } ,
    716                     { 'attribute': 'project', 'value':
    717                         ap['project'].get('name', {}).get('localname', "???") },
    718                 ]
    719             }
    720 
    721         if self.dragon_endpoint:
    722             msg['fedAttr'].append({'attribute': 'dragon',
    723                 'value': self.dragon_endpoint})
    724         if self.deter_internal:
    725             print 'adding internal'
    726             msg['fedAttr'].append({'attribute': 'deter_internal',
    727                 'value': self.deter_internal})
    728         else: print "internal: %s" % self.deter_internal
    729         #XXX: ??
    730         if self.dragon_vlans:
    731             msg['fedAttr'].append({'attribute': 'vlans',
    732                 'value': self.dragon_vlans})
    733 
    734         if services:
    735             msg['service'] = services
    736         return msg
     362            for u in ap['project']['user']:
     363                uname = u['userID']['localname']
     364                if u['role'] == 'experimentCreation':
     365                    self.allocation[aid]['user'] = uname
     366                for k in [ k['sshPubkey'] for k in u['access'] \
     367                        if k.has_key('sshPubkey') ]:
     368                    kv = "%s:%s" % (uname, k)
     369                    if self.keys.has_key(kv): self.keys[kv] += 1
     370                    else: self.keys[kv] = 1
     371                    self.allocation[aid]['keys'].append((uname, k))
     372        except KeyError:
     373            self.state_lock.release()
     374            raise service_error(service_error.internal,
     375                    "Misformed allocation response?")
     376
     377        self.allocation[aid]['owners'] = owners
     378        self.write_state()
     379        self.state_lock.release()
     380        return (pname, uname)
    737381
    738382    def RequestAccess(self, req, fid):
     
    774418            raise service_error(service_error.req, "No request!?")
    775419
    776         if req.has_key('destinationTestbed'):
    777             dt = unpack_id(req['destinationTestbed'])
    778 
    779         if dt == None or dt in self.testbed:
    780             # Request for this fedd
    781             found, dyn, owners = self.lookup_access(req, fid)
    782             restricted = None
    783             ap = None
    784 
    785             # if this includes a project export request and the exported
    786             # project is not the access project, access denied.
    787             if 'service' in req:
    788                 ep = get_export_project(req['service'])
    789                 if ep and ep != found[0].name:
    790                     raise service_error(service_error.access,
    791                             "Cannot export %s" % ep)
    792 
    793             # XXX
    794             # Check for access to restricted nodes
    795             if req.has_key('resources') and req['resources'].has_key('node'):
    796                 resources = req['resources']
    797                 restricted = [ gateway_hardware(t) for n in resources['node'] \
    798                                 if n.has_key('hardware') \
    799                                     for t in n['hardware'] \
    800                                         if gateway_hardware(t) \
    801                                             in self.restricted ]
    802                 inaccessible = [ t for t in restricted \
    803                                     if t not in found[0].node_types]
    804                 if len(inaccessible) > 0:
    805                     raise service_error(service_error.access,
    806                             "Access denied (nodetypes %s)" % \
    807                             str(', ').join(inaccessible))
    808             # XXX
    809 
    810             # These were passed around before, but now are hidden from users
    811             # and configurators alike, beyond a configuration file entry.
    812             create_ssh = [ self.ssh_pubkey_file ]
    813             service_ssh = [ self.ssh_pubkey_file ]
    814 
    815             if len(create_ssh) > 0 and len(service_ssh) >0:
    816                 if dyn[1]:
    817                     # Compose the dynamic project request
    818                     # (only dynamic, dynamic currently allowed)
    819                     preq = { 'AllocateProjectRequestBody': \
    820                                 { 'project' : {\
    821                                     'user': [ \
    822                                     { \
    823                                         'access': [ { 'sshPubkey': s } \
    824                                             for s in service_ssh ],
    825                                          'role': "serviceAccess",\
    826                                     }, \
    827                                     { \
    828                                         'access': [ { 'sshPubkey': s } \
    829                                             for s in create_ssh ],
    830                                          'role': "experimentCreation",\
    831                                     }, \
    832                                     ], \
    833                                     }\
    834                                 }\
    835                             }
    836                     if restricted != None and len(restricted) > 0:
    837                         preq['AllocateProjectRequestBody']['resources'] = \
    838                              {'node': [ { 'hardware' :  [ h ] } \
    839                                     for h in restricted ] }
    840                     ap = self.allocate_project.dynamic_project(preq)
    841                 else:
    842                     preq = {'StaticProjectRequestBody' : \
    843                             { 'project': \
    844                                 { 'name' : { 'localname' : found[0].name },\
    845                                   'user' : [ \
    846                                     {\
    847                                         'userID': { 'localname' : found[1] }, \
    848                                         'access': [ { 'sshPubkey': s }
    849                                             for s in create_ssh ],
    850                                         'role': 'experimentCreation'\
    851                                     },\
    852                                     {\
    853                                         'userID': { 'localname' : found[2] }, \
    854                                         'access': [ { 'sshPubkey': s }
    855                                             for s in service_ssh ],
    856                                         'role': 'serviceAccess'\
    857                                     },\
    858                                 ]}\
    859                             }\
    860                     }
    861                     if restricted != None and len(restricted) > 0:
    862                         preq['StaticProjectRequestBody']['resources'] = \
    863                             {'node': [ { 'hardware' :  [ h ] } \
    864                                     for h in restricted ] }
    865                     ap = self.allocate_project.static_project(preq)
    866             else:
    867                 raise service_error(service_error.req,
    868                         "SSH access parameters required")
    869             # keep track of what's been added
    870             allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
    871             aid = unicode(allocID)
    872 
    873             self.state_lock.acquire()
    874             self.allocation[aid] = { }
    875             try:
    876                 pname = ap['project']['name']['localname']
    877             except KeyError:
    878                 pname = None
    879 
    880             if dyn[1]:
    881                 if not pname:
    882                     self.state_lock.release()
    883                     raise service_error(service_error.internal,
    884                             "Misformed allocation response?")
    885                 if self.projects.has_key(pname): self.projects[pname] += 1
    886                 else: self.projects[pname] = 1
    887                 self.allocation[aid]['project'] = pname
    888             else:
    889                 # sproject is a static project associated with this allocation.
    890                 self.allocation[aid]['sproject'] = pname
    891 
    892             if ap.has_key('resources'):
    893                 if not pname:
    894                     self.state_lock.release()
    895                     raise service_error(service_error.internal,
    896                             "Misformed allocation response?")
    897                 self.allocation[aid]['types'] = set()
    898                 nodes = ap['resources'].get('node', [])
    899                 for n in nodes:
    900                     for h in n.get('hardware', []):
    901                         if self.types.has_key((pname, h)):
    902                             self.types[(pname, h)] += 1
    903                         else:
    904                             self.types[(pname, h)] = 1
    905                         self.allocation[aid]['types'].add((pname,h))
    906 
    907 
    908             self.allocation[aid]['keys'] = [ ]
    909 
    910             try:
    911                 for u in ap['project']['user']:
    912                     uname = u['userID']['localname']
    913                     if u['role'] == 'experimentCreation':
    914                         self.allocation[aid]['user'] = uname
    915                     for k in [ k['sshPubkey'] for k in u['access'] \
    916                             if k.has_key('sshPubkey') ]:
    917                         kv = "%s:%s" % (uname, k)
    918                         if self.keys.has_key(kv): self.keys[kv] += 1
    919                         else: self.keys[kv] = 1
    920                         self.allocation[aid]['keys'].append((uname, k))
    921             except KeyError:
    922                 self.state_lock.release()
    923                 raise service_error(service_error.internal,
    924                         "Misformed allocation response?")
    925 
    926             self.allocation[aid]['owners'] = owners
    927             services, svc_state = self.export_services(req.get('service',[]),
    928                     pname, uname)
    929             # Store services state in global state
    930             for k, v in svc_state.items():
    931                 self.allocation[aid][k] = v
    932             self.write_state()
    933             self.state_lock.release()
    934             for o in owners:
    935                 self.auth.set_attribute(o, allocID)
    936             try:
    937                 f = open("%s/%s.pem" % (self.certdir, aid), "w")
    938                 print >>f, alloc_cert
    939                 f.close()
    940             except EnvironmentError, e:
    941                 raise service_error(service_error.internal,
    942                         "Can't open %s/%s : %s" % (self.certdir, aid, e))
    943             resp = self.build_response({ 'fedid': allocID } , ap, services)
    944             return resp
    945         else:
    946             if self.allow_proxy:
    947                 resp = self.proxy_RequestAccess.call_service(dt, req,
    948                             self.cert_file, self.cert_pwd,
    949                             self.trusted_certs)
    950                 if resp.has_key('RequestAccessResponseBody'):
    951                     return resp['RequestAccessResponseBody']
    952                 else:
    953                     return None
    954             else:
    955                 raise service_error(service_error.access,
    956                         "Access proxying denied")
     420
     421        found, dyn, owners = self.lookup_access(req, fid)
     422        ap = None
     423
     424        # if this includes a project export request and the exported
     425        # project is not the access project, access denied.
     426        if 'service' in req:
     427            ep = get_export_project(req['service'])
     428            if ep and ep != found[0].name:
     429                raise service_error(service_error.access,
     430                        "Cannot export %s" % ep)
     431
     432        if self.ssh_pubkey_file:
     433            ap = self.do_project_allocation(dyn[1], found[0].name, found[1])
     434        else:
     435            raise service_error(service_error.internal,
     436                    "SSH access parameters required")
     437        # keep track of what's been added
     438        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
     439        aid = unicode(allocID)
     440
     441        pname, uname = self.save_project_state(aid, ap, dyn[1], owners)
     442
     443        services, svc_state = self.export_services(req.get('service',[]),
     444                pname, uname)
     445        self.state_lock.acquire()
     446        # Store services state in global state
     447        for k, v in svc_state.items():
     448            self.allocation[aid][k] = v
     449        self.write_state()
     450        self.state_lock.release()
     451        # Give the owners the right to change this allocation
     452        for o in owners:
     453            self.auth.set_attribute(o, allocID)
     454        try:
     455            f = open("%s/%s.pem" % (self.certdir, aid), "w")
     456            print >>f, alloc_cert
     457            f.close()
     458        except EnvironmentError, e:
     459            raise service_error(service_error.internal,
     460                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
     461        resp = self.build_access_response({ 'fedid': allocID } ,
     462                ap, services)
     463        return resp
    957464
    958465    def ReleaseAccess(self, req, fid):
     
    1070577                        "Access proxying denied")
    1071578
    1072     def generate_portal_configs(self, topo, pubkey_base, secretkey_base,
    1073             tmpdir, master, lproj, leid, connInfo, services):
    1074 
    1075         def conninfo_to_dict(key, info):
    1076             """
    1077             Make a cpoy of the connection information about key, and flatten it
    1078             into a single dict by parsing out any feddAttrs.
    1079             """
    1080 
    1081             rv = None
    1082             for i in info:
    1083                 if key == i.get('portal', "") or \
    1084                         key in [e.get('element', "") \
    1085                         for e in i.get('member', [])]:
    1086                     rv = i.copy()
    1087                     break
    1088 
    1089             else:
    1090                 return rv
    1091 
    1092             if 'fedAttr' in rv:
    1093                 for a in rv['fedAttr']:
    1094                     attr = a.get('attribute', "")
    1095                     val = a.get('value', "")
    1096                     if attr and attr not in rv:
    1097                         rv[attr] = val
    1098                 del rv['fedAttr']
    1099             return rv
    1100 
    1101         # XXX: un hardcode this
    1102         def client_null(f, s):
    1103             print >>f, "Service: %s" % s['name']
    1104 
    1105         def client_seer_master(f, s):
    1106             print >>f, 'PortalAlias: seer-master';
    1107 
    1108         def client_smb(f, s):
    1109             print >>f, "Service: %s" % s['name']
    1110             smbshare = None
    1111             smbuser = None
    1112             smbproj = None
    1113             for a in s.get('fedAttr', []):
    1114                 if a.get('attribute', '') == 'SMBSHARE':
    1115                     smbshare = a.get('value', None)
    1116                 elif a.get('attribute', '') == 'SMBUSER':
    1117                     smbuser = a.get('value', None)
    1118                 elif a.get('attribute', '') == 'SMBPROJ':
    1119                     smbproj = a.get('value', None)
    1120 
    1121             if all((smbshare, smbuser, smbproj)):
    1122                 print >>f, "SMBshare: %s" % smbshare
    1123                 print >>f, "ProjectUser: %s" % smbuser
    1124                 print >>f, "ProjectName: %s" % smbproj
    1125 
    1126         def client_hide_hosts(f, s):
    1127             for a in s.get('fedAttr', [ ]):
    1128                 if a.get('attribute', "") == 'hosts':
    1129                     print >>f, "Hide: %s" % a.get('value', "")
    1130 
    1131         client_service_out = {
    1132                 'SMB': client_smb,
    1133                 'tmcd': client_null,
    1134                 'seer': client_null,
    1135                 'userconfig': client_null,
    1136                 'project_export': client_null,
    1137                 'seer_master': client_seer_master,
    1138                 'hide_hosts': client_hide_hosts,
    1139             }
    1140 
    1141         def client_seer_master_export(f, s):
    1142             print >>f, "AddedNode: seer-master"
    1143 
    1144         def client_seer_local_export(f, s):
    1145             print >>f, "AddedNode: control"
    1146 
    1147         client_export_service_out = {
    1148                 'seer_master': client_seer_master_export,
    1149                 'local_seer_control': client_seer_local_export,
    1150             }
    1151 
    1152         def server_port(f, s):
    1153             p = urlparse(s.get('server', 'http://localhost'))
    1154             print >>f, 'port: remote:%s:%s:%s' % (p.port, p.hostname, p.port)
    1155 
    1156         def server_null(f,s): pass
    1157 
    1158         def server_seer(f, s):
    1159             print >>f, 'seer: True'
    1160 
    1161         server_service_out = {
    1162                 'SMB': server_port,
    1163                 'tmcd': server_port,
    1164                 'userconfig': server_null,
    1165                 'project_export': server_null,
    1166                 'seer': server_seer,
    1167                 'seer_master': server_port,
    1168                 'hide_hosts': server_null,
    1169             }
    1170         # XXX: end un hardcode this
    1171 
    1172 
    1173         seer_out = False
    1174         client_out = False
    1175         mproj = None
    1176         mexp = None
    1177         control_gw = None
    1178         testbed = ""
    1179         # Create configuration files for the portals
    1180         for e in [ e for e in topo.elements \
    1181                 if isinstance(e, topdl.Computer) and e.get_attribute('portal')]:
    1182             myname = e.name
    1183             type = e.get_attribute('portal_type')
    1184 
    1185             info = conninfo_to_dict(myname, connInfo)
    1186 
    1187             if not info:
    1188                 raise service_error(service_error.req,
    1189                         "No connectivity info for %s" % myname)
    1190 
    1191             peer = info.get('peer', "")
    1192             ldomain = self.domain;
    1193             ssh_port = info.get('ssh_port', 22)
    1194 
    1195             # Collect this for the client.conf file
    1196             if 'masterexperiment' in info:
    1197                 mproj, meid = info['masterexperiment'].split("/", 1)
    1198 
    1199             if type in ('control', 'both'):
    1200                 testbed = e.get_attribute('testbed')
    1201                 control_gw = myname
    1202 
    1203             active = info.get('active', 'False')
    1204 
    1205             cfn = "%s/%s.gw.conf" % (tmpdir, myname.lower())
    1206             tunnelconfig = self.tunnel_config
    1207             try:
    1208                 f = open(cfn, "w")
    1209                 if active == 'True':
    1210                     print >>f, "active: True"
    1211                     print >>f, "ssh_port: %s" % ssh_port
    1212                     if type in ('control', 'both'):
    1213                         for s in [s for s in services \
    1214                                 if s.get('name', "") in self.imports]:
    1215                             server_service_out[s['name']](f, s)
    1216 
    1217                 if tunnelconfig:
    1218                     print >>f, "tunnelip: %s" % tunnelconfig
    1219                 print >>f, "peer: %s" % peer.lower()
    1220                 print >>f, "ssh_pubkey: /proj/%s/exp/%s/tmp/%s" % \
    1221                         (lproj, leid, pubkey_base)
    1222                 print >>f, "ssh_privkey: /proj/%s/exp/%s/tmp/%s" % \
    1223                         (lproj, leid, secretkey_base)
    1224                 f.close()
    1225             except EnvironmentError, e:
    1226                 raise service_error(service_error.internal,
    1227                         "Can't write protal config %s: %s" % (cfn, e))
    1228 
    1229         # Done with portals, write the client config file.
    1230         try:
    1231             f = open("%s/client.conf" % tmpdir, "w")
    1232             if control_gw:
    1233                 print >>f, "ControlGateway: %s.%s.%s%s" % \
    1234                     (myname.lower(), leid.lower(), lproj.lower(),
    1235                             ldomain.lower())
    1236             for s in services:
    1237                 if s.get('name',"") in self.imports and \
    1238                         s.get('visibility','') == 'import':
    1239                     client_service_out[s['name']](f, s)
    1240                 if s.get('name', '') in self.exports and \
    1241                         s.get('visibility', '') == 'export' and \
    1242                         s['name'] in client_export_service_out:
    1243                     client_export_service_out[s['name']](f, s)
    1244             # Seer uses this.
    1245             if mproj and meid:
    1246                 print >>f, "ExperimentID: %s/%s" % (mproj, meid)
    1247             # Better way...
    1248             if testbed == master:
    1249                 print >>f, "SEERBase: True"
    1250             f.close()
    1251         except EnvironmentError, e:
    1252             raise service_error(service_error.internal,
    1253                     "Cannot write client.conf: %s" %s)
    1254 
    1255579    def generate_ns2(self, topo, expfn, softdir, master, connInfo):
    1256580        class dragon_commands:
     
    1394718                    "Cannot write experiment file %s: %s" % (expfn,e))
    1395719
    1396     def export_store_info(self, cf, proj, ename, connInfo):
    1397         """
    1398         For the export requests in the connection info, install the peer names
    1399         at the experiment controller via SetValue calls.
    1400         """
    1401 
    1402         for c in connInfo:
    1403             for p in [ p for p in c.get('parameter', []) \
    1404                     if p.get('type', '') == 'output']:
    1405 
    1406                 if p.get('name', '') == 'peer':
    1407                     k = p.get('key', None)
    1408                     surl = p.get('store', None)
    1409                     if surl and k and k.index('/') != -1:
    1410                         value = "%s.%s.%s%s" % \
    1411                                 (k[k.index('/')+1:], ename, proj, self.domain)
    1412                         req = { 'name': k, 'value': value }
    1413                         self.log.debug("Setting %s to %s on %s" % \
    1414                                 (k, value, surl))
    1415                         self.call_SetValue(surl, req, cf)
    1416                     else:
    1417                         self.log.error("Bad export request: %s" % p)
    1418                 elif p.get('name', '') == 'ssh_port':
    1419                     k = p.get('key', None)
    1420                     surl = p.get('store', None)
    1421                     if surl and k:
    1422                         req = { 'name': k, 'value': self.ssh_port }
    1423                         self.log.debug("Setting %s to %s on %s" % \
    1424                                 (k, self.ssh_port, surl))
    1425                         self.call_SetValue(surl, req, cf)
    1426                     else:
    1427                         self.log.error("Bad export request: %s" % p)
    1428                 else:
    1429                     self.log.error("Unknown export parameter: %s" % \
    1430                             p.get('name'))
    1431                     continue
    1432 
    1433     def import_store_info(self, cf, connInfo):
    1434         """
    1435         Pull any import parameters in connInfo in.  We translate them either
    1436         into known member names or fedAddrs.
    1437         """
    1438 
    1439         for c in connInfo:
    1440             for p in [ p for p in c.get('parameter', []) \
    1441                     if p.get('type', '') == 'input']:
    1442                 name = p.get('name', None)
    1443                 key = p.get('key', None)
    1444                 store = p.get('store', None)
    1445 
    1446                 if name and key and store :
    1447                     req = { 'name': key, 'wait': True }
    1448                     self.log.debug("Waiting for %s (%s) from %s" % \
    1449                             (name, key, store))
    1450                     r = self.call_GetValue(store, req, cf)
    1451                     r = r.get('GetValueResponseBody', None)
    1452                     if r :
    1453                         if r.get('name', '') == key:
    1454                             v = r.get('value', None)
    1455                             if v is not None:
    1456                                 if name == 'peer':
    1457                                     self.log.debug("Got peer %s" % v)
    1458                                     c['peer'] = v
    1459                                 else:
    1460                                     self.log.debug("Got %s %s" % (name, v))
    1461                                     if c.has_key('fedAttr'):
    1462                                         c['fedAttr'].append({
    1463                                             'attribute': name, 'value': v})
    1464                                     else:
    1465                                         c['fedAttr']= [{
    1466                                             'attribute': name, 'value': v}]
    1467                             else:
    1468                                 raise service_error(service_error.internal,
    1469                                         'None value exported for %s'  % key)
    1470                         else:
    1471                             raise service_error(service_error.internal,
    1472                                     'Different name returned for %s: %s' \
    1473                                             % (key, r.get('name','')))
    1474                     else:
    1475                         raise service_error(service_error.internal,
    1476                             'Badly formatted response: no GetValueResponseBody')
    1477                 else:
    1478                     raise service_error(service_error.internal,
    1479                         'Bad Services missing info for import %s' % c)
    1480 
    1481720    def configure_userconf(self, services):
    1482721        """
Note: See TracChangeset for help on using the changeset viewer.