#!/usr/local/bin/python import sys, os import re from federation.fedid import fedid from optparse import OptionParser, OptionValueError class attribute: def __init__(self, p, a): self.principal = p self.attr = a def __str__(self): return "%s.%s" % (self.principal, self.attr) class credential: def __init__(self, p, a, req): self.principal = p if isinstance(a, (tuple, list, set)) and len(a) == 1: self.attr = a[0] else: self.attr = a self.req = req def __str__(self): if isinstance(self.req, (tuple, list, set)): return "%s.%s <- %s" % (self.principal, self.attr, " & ".join(["%s" % r for r in self.req])) else: return "%s.%s <- %s" % (self.principal, self.attr, self.req) class parse_error(RuntimeError): pass def parse_emulab(l, creds, me, to_id, p, gp, gu): right_side_str = '\s*,\s*\(\s*%s\s*,\s*%s\s*,\s*%s\s*\)' % \ (id_same_str, id_same_str,id_same_str) m = re.match(right_side_str, l) if m: project, user = m.group(1,2) if project == '': if gp is not None: project = gp else: raise parse_error("Project cannot be decisively mapped: %s" % l) if user == '': if gu is not None: project = gu else: raise parse_error("User cannot be decisively mapped: %s" % l) if project and user: a = 'project_%s_user_%s' % (project, user) elif project: a = 'project_%s' % project elif user: a = 'user_%s' % user else: raise parse_error("No mapping for %s/%s!?" % (gp, gu)) c = credential(me, a, [attribute(p, x) for x in (gp, gu) if x is not None]) creds.add(c) if (project, user) in to_id: to_id[(project,user)].append(c) else: to_id[(project,user)] = [ c ] else: raise parse_error("Badly formatted local mapping: %s" % l) def parse_protogeni(l, creds, me, to_id, p, gp, gu): right_side_str = '\s*,\s*\(\s*(%s)\s*,\s*(%s)\s*,\s*(%s)\s*,\s*(%s)\s*\)' \ % (path_str, id_str, path_str, id_str) m = re.match(right_side_str, l) if m: cert, user, key, pw = m.group(1,2,3,4) acert = re.sub('/', '_', cert) a = "cert_%s_user_%s" % (acert, user) c = credential(me, a, [attribute(p, x) for x in (gp, gu) if x is not None]) creds.add(c) if (cert, user, key, pw) in to_id: to_id[(cert, user, key, pw)].append(c) else: to_id[(cert, user, key, pw)] = [ c ] else: raise parse_error("Badly formatted local mapping: %s" % l) def parse_dragon(l, creds, me, to_id, p, gp, gu): right_side_str = '\s*,\s*\(\s*(%s)\s*\)' % \ (id_str) m = re.match(right_side_str, l) if m: repo= m.group(1) c = credential(me, 'repo_%s' % repo, [attribute(p, x) for x in (gp, gu) if x is not None]) creds.add(c) if repo in to_id: to_id[repo].append(c) else: to_id[repo] = [ c ] else: raise parse_error("Badly formatted local mapping: %s" % l) parse_skel = parse_dragon def parse_internal(l, creds, me, to_id, p, gp, gu): pass class access_opts(OptionParser): mappers = { 'emulab': parse_emulab, 'dragon': parse_dragon, 'internal': parse_internal, 'skel': parse_skel, 'protogeni': parse_protogeni, } @staticmethod def parse_mapper(opt, s, val, parser, dest): if val in access_opts.mappers: setattr(parser.values, dest, access_opts.mappers[val]) else: raise OptionValueError('%s must be one of %s' % \ (s, ", ".join(access_opts.mappers.keys()))) def __init__(self): OptionParser.__init__(self, usage='%prog [opts] file [...]') self.add_option('--cert', dest='cert', default=None, help='my fedid as an X.509 certificate') self.add_option('--key', dest='key', default=None, help='key for the certificate') self.add_option('--dir', dest='dir', default=None, help='Output directory for credentials') self.add_option('--type', action='callback', nargs=1, type='str', callback=access_opts.parse_mapper, callback_kwargs = { 'dest': 'mapper'}, help='Type of access file to parse. One of %s. ' %\ ", ".join(access_opts.mappers.keys()) + \ 'Omit for generic parsing.') self.add_option('--quiet', dest='quiet', action='store_true', default=False, help='Do not print credential to local attribute map') self.add_option('--create-creds', action='store_true', dest='create_creds', default=False, help='create credentials for rules. Requires ' + \ '--cert, --key, and --dir to be given.') self.set_defaults(mapper=None) def create_creds(creds, cert, key, dir, creddy='/usr/local/bin/creddy'): def attrs(r): if r.principal and r.attr: return ['--subject-id=%s' % r.principal, '--subject-role=%s' %r.attr] elif r.principal: return ['--subject-id=%s' % r.prinicpal] else: raise parse_error('Attribute without a principal?') for i, c in enumerate(creds): cmd = [creddy, '--attribute', '--issuer=%s' % cert, '--key=%s' % key, '--role=%s' % c.attr, '--out=%s/cred%d' % (dir, i)] for r in c.req: cmd.extend(attrs(r)) print " ".join(cmd) comment_re = re.compile('^\s*#|^$') fedid_str = 'fedid:([0-9a-fA-F]{40})' id_str = '[a-zA-Z][\w_-]*' path_str = '[a-zA-Z_/\.-]+' id_any_str = '(%s|)' % id_str id_same_str = '(%s|)' % id_str left_side_str = '\(\s*%s\s*,\s*%s\s*,\s*%s\s*\)' % \ (fedid_str, id_any_str, id_any_str) right_side_str = '(%s)(\s*,\s*\(.*\))?' % (id_str) line_re = re.compile('%s\s*->\s*%s' % (left_side_str, right_side_str)) p = access_opts() opts, args = p.parse_args() if len(args) < 1: sys.exit('No filenames given to parse') if opts.cert: try: me = fedid(file=opts.cert) except EnvironmentError, e: sys.exit('Bad --cert: %s (%s)' % (e.strerror, e.filename or '?!')) else: print >>sys.stderr, 'No --cert, using dummy fedid' me = fedid(hexstr='0123456789012345678901234567890123456789') if opts.key and not os.access(opts.key, os.R_OK): sys.exit('Cannot read key (%s)' % opts.key) if opts.dir: if not os.path.isdir(opts.dir): sys.exit('%s is not a directory' % opts.dir) elif not os.access(opts.dir, os.W_OK): sys.exit('%s is not writable' % opts.dir) mapper = opts.mapper for fn in args: creds = set() to_id = { } try: f = open(fn, "r") for i, l in enumerate(f): try: if comment_re.match(l): continue else: m = line_re.match(l) if m: p, da = m.group(1, 4) gp, gu = m.group(2, 3) if gp == '': gp = None if gu == '': gu = None creds.add(credential(me, da, [attribute(p, x) for x in (gp, gu) \ if x is not None])) if m.group(5) and mapper: mapper(m.group(5), creds, me, to_id, p, gp, gu) else: raise parse_error('Syntax error') except parse_error, e: raise parse_error('Error on line %d of %s: %s' % \ (i, fn, e.message)) f.close() except parse_error, e: print >> sys.stderr, "%s" % e continue except EnvironmentError, e: print >>sys.stderr, "File error %s: %s" % \ (e.filename or '!?', e.strerror) continue if opts.create_creds: if all([opts.cert, opts.key, opts.dir]): create_creds([c for c in creds if c.principal == me], opts.cert, opts.key, opts.dir) else: print >>sys.stderr, 'Cannot create credentials. Missing parameter' if not opts.quiet: for k, c in to_id.items(): for a in set(["%s.%s" % (x.principal, x.attr) for x in c]): print "%s -> (%s)" % ( a, ", ".join(k))