[2e46f35] | 1 | #!/usr/bin/env python |
---|
[87c0fc1] | 2 | |
---|
| 3 | import sys |
---|
| 4 | import re |
---|
| 5 | |
---|
| 6 | import os |
---|
| 7 | import os.path |
---|
| 8 | import subprocess |
---|
[e65150a] | 9 | from tempfile import mkdtemp |
---|
[87c0fc1] | 10 | |
---|
| 11 | from string import join |
---|
| 12 | |
---|
[4157521] | 13 | import Creddy |
---|
| 14 | |
---|
[e65150a] | 15 | from federation.authorizer import abac_authorizer |
---|
[62f3dd9] | 16 | from federation.util import abac_pem_type, abac_split_cert, file_expanding_opts |
---|
[c573278] | 17 | |
---|
[62f3dd9] | 18 | class Parser(file_expanding_opts): |
---|
[87c0fc1] | 19 | def __init__(self): |
---|
[62f3dd9] | 20 | file_expanding_opts.__init__(self) |
---|
[87c0fc1] | 21 | self.add_option('--cert', dest='cert', default=None, |
---|
[62f3dd9] | 22 | action='callback', callback=self.expand_file, type='str', |
---|
[87c0fc1] | 23 | help='my fedid as an X.509 certificate') |
---|
| 24 | self.add_option('--key', dest='key', default=None, |
---|
[62f3dd9] | 25 | action='callback', callback=self.expand_file, type='str', |
---|
[87c0fc1] | 26 | help='key for the certificate') |
---|
| 27 | self.add_option('--dir', dest='dir', default=None, |
---|
[62f3dd9] | 28 | action='callback', callback=self.expand_file, type='str', |
---|
[87c0fc1] | 29 | help='Output directory for credentials') |
---|
[f938d66] | 30 | self.add_option('--make_dir', action='store_true', dest='make_dir', |
---|
[87c0fc1] | 31 | default=False, help='Create the --dir directory') |
---|
| 32 | self.add_option('--debug', action='store_true', dest='debug', |
---|
[4157521] | 33 | default=False, help='Just print the libcreddy parameters') |
---|
[e65150a] | 34 | self.add_option('--policy_only', action='store_const', const=False, |
---|
| 35 | dest='make_authorizer', default=True, |
---|
[b5aa64a] | 36 | help='Only create the directory of certs, ' + \ |
---|
| 37 | 'do not create an authorizer') |
---|
[e65150a] | 38 | self.add_option('--update', action='store_const', const=True, |
---|
| 39 | dest='update_authorizer', default=False, |
---|
| 40 | help='Add the generated policy to an existing authorizer') |
---|
[87c0fc1] | 41 | |
---|
| 42 | class identity: |
---|
| 43 | def __init__(self, name, roles=None): |
---|
| 44 | self.name = name |
---|
| 45 | if roles: self.roles = set(roles) |
---|
| 46 | |
---|
| 47 | def add_roles(self, roles): |
---|
| 48 | self.roles |= set(roles) |
---|
| 49 | |
---|
| 50 | def __str__(self): |
---|
| 51 | return "%s: %s" % (self.name, join(self.roles, ', ')) |
---|
| 52 | |
---|
[e65150a] | 53 | def clear_dir(dir): |
---|
| 54 | for path, dirs, files in os.walk(dir, topdown=False): |
---|
| 55 | for f in files: os.unlink(os.path.join(path, f)) |
---|
| 56 | for d in dirs: os.rmdir(os.path.join(path, d)) |
---|
| 57 | |
---|
| 58 | def parse_configs(files): |
---|
| 59 | """ |
---|
| 60 | Step through each file pulling the roles out of the database lines, if any, |
---|
| 61 | and creating (or appending to) identity objects, one identity in a dict for |
---|
| 62 | each fedid. Return that dict. May raise an exception when file |
---|
| 63 | difficulties occur. |
---|
| 64 | """ |
---|
| 65 | comment_re = re.compile('^\s*#|^$') |
---|
| 66 | fedid_str = 'fedid:([0-9a-fA-F]{40})' |
---|
[f3898f7] | 67 | id_str = '[a-zA-Z][\w/_-]*' |
---|
[e65150a] | 68 | single_re = re.compile('\s*%s\s*->\s*(%s)' % (fedid_str, id_str)) |
---|
| 69 | double_re = re.compile('\s*%s\s*->\s*\((%s)\s*,\s*(%s)\)' % \ |
---|
| 70 | (fedid_str, id_str, id_str)) |
---|
| 71 | bad_role = re.compile('[^a-zA-Z0-9_]+') |
---|
| 72 | |
---|
| 73 | roles = { } |
---|
| 74 | |
---|
| 75 | for fn in files: |
---|
| 76 | f = open(fn, "r") |
---|
| 77 | for l in f: |
---|
| 78 | id = None |
---|
| 79 | for r in (comment_re, single_re, double_re): |
---|
| 80 | m = r.match(l) |
---|
| 81 | if m: |
---|
| 82 | # NB, the comment_re has no groups |
---|
| 83 | if m.groups(): |
---|
| 84 | g = m.groups() |
---|
| 85 | id = g[0] |
---|
| 86 | r = [ bad_role.sub('_', x) for x in g[1:] ] |
---|
| 87 | break |
---|
| 88 | else: |
---|
| 89 | print 'Unmatched line: %s' % l |
---|
| 90 | |
---|
| 91 | if id: |
---|
[dd73c6d] | 92 | # New, create, and feduser are implicit. >sigh< |
---|
| 93 | r.extend(('new', 'create', 'feduser')) |
---|
[e65150a] | 94 | if id in roles: roles[id].add_roles(r) |
---|
| 95 | else: roles[id] = identity(r[0], r) |
---|
| 96 | |
---|
| 97 | return roles |
---|
| 98 | |
---|
| 99 | def make_credentials(roles, cert, key, creds_dir, debug): |
---|
| 100 | """ |
---|
[4157521] | 101 | From the dict of identities, indexed by fedid, call libcreddy to create the |
---|
[e65150a] | 102 | ABAC certificates. Return a list of the created files. If debug is true, |
---|
[4157521] | 103 | just print the creddy attribute creation parameters. |
---|
[e65150a] | 104 | """ |
---|
| 105 | credfiles = [] |
---|
| 106 | for k, id in roles.items(): |
---|
| 107 | for i, r in enumerate(id.roles): |
---|
| 108 | cf = '%s/%s%03d_attr.der' % \ |
---|
| 109 | (creds_dir or 'new_cert_dir', id.name, i) |
---|
[4157521] | 110 | |
---|
| 111 | |
---|
[e65150a] | 112 | if debug: |
---|
[4157521] | 113 | print 'cert %s key %s role %s principal %s out %s' % \ |
---|
| 114 | (cert, key, r, k, cf) |
---|
[e65150a] | 115 | else: |
---|
[f3898f7] | 116 | cid = Creddy.ID(cert) |
---|
| 117 | cid.load_privkey(key) |
---|
| 118 | cattr = Creddy.Attribute(cid, r, 3600 * 24 * 365 * 10) |
---|
| 119 | cattr.principal(k) |
---|
[4157521] | 120 | cattr.bake() |
---|
| 121 | cattr.write_name(cf) |
---|
| 122 | credfiles.append(cf) |
---|
[e65150a] | 123 | return credfiles |
---|
[87c0fc1] | 124 | |
---|
| 125 | parser = Parser() |
---|
| 126 | opts, args = parser.parse_args() |
---|
[c573278] | 127 | cert, key = None, None |
---|
| 128 | delete_certs = False |
---|
[87c0fc1] | 129 | |
---|
[e65150a] | 130 | if not opts.make_authorizer and opts.update_authorizer: |
---|
| 131 | sys.exit('--policy_only and --update are in conflict. Pick one.') |
---|
[87c0fc1] | 132 | |
---|
[c573278] | 133 | if opts.key: |
---|
| 134 | if os.access(opts.key, os.R_OK): key = opts.key |
---|
| 135 | else: sys.exit('Cannot read %s (key file)' % opts.key) |
---|
[87c0fc1] | 136 | |
---|
[e65150a] | 137 | if opts.make_authorizer: |
---|
| 138 | creds_dir = mkdtemp() |
---|
| 139 | delete_creds = True |
---|
| 140 | else: |
---|
| 141 | creds_dir = opts.dir |
---|
| 142 | delete_creds = False |
---|
| 143 | |
---|
[c573278] | 144 | if opts.cert: |
---|
| 145 | if os.access(opts.cert, os.R_OK): |
---|
| 146 | if not key: |
---|
| 147 | if abac_pem_type(opts.cert) == 'both': |
---|
| 148 | key, cert = abac_split_cert(opts.cert) |
---|
| 149 | delete_certs = True |
---|
| 150 | else: |
---|
| 151 | cert = opts.cert |
---|
| 152 | else: |
---|
| 153 | sys.exit('Cannot read %s (certificate file)' % opts.cert) |
---|
[87c0fc1] | 154 | |
---|
[c573278] | 155 | if any([ x is None for x in (cert, opts.dir, key)]): |
---|
| 156 | print >>sys.stderr, "Need output dir, certificate and key to make creds" |
---|
| 157 | print >>sys.stderr, "Reverting to debug mode" |
---|
| 158 | debug = True |
---|
| 159 | else: |
---|
| 160 | debug = opts.debug |
---|
[87c0fc1] | 161 | |
---|
[dcffcc6] | 162 | if opts.dir: |
---|
[4909fcf] | 163 | if not os.path.isabs(opts.dir): |
---|
| 164 | sys.exit('--dir must be absolute') |
---|
[dcffcc6] | 165 | if opts.make_dir: |
---|
| 166 | if not os.path.isdir(opts.dir) and not debug: |
---|
| 167 | try: |
---|
| 168 | os.mkdir(opts.dir, 0755) |
---|
| 169 | except EnvironmentError, e: |
---|
| 170 | sys.exit('Could not make %s: %s' % (opts.dir, e)) |
---|
| 171 | else: |
---|
| 172 | if not os.path.isdir(opts.dir): |
---|
| 173 | sys.exit('%s is not a directory' % opts.dir) |
---|
| 174 | elif not os.access(opts.dir, os.W_OK): |
---|
| 175 | sys.exit('%s is not writable' % opts.dir) |
---|
| 176 | |
---|
| 177 | |
---|
[c573278] | 178 | try: |
---|
[e65150a] | 179 | roles = parse_configs(args) |
---|
[c573278] | 180 | if not roles: |
---|
| 181 | print >>sys.stderr, "No roles found. Did you specify a configuration?" |
---|
| 182 | |
---|
[e65150a] | 183 | try: |
---|
| 184 | credfiles = make_credentials(roles, cert, key, creds_dir, debug) |
---|
| 185 | |
---|
| 186 | if opts.make_authorizer: |
---|
[c573278] | 187 | if debug: |
---|
[e65150a] | 188 | print >>sys.stderr, 'Debug mode, no authorizer created' |
---|
| 189 | elif opts.update_authorizer: |
---|
| 190 | operation = 'updat' |
---|
| 191 | a = abac_authorizer(load=opts.dir) |
---|
| 192 | a.import_credentials(file_list=credfiles) |
---|
| 193 | a.save() |
---|
[87c0fc1] | 194 | else: |
---|
[e65150a] | 195 | operation = 'creat' |
---|
| 196 | a = abac_authorizer(key=opts.key, me=opts.cert, |
---|
| 197 | certs=creds_dir, save=opts.dir) |
---|
| 198 | a.save() |
---|
| 199 | except EnvironmentError, e: |
---|
| 200 | sys.exit("Can't create or write %s: %s" % (e.filename, |
---|
| 201 | e.strerror)) |
---|
| 202 | except abac_authorizer.bad_cert_error, e: |
---|
[3bcb2eb] | 203 | sys.exit("Error %sing authorizer: %s" % (operation, e)) |
---|
[e65150a] | 204 | except RuntimeError, e: |
---|
| 205 | sys.exit('%s' % e) |
---|
| 206 | |
---|
[c573278] | 207 | finally: |
---|
| 208 | if delete_certs: |
---|
| 209 | if cert: os.unlink(cert) |
---|
| 210 | if key: os.unlink(key) |
---|
[e65150a] | 211 | if delete_creds: |
---|
| 212 | clear_dir(creds_dir) |
---|
| 213 | os.rmdir(creds_dir) |
---|