source: fedd/federation/deter_internal_access.py @ 8f1db21

compt_changesinfo-ops
Last change on this file since 8f1db21 was e83f2f2, checked in by Ted Faber <faber@…>, 14 years ago

Move proofs around. Lots of changes, including fault handling.

  • Property mode set to 100644
File size: 9.6 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    # RequestAccess and ReleaseAccess come from the base
145
146    def extract_parameters(self, top):
147        """
148        DETER's internal networking currently supports a fixed capacity link
149        between two endpoints.  Those endpoints may specify a VPN (or list or
150        range) to use.  This extracts the and vpn preferences from the segments
151        (as attributes) and the capacity of the connection as given by the
152        substrate.  The two endpoints VLAN choices are intersected to get set
153        of VLANs that are acceptable (no VLAN requiremnets means any is
154        acceptable).
155        """
156        segments = filter(lambda x: isinstance(x, topdl.Segment), top.elements)
157
158        if len(segments) != 2 or len(top.substrates) != 1:
159            raise service_error(service_error.req,
160                    "Requests to DRAGON must have exactlty two segments " +\
161                            "and one substrate")
162
163        vlans = set()
164        for s in segments:
165            v = s.get_attribute('vlans')
166            vlans &= self.parse_vlans(v)
167
168        if len(vlans) == 0:
169            vlans = None
170
171        sub = top.substrates[0]
172        if sub.capacity:
173            cap = int(sub.capacity.rate / 1000.0)
174            if cap < 1: cap = 1
175        else:
176            cap = 100
177
178        return cap, vlans
179
180    def export_store_info(self, cf, vlan, connInfo):
181        """
182        For the export requests in the connection info, install the peer names
183        at the experiment controller via SetValue calls.
184        """
185
186        for c in connInfo:
187            for p in [ p for p in c.get('parameter', []) \
188                    if p.get('type', '') == 'output']:
189
190                if p.get('name', '') != 'vlan_id':
191                    self.log.error("Unknown export parameter: %s" % \
192                            p.get('name'))
193                    continue
194
195                k = p.get('key', None)
196                surl = p.get('store', None)
197                if surl and k:
198                    value = "%s" % vlan
199                    req = { 'name': k, 'value': value }
200                    self.call_SetValue(surl, req, cf)
201                else:
202                    self.log.error("Bad export request: %s" % p)
203
204    def StartSegment(self, req, fid):
205        err = None  # Any service_error generated after tmpdir is created
206        rv = None   # Return value from segment creation
207
208        try:
209            req = req['StartSegmentRequestBody']
210            topref = req['segmentdescription']['topdldescription']
211        except KeyError:
212            raise service_error(server_error.req, "Badly formed request")
213
214        auth_attr = req['allocID']['fedid']
215        aid = "%s" % auth_attr
216        attrs = req.get('fedAttr', [])
217        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
218                with_proof=True)
219        if not access_ok:
220            raise service_error(service_error.access, "Access denied")
221        else:
222            # See if this is a replay of an earlier succeeded StartSegment -
223            # sometimes SSL kills 'em.  If so, replay the response rather than
224            # redoing the allocation.
225            self.state_lock.acquire()
226            retval = self.state[aid].get('started', None)
227            self.state_lock.release()
228            if retval:
229                self.log.warning("Duplicate StartSegment for %s: " % aid + \
230                        "replaying response")
231                return retval
232
233        certfile = "%s/%s.pem" % (self.certdir, aid)
234
235        if topref: topo = topdl.Topology(**topref)
236        else:
237            raise service_error(service_error.req, 
238                    "Request missing segmentdescription'")
239
240        connInfo = req.get('connection', [])
241
242        cap, vlans = self.extract_parameters(topo)
243
244        # No vlans passes in, consider any vlan acceptable
245        if not vlans: 
246            vlans = self.vlans
247
248        avail = self.vlans | vlans
249
250        if len(avail) != 0:
251            vlan_no = avail.pop()
252            self.vlans.discard(vlan_no)
253        else:
254            raise service_error(service_error.federant, "No vlan available")
255
256        self.export_store_info(certfile, vlan_no, connInfo)
257
258
259        # This annotation isn't strictly necessary, but may help in debugging
260        rtopo = topo.clone()
261        for s in rtopo.substrates:
262            s.set_attribute('vlan', vlan_no)
263
264        # Grab the log (this is some anal locking, but better safe than
265        # sorry)
266        self.state_lock.acquire()
267        self.state[aid]['vlan'] = vlan_no
268        logv = "Allocated vlan: %d" % vlan_no
269        # It's possible that the StartSegment call gets retried (!).
270        # if the 'started' key is in the allocation, we'll return it rather
271        # than redo the setup.
272        self.state[aid]['started'] = { 
273                'allocID': req['allocID'],
274                'allocationLog': logv,
275                'segmentdescription': { 'topdldescription': rtopo.to_dict() },
276                'proof': proof.to_dict(),
277                }
278        retval = copy.deepcopy(self.state[aid]['started'])
279        self.write_state()
280        self.state_lock.release()
281
282        return retval
283
284    def TerminateSegment(self, req, fid):
285        try:
286            req = req['TerminateSegmentRequestBody']
287        except KeyError:
288            raise service_error(server_error.req, "Badly formed request")
289
290        auth_attr = req['allocID']['fedid']
291        aid = "%s" % auth_attr
292
293        self.log.debug("Terminate request for %s" %aid)
294        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
295                with_proof=True)
296        if not access_ok:
297            raise service_error(service_error.access, "Access denied")
298
299        self.state_lock.acquire()
300        if self.state.has_key(aid):
301            vlan_no = self.state[aid].get('vlan', None)
302        else:
303            vlan_no = None
304        self.state_lock.release()
305        self.log.debug("Stop segment for vlan: %s" % vlan_no)
306
307        if not vlan_no:
308            raise service_error(service_error.internal, 
309                    "Can't find assigfned vlan for for %s" % aid)
310   
311        self.vlans.add(vlan_no)
312        self.state_lock.acquire()
313        self.state[aid]['vlan'] = None
314        self.state_lock.release()
315        return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Note: See TracBrowser for help on using the repository browser.