source: fedd/access_to_abac.py @ af25848

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

Comments

  • Property mode set to 100755
File size: 9.9 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    '''
11    Encapculate a principal/attribute pair.
12    '''
13    def __init__(self, p, a):
14        self.principal = p
15        self.attr = a
16
17    def __str__(self):
18        if self.attr:
19            return "%s.%s" % (self.principal, self.attr)
20        else:
21            return "%s" % self.principal
22
23class credential:
24    '''
25    A Credential, that is the requisites (as attributes) and the assigned
26    attribute (as principal, attr).   If req is iterable, the requirements are
27    an intersection/conjunction.
28    '''
29    def __init__(self, p, a, req):
30        self.principal = p
31        if isinstance(a, (tuple, list, set)) and len(a) == 1:
32            self.attr = a[0]
33        else:
34            self.attr = a
35        self.req = req
36
37    def __str__(self):
38        if isinstance(self.req, (tuple, list, set)):
39            return "%s.%s <- %s" % (self.principal, self.attr, 
40                    " & ".join(["%s" % r for r in self.req]))
41        else:
42            return "%s.%s <- %s" % (self.principal, self.attr, self.req)
43
44# Mappinng generation functiona and the access parser throw these when there is
45# a parsing problem.
46class parse_error(RuntimeError): pass
47
48# Functions to parse the individual access maps as well as an overall function
49# to parse generic ones.  The specific ones create a credential to local
50# attributes mapping and the global one creates the access policy credentials.
51
52#  All the local parsing functions get the unparsed remainder of the line
53#  (after the three-name and the attribute it maps to), the credential list to
54#  add the new ABAC credential(s) that will be mapped into the loacl
55#  credentials, the fedid of this entity, a dict mapping the local credentials
56#  to ABAC credentials that are required to exercise those local rights and the
57#  three-name (p, gp, gu) that is being mapped.
58def parse_emulab(l, creds, me, to_id, p, gp, gu):
59    '''
60    Parse the emulab (project, allocation_user, access_user) format.  Access
61    users are deprecates and allocation users used for both.  This fuction
62    collapses them.
63    '''
64    right_side_str = '\s*,\s*\(\s*%s\s*,\s*%s\s*,\s*%s\s*\)' % \
65            (id_same_str, id_same_str,id_same_str)
66
67    m = re.match(right_side_str, l)
68    if m:
69        project, user = m.group(1,2)
70        # Resolve "<same>"s in project and user
71        if project == '<same>':
72            if gp  is not None:
73                project = gp
74            else:
75                raise parse_error("Project cannot be decisively mapped: %s" % l)
76        if user == '<same>':
77            if gu is not None:
78                project = gu
79            else:
80                raise parse_error("User cannot be decisively mapped: %s" % l)
81
82        # Create a semi-mnemonic name for the destination credential (the one
83        # that will be mapped to the local attributes
84        if project and user:
85            a = 'project_%s_user_%s' % (project, user)
86        elif project:
87            a = 'project_%s' % project
88        elif user:
89            a = 'user_%s' % user
90        else:
91            raise parse_error("No mapping for %s/%s!?" % (gp, gu))
92
93        # Store the creds and map entries
94        c = credential(me, a, 
95                [attribute(p, x) for x in (gp, gu) if x is not None])
96        creds.add(c)
97        if (project, user) in to_id: to_id[(project,user)].append(c)
98        else: to_id[(project,user)] = [ c ]
99    else:
100        raise parse_error("Badly formatted local mapping: %s" % l)
101
102
103def parse_protogeni(l, creds, me, to_id, p, gp, gu):
104    '''
105    Parse the protoGENI (cert, user, user_key, cert_pw) format.
106    '''
107    right_side_str = '\s*,\s*\(\s*(%s)\s*,\s*(%s)\s*,\s*(%s)\s*,\s*(%s)\s*\)' \
108            % (path_str, id_str, path_str, id_str)
109
110    m = re.match(right_side_str, l)
111    if m:
112        cert, user, key, pw = m.group(1,2,3,4)
113        # The credential is formed from just the path (with / mapped to _) and
114        # the username.
115        acert = re.sub('/', '_', cert)
116
117        a = "cert_%s_user_%s" % (acert, user)
118
119        # Store em
120        c = credential(me, a, 
121                [attribute(p, x) for x in (gp, gu) if x is not None])
122        creds.add(c)
123        if (cert, user, key, pw) in to_id: 
124            to_id[(cert, user, key, pw)].append(c)
125        else: 
126            to_id[(cert, user, key, pw)] = [ c ]
127    else:
128        raise parse_error("Badly formatted local mapping: %s" % l)
129
130def parse_dragon(l, creds, me, to_id, p, gp, gu):
131    '''
132    Parse the dragon (repository_name) version.
133    '''
134    right_side_str = '\s*,\s*\(\s*(%s)\s*\)' % \
135            (id_str)
136
137    m = re.match(right_side_str, l)
138    if m:
139        repo= m.group(1)
140        c = credential(me, 'repo_%s' % repo, 
141                [attribute(p, x) for x in (gp, gu) if x is not None])
142        creds.add(c)
143        if repo in to_id: to_id[repo].append(c)
144        else: to_id[repo] = [ c ]
145    else:
146        raise parse_error("Badly formatted local mapping: %s" % l)
147
148def parse_skel(l, creds, me, to_id, p, gp, gu):
149    '''
150    Parse the skeleton (local_attr) version.
151    '''
152    right_side_str = '\s*,\s*\(\s*(%s)\s*\)' % \
153            (id_str)
154
155    m = re.match(right_side_str, l)
156    if m:
157        lattr = m.group(1)
158        c = credential(me, 'lattr_%s' % lattr, 
159                [attribute(p, x) for x in (gp, gu) if x is not None])
160        creds.add(c)
161        if lattr in to_id: to_id[lattr].append(c)
162        else: to_id[lattr] = [ c ]
163    else:
164        raise parse_error("Badly formatted local mapping: %s" % l)
165
166# internal plug-ins have no local attributes.
167def parse_internal(l, creds, me, to_id, p, gp, gu): pass
168
169
170class access_opts(OptionParser):
171    '''
172    Parse the options for this program.  Most are straightforward, but the
173    mapper uses a callback to convert from a string to a local mapper function.
174    '''
175    # Valid mappers
176    mappers = { 
177            'emulab': parse_emulab, 
178            'dragon': parse_dragon,
179            'internal': parse_internal,
180            'skel': parse_skel,
181            'protogeni': parse_protogeni,
182            }
183
184    @staticmethod
185    def parse_mapper(opt, s, val, parser, dest):
186        if val in access_opts.mappers:
187            setattr(parser.values, dest, access_opts.mappers[val])
188        else:
189            raise OptionValueError('%s must be one of %s' % \
190                    (s, ", ".join(access_opts.mappers.keys())))
191
192    def __init__(self):
193        OptionParser.__init__(self, usage='%prog [opts] file [...]')
194        self.add_option('--cert', dest='cert', default=None,
195                help='my fedid as an X.509 certificate')
196        self.add_option('--key', dest='key', default=None,
197                help='key for the certificate')
198        self.add_option('--dir', dest='dir', default=None,
199                help='Output directory for credentials')
200        self.add_option('--type', action='callback', nargs=1, type='str',
201                callback=access_opts.parse_mapper, 
202                callback_kwargs = { 'dest': 'mapper'}, 
203                help='Type of access file to parse.  One of %s. ' %\
204                        ", ".join(access_opts.mappers.keys()) + \
205                        'Omit for generic parsing.')
206        self.add_option('--quiet', dest='quiet', action='store_true', 
207                default=False,
208                help='Do not print credential to local attribute map')
209        self.add_option('--create-creds', action='store_true', 
210                dest='create_creds', default=False,
211                help='create credentials for rules.  Requires ' + \
212                        '--cert, --key, and --dir to be given.')
213        self.set_defaults(mapper=None)
214
215def create_creds(creds, cert, key, dir, creddy='/usr/local/bin/creddy'):
216    '''
217    Make the creddy calls to create the attributes from the list of credential
218    objects in the creds parameter.
219    '''
220    def attrs(r):
221        '''
222        Convert an attribute into creddy --subject-id and --subject-role
223        parameters
224        '''
225        if r.principal and r.attr:
226            return ['--subject-id=%s' % r.principal, 
227                    '--subject-role=%s' %r.attr]
228        elif r.principal:
229            return ['--subject-id=%s' % r.prinicpal]
230        else:
231            raise parse_error('Attribute without a principal?')
232
233    # main line of create_creds
234    for i, c in enumerate(creds):
235        cmd = [creddy, '--attribute', '--issuer=%s' % cert, '--key=%s' % key,
236                '--role=%s' % c.attr, '--out=%s/cred%d' % (dir, i)]
237        for r in c.req:
238            cmd.extend(attrs(r))
239        print " ".join(cmd)
240
241# Regular expressions and parts thereof for parsing
242comment_re = re.compile('^\s*#|^$')
243fedid_str = 'fedid:([0-9a-fA-F]{40})'
244id_str = '[a-zA-Z][\w_-]*'
245path_str = '[a-zA-Z_/\.-]+'
246id_any_str = '(%s|<any>)' % id_str
247id_same_str = '(%s|<same>)' % id_str
248left_side_str = '\(\s*%s\s*,\s*%s\s*,\s*%s\s*\)' % \
249        (fedid_str, id_any_str, id_any_str)
250right_side_str = '(%s)(\s*,\s*\(.*\))?' % (id_str)
251line_re = re.compile('%s\s*->\s*%s' % (left_side_str, right_side_str))
252
253p = access_opts()
254opts, args = p.parse_args()
255
256# Validate arguments
257if len(args) < 1:
258    sys.exit('No filenames given to parse')
259
260if opts.cert: 
261    try:
262        me = fedid(file=opts.cert) 
263    except EnvironmentError, e:
264        sys.exit('Bad --cert: %s (%s)' % (e.strerror, e.filename or '?!'))
265else: 
266    print >>sys.stderr, 'No --cert, using dummy fedid'
267    me = fedid(hexstr='0123456789012345678901234567890123456789')
268
269if opts.key and not os.access(opts.key, os.R_OK):
270    sys.exit('Cannot read key (%s)' % opts.key)
271
272if opts.dir:
273    if not os.path.isdir(opts.dir):
274        sys.exit('%s is not a directory' % opts.dir)
275    elif not os.access(opts.dir, os.W_OK):
276        sys.exit('%s is not writable' % opts.dir)
277
278mapper = opts.mapper
279
280# Do the parsing
281for fn in args:
282    creds = set()
283    to_id = { }
284    try:
285        f = open(fn, "r")
286        for i, l in enumerate(f):
287            try:
288                if comment_re.match(l):
289                    continue
290                else:
291                    m =  line_re.match(l)
292                    if m:
293                        p, da = m.group(1, 4)
294                        gp, gu = m.group(2, 3)
295                        if gp == '<any>': gp = None
296                        if gu == '<any>': gu = None
297
298                        creds.add(credential(me, da, 
299                                [attribute(p, x) for x in (gp, gu) \
300                                    if x is not None]))
301                        if m.group(5) and mapper:
302                            mapper(m.group(5), creds, me, to_id, p, gp, gu)
303                    else:
304                        raise parse_error('Syntax error')
305            except parse_error, e:
306                f.close()
307                raise parse_error('Error on line %d of %s: %s' % \
308                        (i, fn, e.message))
309               
310        f.close()
311    except parse_error, e:
312        print >> sys.stderr, "%s" % e
313        continue
314
315    except EnvironmentError, e:
316        print >>sys.stderr, "File error %s: %s" % \
317                (e.filename or '!?', e.strerror) 
318        continue
319
320    # Credential output
321    if opts.create_creds:
322        if all([opts.cert, opts.key, opts.dir]):
323            create_creds([c for c in creds if c.principal == me],
324                    opts.cert, opts.key, opts.dir)
325        else:
326            print >>sys.stderr, 'Cannot create credentials.  Missing parameter'
327
328    # Local map output
329    if not opts.quiet:
330        for k, c in to_id.items():
331            for a in set(["%s.%s" % (x.principal, x.attr) for x in c]):
332                print "%s -> (%s)" % ( a, ", ".join(k))
Note: See TracBrowser for help on using the repository browser.