#!/usr/local/bin/python import os,sys import re import string from fedd_util import * from fedd_access_project import access_project # Used to report errors parsing the configuration files, not in providing # service class parse_error(RuntimeError): pass class config_file: """ The implementation of access control based on mapping users to projects. Users can be mapped to existing projects or have projects created dynamically. This implements both direct requests and proxies. """ # Attributes that can be parsed from the configuration file bool_attrs = ("dynamic_projects", "project_priority", "create_debug") emulab_attrs = ("boss", "ops", "domain", "fileserver", "eventserver") id_attrs = ("testbed", "cert_file", "cert_pwd", "trusted_certs", "proxy", "proxy_cert_file", "proxy_cert_pwd", "proxy_trusted_certs", "dynamic_projects_url", "dynamic_projects_cert_file", "dynamic_projects_cert_pwd", "dynamic_projects_trusted_certs", "create_experiment_cert_file", "create_experiment_cert_pwd", "create_experiment_trusted_certs", "federation_script_dir", "ssh_pubkey_file", "experiment_state_file") id_list_attrs = ("restricted",) def __init__(self, config=None): """ Initializer. Parses a configuration if one is given. """ # Create instance attributes from the static lists for a in config_file.bool_attrs: setattr(self, a, False) for a in config_file.emulab_attrs + config_file.id_attrs: setattr(self, a, None) for a in config_file.id_list_attrs: setattr(self, a, []) self.attrs = { } self.fedid_category = { } self.fedid_default = "user" self.access = { } if config: self.read_config(config) def read_trust(self, trust): """ Read a trust file that splits fedids into testbeds, users or projects Format is: [type] fedid fedid default: type """ lineno = 0; cat = None cat_re = re.compile("\[(user|testbed|project)\]$", re.IGNORECASE) fedid_re = re.compile("[" + string.hexdigits + "]+$") default_re = re.compile("default:\s*(user|testbed|project)$", re.IGNORECASE) f = open(trust, "r") for line in f: lineno += 1 line = line.strip() if len(line) == 0 or line.startswith("#"): continue # Category line m = cat_re.match(line) if m != None: cat = m.group(1).lower() continue # Fedid line m = fedid_re.match(line) if m != None: if cat != None: self.fedid_category[fedid(hexstr=m.string)] = cat else: raise parse_error(\ "Bad fedid in trust file (%s) line: %d" % \ (trust, lineno)) continue # default line m = default_re.match(line) if m != None: self.fedid_default = m.group(1).lower() continue # Nothing matched - bad line, raise exception f.close() raise parse_error(\ "Unparsable line in trustfile %s line %d" % (trust, lineno)) f.close() def read_config(self, config): """ Read a configuration file and set internal parameters. The format is more complex than one might hope. The basic format is attribute value pairs separated by colons(:) on a signle line. The attributes in bool_attrs, emulab_attrs and id_attrs can all be set directly using the name: value syntax. E.g. boss: hostname sets self.boss to hostname. In addition, there are access lines of the form (tb, proj, user) -> (aproj, auser) that map the first tuple of names to the second for access purposes. Names in the key (left side) can include " or " to act as wildcards or to require the fields to be empty. Similarly aproj or auser can be or indicating that either the matching key is to be used or a dynamic user or project will be created. These names can also be federated IDs (fedid's) if prefixed with fedid:. Finally, the aproj can be followed with a colon-separated list of node types to which that project has access (or will have access if dynamic). Testbed attributes outside the forms above can be given using the format attribute: name value: value. The name is a single word and the value continues to the end of the line. Empty lines and lines startin with a # are ignored. Parsing errors result in a parse_error exception being raised. """ lineno=0 name_expr = "["+string.ascii_letters + string.digits + "\.\-_]+" fedid_expr = "fedid:[" + string.hexdigits + "]+" key_name = "(||"+fedid_expr + "|"+ name_expr + ")" access_proj = "((?::" + name_expr +")*|"+ \ "" + "(?::" + name_expr + ")*|" + \ fedid_expr + "(?::" + name_expr + ")*|" + \ name_expr + "(?::" + name_expr + ")*)" access_name = "(||" + fedid_expr + "|"+ name_expr + ")" bool_re = re.compile('(' + '|'.join(config_file.bool_attrs) + '):\s+(true|false)', re.IGNORECASE) string_re = re.compile( "(" + \ '|'.join(config_file.emulab_attrs + config_file.id_attrs) + \ '):\s*(.*)', re.IGNORECASE) list_re = re.compile("(" + '|'.join(config_file.id_list_attrs) + \ "):\s*(.*)", re.IGNORECASE) attr_re = re.compile('attribute:\s*([\._\-a-z0-9]+)\s+value:\s*(.*)', re.IGNORECASE) access_re = re.compile('\('+key_name+'\s*,\s*'+key_name+'\s*,\s*'+ key_name+'\s*\)\s*->\s*\('+access_proj + '\s*,\s*' + access_name + '\s*\)', re.IGNORECASE) trustfile_re = re.compile("trustfile:\s*(.*)", re.IGNORECASE) def parse_name(n): if n.startswith('fedid:'): return fedid(n[len('fedid:'):]) else: return n f = open(config, "r"); for line in f: lineno += 1 line = line.strip(); if len(line) == 0 or line.startswith('#'): continue # Boolean attribute line m = bool_re.match(line); if m != None: attr, val = m.group(1,2) setattr(self, attr.lower(), bool(val.lower() == "true")) continue # String attribute line m = string_re.match(line) if m != None: attr, val = m.group(1,2) setattr(self, attr.lower(), val) continue # List attributes m = list_re.match(line) if m != None: attr, val = m.group(1,2) l = getattr(self, attr.lower()) l.append(val) continue # Extended (attribute: x value: y) attribute line m = attr_re.match(line) if m != None: attr, val = m.group(1,2) self.attrs[attr] = val continue # Access line (t, p, u) -> (ap, au) line m = access_re.match(line) if m != None: access_key = tuple([ parse_name(x) for x in m.group(1,2,3)]) aps = m.group(4).split(":"); if aps[0] == 'fedid:': del aps[0] aps[0] = fedid(hexstr=aps[0]) au = m.group(5) if au.startswith("fedid:"): au = fedid(hexstr=aus[len("fedid:"):]) access_val = (access_project(aps[0], aps[1:]), au) self.access[access_key] = access_val continue # Trustfile inclusion m = trustfile_re.match(line) if m != None: self.read_trust(m.group(1)) continue # Nothing matched to here: unknown line - raise exception f.close() raise parse_error("Unknown statement at line %d of %s" % \ (lineno, config)) f.close() if __name__ == '__main__': if sys.argv[1]: config = config_file(sys.argv[1]) for a in [ a for a in dir(config) if a[0] != '_' \ and not callable(getattr(config,a))]: print "%s: %s" % (a, getattr(config, a))