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