source: fedd/access_to_abac.py @ 5a721ed

axis_examplecompt_changesinfo-ops
Last change on this file since 5a721ed was 5a721ed, checked in by Ted Faber <faber@…>, 14 years ago

Basic function pending creddy updates.

  • Property mode set to 100755
File size: 7.2 KB
Line 
1#!/usr/local/bin/python
2
3import sys, os
4import re
5
6from federation.fedid import fedid
7from optparse import OptionParser, OptionValueError
8
9class attribute:
10    def __init__(self, p, a):
11        self.principal = p
12        self.attr = a
13
14    def __str__(self):
15        return "%s.%s" % (self.principal, self.attr)
16
17class credential:
18    def __init__(self, p, a, req):
19        self.principal = p
20        if isinstance(a, (tuple, list, set)) and len(a) == 1:
21            self.attr = a[0]
22        else:
23            self.attr = a
24        self.req = req
25
26    def __str__(self):
27        if isinstance(self.req, (tuple, list, set)):
28            return "%s.%s <- %s" % (self.principal, self.attr, 
29                    " & ".join(["%s" % r for r in self.req]))
30        else:
31            return "%s.%s <- %s" % (self.principal, self.attr, self.req)
32
33class parse_error(RuntimeError): pass
34
35def parse_emulab(l, creds, me, to_id, p, gp, gu):
36    right_side_str = '\s*,\s*\(\s*%s\s*,\s*%s\s*,\s*%s\s*\)' % \
37            (id_same_str, id_same_str,id_same_str)
38
39    m = re.match(right_side_str, l)
40    if m:
41        project, user = m.group(1,2)
42        if project == '<same>':
43            if gp  is not None:
44                project = gp
45            else:
46                raise parse_error("Project cannot be decisively mapped: %s" % l)
47        if user == '<same>':
48            if gu is not None:
49                project = gu
50            else:
51                raise parse_error("User cannot be decisively mapped: %s" % l)
52        if project and user:
53            a = 'project_%s_user_%s' % (project, user)
54        elif project:
55            a = 'project_%s' % project
56        elif user:
57            a = 'user_%s' % user
58        else:
59            raise parse_error("No mapping for %s/%s!?" % (gp, gu))
60
61        c = credential(me, a, 
62                [attribute(p, x) for x in (gp, gu) if x is not None])
63        creds.add(c)
64        if (project, user) in to_id: to_id[(project,user)].append(c)
65        else: to_id[(project,user)] = [ c ]
66    else:
67        raise parse_error("Badly formatted local mapping: %s" % l)
68
69
70def parse_protogeni(l, creds, me, to_id, p, gp, gu):
71    right_side_str = '\s*,\s*\(\s*(%s)\s*,\s*(%s)\s*,\s*(%s)\s*,\s*(%s)\s*\)' \
72            % (path_str, id_str, path_str, id_str)
73
74    m = re.match(right_side_str, l)
75    if m:
76        cert, user, key, pw = m.group(1,2,3,4)
77        acert = re.sub('/', '_', cert)
78
79        a = "cert_%s_user_%s" % (acert, user)
80        c = credential(me, a, 
81                [attribute(p, x) for x in (gp, gu) if x is not None])
82        creds.add(c)
83        if (cert, user, key, pw) in to_id: 
84            to_id[(cert, user, key, pw)].append(c)
85        else: 
86            to_id[(cert, user, key, pw)] = [ c ]
87    else:
88        raise parse_error("Badly formatted local mapping: %s" % l)
89
90def parse_dragon(l, creds, me, to_id, p, gp, gu):
91    right_side_str = '\s*,\s*\(\s*(%s)\s*\)' % \
92            (id_str)
93
94    m = re.match(right_side_str, l)
95    if m:
96        repo= m.group(1)
97        c = credential(me, 'repo_%s' % repo, 
98                [attribute(p, x) for x in (gp, gu) if x is not None])
99        creds.add(c)
100        if repo in to_id: to_id[repo].append(c)
101        else: to_id[repo] = [ c ]
102    else:
103        raise parse_error("Badly formatted local mapping: %s" % l)
104
105parse_skel = parse_dragon
106
107def parse_internal(l, creds, me, to_id, p, gp, gu): pass
108
109
110class access_opts(OptionParser):
111    mappers = { 
112            'emulab': parse_emulab, 
113            'dragon': parse_dragon,
114            'internal': parse_internal,
115            'skel': parse_skel,
116            'protogeni': parse_protogeni,
117            }
118
119    @staticmethod
120    def parse_mapper(opt, s, val, parser, dest):
121        if val in access_opts.mappers:
122            setattr(parser.values, dest, access_opts.mappers[val])
123        else:
124            raise OptionValueError('%s must be one of %s' % \
125                    (s, ", ".join(access_opts.mappers.keys())))
126
127    def __init__(self):
128        OptionParser.__init__(self, usage='%prog [opts] file [...]')
129        self.add_option('--cert', dest='cert', default=None,
130                help='my fedid as an X.509 certificate')
131        self.add_option('--key', dest='key', default=None,
132                help='key for the certificate')
133        self.add_option('--dir', dest='dir', default=None,
134                help='Output directory for credentials')
135        self.add_option('--type', action='callback', nargs=1, type='str',
136                callback=access_opts.parse_mapper, 
137                callback_kwargs = { 'dest': 'mapper'}, 
138                help='Type of access file to parse.  One of %s. ' %\
139                        ", ".join(access_opts.mappers.keys()) + \
140                        'Omit for generic parsing.')
141        self.add_option('--quiet', dest='quiet', action='store_true', 
142                default=False,
143                help='Do not print credential to local attribute map')
144        self.add_option('--create-creds', action='store_true', 
145                dest='create_creds', default=False,
146                help='create credentials for rules.  Requires ' + \
147                        '--cert, --key, and --dir to be given.')
148        self.set_defaults(mapper=None)
149
150def create_creds(creds, cert, key, dir, creddy='/usr/local/bin/creddy'):
151    def attrs(r):
152        if r.principal and r.attr:
153            return ['--subject-id=%s' % r.principal, 
154                    '--subject-role=%s' %r.attr]
155        elif r.principal:
156            return ['--subject-id=%s' % r.prinicpal]
157        else:
158            raise parse_error('Attribute without a principal?')
159
160    for i, c in enumerate(creds):
161        cmd = [creddy, '--attribute', '--issuer=%s' % cert, '--key=%s' % key,
162                '--role=%s' % c.attr, '--out=%s/cred%d' % (dir, i)]
163        for r in c.req:
164            cmd.extend(attrs(r))
165        print " ".join(cmd)
166
167comment_re = re.compile('^\s*#|^$')
168fedid_str = 'fedid:([0-9a-fA-F]{40})'
169id_str = '[a-zA-Z][\w_-]*'
170path_str = '[a-zA-Z_/\.-]+'
171id_any_str = '(%s|<any>)' % id_str
172id_same_str = '(%s|<same>)' % id_str
173left_side_str = '\(\s*%s\s*,\s*%s\s*,\s*%s\s*\)' % \
174        (fedid_str, id_any_str, id_any_str)
175right_side_str = '(%s)(\s*,\s*\(.*\))?' % (id_str)
176line_re = re.compile('%s\s*->\s*%s' % (left_side_str, right_side_str))
177
178p = access_opts()
179opts, args = p.parse_args()
180
181if len(args) < 1:
182    sys.exit('No filenames given to parse')
183
184if opts.cert: 
185    try:
186        me = fedid(file=opts.cert) 
187    except EnvironmentError, e:
188        sys.exit('Bad --cert: %s (%s)' % (e.strerror, e.filename or '?!'))
189else: 
190    print >>sys.stderr, 'No --cert, using dummy fedid'
191    me = fedid(hexstr='0123456789012345678901234567890123456789')
192
193if opts.key and not os.access(opts.key, os.R_OK):
194    sys.exit('Cannot read key (%s)' % opts.key)
195
196if opts.dir:
197    if not os.path.isdir(opts.dir):
198        sys.exit('%s is not a directory' % opts.dir)
199    elif not os.access(opts.dir, os.W_OK):
200        sys.exit('%s is not writable' % opts.dir)
201
202mapper = opts.mapper
203
204for fn in args:
205    creds = set()
206    to_id = { }
207    try:
208        f = open(fn, "r")
209        for i, l in enumerate(f):
210            try:
211                if comment_re.match(l):
212                    continue
213                else:
214                    m =  line_re.match(l)
215                    if m:
216                        p, da = m.group(1, 4)
217                        gp, gu = m.group(2, 3)
218                        if gp == '<any>': gp = None
219                        if gu == '<any>': gu = None
220
221                        creds.add(credential(me, da, 
222                                [attribute(p, x) for x in (gp, gu) \
223                                    if x is not None]))
224                        if m.group(5) and mapper:
225                            mapper(m.group(5), creds, me, to_id, p, gp, gu)
226                    else:
227                        raise parse_error('Syntax error')
228            except parse_error, e:
229                raise parse_error('Error on line %d of %s: %s' % \
230                        (i, fn, e.message))
231               
232        f.close()
233    except parse_error, e:
234        print >> sys.stderr, "%s" % e
235        continue
236
237    except EnvironmentError, e:
238        print >>sys.stderr, "File error %s: %s" % \
239                (e.filename or '!?', e.strerror) 
240        continue
241
242    if opts.create_creds:
243        if all([opts.cert, opts.key, opts.dir]):
244            create_creds([c for c in creds if c.principal == me],
245                    opts.cert, opts.key, opts.dir)
246        else:
247            print >>sys.stderr, 'Cannot create credentials.  Missing parameter'
248
249    if not opts.quiet:
250        for k, c in to_id.items():
251            for a in set(["%s.%s" % (x.principal, x.attr) for x in c]):
252                print "%s -> (%s)" % ( a, ", ".join(k))
Note: See TracBrowser for help on using the repository browser.