#!/usr/bin/env python import sys import re import os import os.path import subprocess from tempfile import mkdtemp from string import join from federation.authorizer import abac_authorizer from federation.util import abac_pem_type, abac_split_cert, file_expanding_opts class Parser(file_expanding_opts): def __init__(self): file_expanding_opts.__init__(self) self.add_option('--cert', dest='cert', default=None, action='callback', callback=self.expand_file, type='str', help='my fedid as an X.509 certificate') self.add_option('--key', dest='key', default=None, action='callback', callback=self.expand_file, type='str', help='key for the certificate') self.add_option('--dir', dest='dir', default=None, action='callback', callback=self.expand_file, type='str', help='Output directory for credentials') self.add_option('--make_dir', action='store_true', dest='make_dir', default=False, help='Create the --dir directory') self.add_option('--debug', action='store_true', dest='debug', default=False, help='Just print the creddy commands') self.add_option('--policy_only', action='store_const', const=False, dest='make_authorizer', default=True, help='Only create the directory of certs, ' + \ 'do not create an authorizer') self.add_option('--update', action='store_const', const=True, dest='update_authorizer', default=False, help='Add the generated policy to an existing authorizer') class identity: def __init__(self, name, roles=None): self.name = name if roles: self.roles = set(roles) def add_roles(self, roles): self.roles |= set(roles) def __str__(self): return "%s: %s" % (self.name, join(self.roles, ', ')) def clear_dir(dir): for path, dirs, files in os.walk(dir, topdown=False): for f in files: os.unlink(os.path.join(path, f)) for d in dirs: os.rmdir(os.path.join(path, d)) def parse_configs(files): """ Step through each file pulling the roles out of the database lines, if any, and creating (or appending to) identity objects, one identity in a dict for each fedid. Return that dict. May raise an exception when file difficulties occur. """ comment_re = re.compile('^\s*#|^$') fedid_str = 'fedid:([0-9a-fA-F]{40})' id_str = '[a-zA-Z][\w_-]*' single_re = re.compile('\s*%s\s*->\s*(%s)' % (fedid_str, id_str)) double_re = re.compile('\s*%s\s*->\s*\((%s)\s*,\s*(%s)\)' % \ (fedid_str, id_str, id_str)) bad_role = re.compile('[^a-zA-Z0-9_]+') roles = { } for fn in files: f = open(fn, "r") for l in f: id = None for r in (comment_re, single_re, double_re): m = r.match(l) if m: # NB, the comment_re has no groups if m.groups(): g = m.groups() id = g[0] r = [ bad_role.sub('_', x) for x in g[1:] ] break else: print 'Unmatched line: %s' % l if id: # New and create are implicit. >sigh< r.extend(('new', 'create')) if id in roles: roles[id].add_roles(r) else: roles[id] = identity(r[0], r) return roles def make_credentials(roles, cert, key, creds_dir, debug): """ From the dict of identities, indexed by fedid, call creddy to create the ABAC certificates. Return a list of the created files. If debug is true, just print the creddy commands. """ credfiles = [] for k, id in roles.items(): for i, r in enumerate(id.roles): cf = '%s/%s%03d_attr.der' % \ (creds_dir or 'new_cert_dir', id.name, i) cmd = ['creddy', '--attribute', '--issuer=%s' % (cert or 'cert_file'), '--key=%s' % (key or 'key_file'), '--role=%s' % r, '--subject-id=%s' % k, '--out=%s' % cf ] if debug: print join(cmd) else: rv = subprocess.call(cmd) if rv != 0: raise RuntimeError('%s failed: %d' % (join(cmd), rv)) else: credfiles.append(cf) return credfiles parser = Parser() opts, args = parser.parse_args() cert, key = None, None delete_certs = False if not opts.make_authorizer and opts.update_authorizer: sys.exit('--policy_only and --update are in conflict. Pick one.') if opts.key: if os.access(opts.key, os.R_OK): key = opts.key else: sys.exit('Cannot read %s (key file)' % opts.key) if opts.make_authorizer: creds_dir = mkdtemp() delete_creds = True else: creds_dir = opts.dir delete_creds = False if opts.cert: if os.access(opts.cert, os.R_OK): if not key: if abac_pem_type(opts.cert) == 'both': key, cert = abac_split_cert(opts.cert) delete_certs = True else: cert = opts.cert else: sys.exit('Cannot read %s (certificate file)' % opts.cert) if any([ x is None for x in (cert, opts.dir, key)]): print >>sys.stderr, "Need output dir, certificate and key to make creds" print >>sys.stderr, "Reverting to debug mode" debug = True else: debug = opts.debug if opts.dir: if opts.make_dir: if not os.path.isdir(opts.dir) and not debug: try: os.mkdir(opts.dir, 0755) except EnvironmentError, e: sys.exit('Could not make %s: %s' % (opts.dir, e)) else: 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) try: roles = parse_configs(args) if not roles: print >>sys.stderr, "No roles found. Did you specify a configuration?" try: credfiles = make_credentials(roles, cert, key, creds_dir, debug) if opts.make_authorizer: if debug: print >>sys.stderr, 'Debug mode, no authorizer created' elif opts.update_authorizer: operation = 'updat' a = abac_authorizer(load=opts.dir) a.import_credentials(file_list=credfiles) a.save() else: operation = 'creat' a = abac_authorizer(key=opts.key, me=opts.cert, certs=creds_dir, save=opts.dir) a.save() except EnvironmentError, e: sys.exit("Can't create or write %s: %s" % (e.filename, e.strerror)) except abac_authorizer.bad_cert_error, e: sys.exit("Error %sing authorizer: %s" % (op, e)) except RuntimeError, e: sys.exit('%s' % e) finally: if delete_certs: if cert: os.unlink(cert) if key: os.unlink(key) if delete_creds: clear_dir(creds_dir) os.rmdir(creds_dir)