source: fedd/federation/deter_internal_access.py @ c65b7e4

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

Access controllers delete (some) unused ABAC attrs.

  • Property mode set to 100644
File size: 12.0 KB
Line 
1#!/usr/local/bin/python
2
3import os,sys
4import re
5import string
6import copy
7import pickle
8import logging
9import time
10
11from threading import Thread, Lock
12from subprocess import Popen, call, PIPE, STDOUT
13
14from util import *
15from allocate_project import allocate_project_local, allocate_project_remote
16from fedid import fedid, generate_fedid
17from authorizer import authorizer, abac_authorizer
18from service_error import service_error
19from remote_service import xmlrpc_handler, soap_handler, service_caller
20
21import httplib
22import tempfile
23from urlparse import urlparse
24
25import topdl
26import list_log
27import proxy_emulab_segment
28import local_emulab_segment
29
30from access import access_base
31from legacy_access import legacy_access
32
33# Make log messages disappear if noone configures a fedd logger
34class nullHandler(logging.Handler):
35    def emit(self, record): pass
36
37fl = logging.getLogger("fedd.access")
38fl.addHandler(nullHandler())
39
40class access(access_base, legacy_access):
41    @staticmethod
42    def parse_vlans(v, log=None):
43        """
44        Parse a vlan parameter into a set of vlan ids.  Comma separated
45        sequences of vlan ranges are acceptable.
46        """
47        # the vlans can be a single integer, a comma separated list or a
48        # comma separated lists of dashed ranges.  E.g 100 or 100,300 or
49        # 100,300-305,400
50        vset = set()
51        if v:
52            for v in [ x.strip() for x in v.split(",") ]:
53                try:
54                    if v.count("-") == 1:
55                        f, t = v.split("-", 1)
56                        for i in range(int(f), int(t)+1):
57                            vset.add(i)
58                    else:
59                        vset.add(int(v))
60                except ValueError:
61                    if log:
62                        log.warn("Invalid expression in vlan list: %s" % v)
63        return vset
64
65    def __init__(self, config=None, auth=None):
66        """
67        Initializer.  Pulls parameters out of the ConfigParser's access section.
68        """
69
70        access_base.__init__(self, config, auth)
71        self.domain = config.get("access", "domain")
72        vlan_str = config.get("access", "vlans")
73        self.vlans = self.parse_vlans(vlan_str)
74
75        self.attrs = { }
76        self.access = { }
77        # State is a dict of dicts indexed by segment fedid that includes the
78        # owners of the segment as fedids (who can manipulate it, key: owners),
79        # the repo dir/user for the allocation (key: user),  Current allocation
80        # log (key: log), and GRI of the reservation once made (key: gri)
81        self.log = logging.getLogger("fedd.access")
82        set_log_level(config, "access", self.log)
83
84
85        # authorization information
86        self.auth_type = config.get('access', 'auth_type') \
87                or 'legacy'
88        self.auth_dir = config.get('access', 'auth_dir')
89        accessdb = config.get("access", "accessdb")
90        # initialize the authorization system
91        if self.auth_type == 'legacy':
92            self.access = { }
93            if accessdb:
94                self.legacy_read_access(accessdb)
95        elif self.auth_type == 'abac':
96            self.auth = abac_authorizer(load=self.auth_dir)
97            self.access = [ ]
98            if accessdb:
99                self.read_access(accessdb, default=[('access', None)])
100        else:
101            raise service_error(service_error.internal, 
102                    "Unknown auth_type: %s" % self.auth_type)
103
104        if self.auth_type == 'legacy':
105            # Add the ownership attributes to the authorizer.  Note that the
106            # indices of the allocation dict are strings, but the attributes are
107            # fedids, so there is a conversion.
108            self.state_lock.acquire()
109            for k in self.state.keys():
110                for o in self.state[k].get('owners', []):
111                    self.auth.set_attribute(o, fedid(hexstr=k))
112                self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
113                # If the allocation has a vlan assigned, remove it from the
114                # available vlans
115                v = self.state[k].get('vlan', None)
116                if v:
117                    self.vlans.discard(v)
118            self.state_lock.release()
119
120            self.lookup_access = self.legacy_lookup_access_base
121        # under ABAC we use access.lookup_access
122
123
124        self.call_GetValue= service_caller('GetValue')
125        self.call_SetValue= service_caller('SetValue')
126
127        self.soap_services = {\
128            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
129            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
130            'StartSegment': soap_handler("StartSegment", self.StartSegment),
131            'TerminateSegment': soap_handler("TerminateSegment", 
132                self.TerminateSegment),
133            }
134        self.xmlrpc_services =  {\
135            'RequestAccess': xmlrpc_handler('RequestAccess',
136                self.RequestAccess),
137            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
138                self.ReleaseAccess),
139            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
140            'TerminateSegment': xmlrpc_handler('TerminateSegment',
141                self.TerminateSegment),
142            }
143
144    def RequestAccess(self, req, fid):
145        """
146        Handle the access request.  Proxy if not for us.
147
148        Parse out the fields and make the allocations or rejections if for us,
149        otherwise, assuming we're willing to proxy, proxy the request out.
150        """
151
152        # The dance to get into the request body
153        if req.has_key('RequestAccessRequestBody'):
154            req = req['RequestAccessRequestBody']
155        else:
156            raise service_error(service_error.req, "No request!?")
157
158        found, match, owners = self.lookup_access(req, fid)
159        # keep track of what's been added
160        allocID, alloc_cert = generate_fedid(subj="alloc", log=self.log)
161        aid = unicode(allocID)
162
163        self.state_lock.acquire()
164        self.state[aid] = { }
165        self.state[aid]['user'] = found
166        self.state[aid]['owners'] = owners
167        self.state[aid]['vlan'] = None
168        self.state[aid]['auth'] = set()
169        self.append_allocation_authorization(aid, 
170                ((fid, allocID),(allocID, allocID)))
171        self.write_state()
172        self.state_lock.release()
173
174        try:
175            f = open("%s/%s.pem" % (self.certdir, aid), "w")
176            print >>f, alloc_cert
177            f.close()
178        except EnvironmentError, e:
179            raise service_error(service_error.internal, 
180                    "Can't open %s/%s : %s" % (self.certdir, aid, e))
181        return { 'allocID': { 'fedid': allocID } }
182
183    def ReleaseAccess(self, req, fid):
184        # The dance to get into the request body
185        if req.has_key('ReleaseAccessRequestBody'):
186            req = req['ReleaseAccessRequestBody']
187        else:
188            raise service_error(service_error.req, "No request!?")
189
190        # Local request
191        try:
192            if req['allocID'].has_key('localname'):
193                auth_attr = aid = req['allocID']['localname']
194            elif req['allocID'].has_key('fedid'):
195                aid = unicode(req['allocID']['fedid'])
196                auth_attr = req['allocID']['fedid']
197            else:
198                raise service_error(service_error.req,
199                        "Only localnames and fedids are understood")
200        except KeyError:
201            raise service_error(service_error.req, "Badly formed request")
202
203        self.log.debug("[access] deallocation requested for %s", aid)
204        if not self.auth.check_attribute(fid, auth_attr):
205            self.log.debug("[access] deallocation denied for %s", aid)
206            raise service_error(service_error.access, "Access Denied")
207
208        self.state_lock.acquire()
209        if self.state.has_key(aid):
210            self.log.debug("Found allocation for %s" %aid)
211            self.clear_allocation_authorization(aid)
212            del self.state[aid]
213            self.write_state()
214            self.state_lock.release()
215            # And remove the access cert
216            cf = "%s/%s.pem" % (self.certdir, aid)
217            self.log.debug("Removing %s" % cf)
218            os.remove(cf)
219            return { 'allocID': req['allocID'] } 
220        else:
221            self.state_lock.release()
222            raise service_error(service_error.req, "No such allocation")
223
224    def extract_parameters(self, top):
225        """
226        DETER's internal networking currently supports a fixed capacity link
227        between two endpoints.  Those endpoints may specify a VPN (or list or
228        range) to use.  This extracts the and vpn preferences from the segments
229        (as attributes) and the capacity of the connection as given by the
230        substrate.  The two endpoints VLAN choices are intersected to get set
231        of VLANs that are acceptable (no VLAN requiremnets means any is
232        acceptable).
233        """
234        segments = filter(lambda x: isinstance(x, topdl.Segment), top.elements)
235
236        if len(segments) != 2 or len(top.substrates) != 1:
237            raise service_error(service_error.req,
238                    "Requests to DRAGON must have exactlty two segments " +\
239                            "and one substrate")
240
241        vlans = set()
242        for s in segments:
243            v = s.get_attribute('vlans')
244            vlans &= self.parse_vlans(v)
245
246        if len(vlans) == 0:
247            vlans = None
248
249        sub = top.substrates[0]
250        if sub.capacity:
251            cap = int(sub.capacity.rate / 1000.0)
252            if cap < 1: cap = 1
253        else:
254            cap = 100
255
256        return cap, vlans
257
258    def export_store_info(self, cf, vlan, connInfo):
259        """
260        For the export requests in the connection info, install the peer names
261        at the experiment controller via SetValue calls.
262        """
263
264        for c in connInfo:
265            for p in [ p for p in c.get('parameter', []) \
266                    if p.get('type', '') == 'output']:
267
268                if p.get('name', '') != 'vlan_id':
269                    self.log.error("Unknown export parameter: %s" % \
270                            p.get('name'))
271                    continue
272
273                k = p.get('key', None)
274                surl = p.get('store', None)
275                if surl and k:
276                    value = "%s" % vlan
277                    req = { 'name': k, 'value': value }
278                    self.call_SetValue(surl, req, cf)
279                else:
280                    self.log.error("Bad export request: %s" % p)
281
282    def StartSegment(self, req, fid):
283        err = None  # Any service_error generated after tmpdir is created
284        rv = None   # Return value from segment creation
285
286        try:
287            req = req['StartSegmentRequestBody']
288            topref = req['segmentdescription']['topdldescription']
289        except KeyError:
290            raise service_error(server_error.req, "Badly formed request")
291
292        auth_attr = req['allocID']['fedid']
293        aid = "%s" % auth_attr
294        attrs = req.get('fedAttr', [])
295        if not self.auth.check_attribute(fid, auth_attr):
296            raise service_error(service_error.access, "Access denied")
297        else:
298            # See if this is a replay of an earlier succeeded StartSegment -
299            # sometimes SSL kills 'em.  If so, replay the response rather than
300            # redoing the allocation.
301            self.state_lock.acquire()
302            retval = self.state[aid].get('started', None)
303            self.state_lock.release()
304            if retval:
305                self.log.warning("Duplicate StartSegment for %s: " % aid + \
306                        "replaying response")
307                return retval
308
309        certfile = "%s/%s.pem" % (self.certdir, aid)
310
311        if topref: topo = topdl.Topology(**topref)
312        else:
313            raise service_error(service_error.req, 
314                    "Request missing segmentdescription'")
315
316        connInfo = req.get('connection', [])
317
318        cap, vlans = self.extract_parameters(topo)
319
320        # No vlans passes in, consider any vlan acceptable
321        if not vlans: 
322            vlans = self.vlans
323
324        avail = self.vlans | vlans
325
326        if len(avail) != 0:
327            vlan_no = avail.pop()
328            self.vlans.discard(vlan_no)
329        else:
330            raise service_error(service_error.federant, "No vlan available")
331
332        self.export_store_info(certfile, vlan_no, connInfo)
333
334
335        # This annotation isn't strictly necessary, but may help in debugging
336        rtopo = topo.clone()
337        for s in rtopo.substrates:
338            s.set_attribute('vlan', vlan_no)
339
340        # Grab the log (this is some anal locking, but better safe than
341        # sorry)
342        self.state_lock.acquire()
343        self.state[aid]['vlan'] = vlan_no
344        logv = "Allocated vlan: %d" % vlan_no
345        # It's possible that the StartSegment call gets retried (!).
346        # if the 'started' key is in the allocation, we'll return it rather
347        # than redo the setup.
348        self.state[aid]['started'] = { 
349                'allocID': req['allocID'],
350                'allocationLog': logv,
351                'segmentdescription': { 'topdldescription': rtopo.to_dict() }
352                }
353        retval = copy.deepcopy(self.state[aid]['started'])
354        self.write_state()
355        self.state_lock.release()
356
357        return retval
358
359    def TerminateSegment(self, req, fid):
360        try:
361            req = req['TerminateSegmentRequestBody']
362        except KeyError:
363            raise service_error(server_error.req, "Badly formed request")
364
365        auth_attr = req['allocID']['fedid']
366        aid = "%s" % auth_attr
367
368        self.log.debug("Terminate request for %s" %aid)
369        if not self.auth.check_attribute(fid, auth_attr):
370            raise service_error(service_error.access, "Access denied")
371
372        self.state_lock.acquire()
373        if self.state.has_key(aid):
374            vlan_no = self.state[aid].get('vlan', None)
375        else:
376            vlan_no = None
377        self.state_lock.release()
378        self.log.debug("Stop segment for vlan: %s" % vlan_no)
379
380        if not vlan_no:
381            raise service_error(service_error.internal, 
382                    "Can't find assigfned vlan for for %s" % aid)
383   
384        self.vlans.add(vlan_no)
385        self.state_lock.acquire()
386        self.state[aid]['vlan'] = None
387        self.state_lock.release()
388        return { 'allocID': req['allocID'] }
Note: See TracBrowser for help on using the repository browser.