source: fedd/federation/deter_internal_access.py @ d2e86f6

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

Single access works

  • Property mode set to 100644
File size: 9.5 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
27
28from access import access_base
29from legacy_access import legacy_access
30
31# Make log messages disappear if noone configures a fedd logger
32class nullHandler(logging.Handler):
33    def emit(self, record): pass
34
35fl = logging.getLogger("fedd.access")
36fl.addHandler(nullHandler())
37
38class access(access_base, legacy_access):
39    @staticmethod
40    def parse_vlans(v, log=None):
41        """
42        Parse a vlan parameter into a set of vlan ids.  Comma separated
43        sequences of vlan ranges are acceptable.
44        """
45        # the vlans can be a single integer, a comma separated list or a
46        # comma separated lists of dashed ranges.  E.g 100 or 100,300 or
47        # 100,300-305,400
48        vset = set()
49        if v:
50            for v in [ x.strip() for x in v.split(",") ]:
51                try:
52                    if v.count("-") == 1:
53                        f, t = v.split("-", 1)
54                        for i in range(int(f), int(t)+1):
55                            vset.add(i)
56                    else:
57                        vset.add(int(v))
58                except ValueError:
59                    if log:
60                        log.warn("Invalid expression in vlan list: %s" % v)
61        return vset
62
63    def __init__(self, config=None, auth=None):
64        """
65        Initializer.  Pulls parameters out of the ConfigParser's access section.
66        """
67
68        access_base.__init__(self, config, auth)
69        self.domain = config.get("access", "domain")
70        vlan_str = config.get("access", "vlans")
71        self.vlans = self.parse_vlans(vlan_str)
72
73        self.attrs = { }
74        self.access = { }
75        # State is a dict of dicts indexed by segment fedid that includes the
76        # owners of the segment as fedids (who can manipulate it, key: owners),
77        # the repo dir/user for the allocation (key: user),  Current allocation
78        # log (key: log), and GRI of the reservation once made (key: gri)
79        self.log = logging.getLogger("fedd.access")
80        set_log_level(config, "access", self.log)
81
82
83        # authorization information
84        self.auth_type = config.get('access', 'auth_type') \
85                or 'legacy'
86        self.auth_dir = config.get('access', 'auth_dir')
87        accessdb = config.get("access", "accessdb")
88        # initialize the authorization system
89        if self.auth_type == 'legacy':
90            self.access = { }
91            if accessdb:
92                self.legacy_read_access(accessdb)
93        elif self.auth_type == 'abac':
94            self.auth = abac_authorizer(load=self.auth_dir)
95            self.access = [ ]
96            if accessdb:
97                self.read_access(accessdb, default=[('access', None)])
98        else:
99            raise service_error(service_error.internal, 
100                    "Unknown auth_type: %s" % self.auth_type)
101
102        if self.auth_type == 'legacy':
103            # Add the ownership attributes to the authorizer.  Note that the
104            # indices of the allocation dict are strings, but the attributes are
105            # fedids, so there is a conversion.
106            self.state_lock.acquire()
107            for k in self.state.keys():
108                for o in self.state[k].get('owners', []):
109                    self.auth.set_attribute(o, fedid(hexstr=k))
110                self.auth.set_attribute(fedid(hexstr=k),fedid(hexstr=k))
111                # If the allocation has a vlan assigned, remove it from the
112                # available vlans
113                v = self.state[k].get('vlan', None)
114                if v:
115                    self.vlans.discard(v)
116            self.state_lock.release()
117
118            self.lookup_access = self.legacy_lookup_access_base
119        # under ABAC we use access.lookup_access
120
121
122        self.call_GetValue= service_caller('GetValue')
123        self.call_SetValue= service_caller('SetValue')
124
125        self.soap_services = {\
126            'RequestAccess': soap_handler("RequestAccess", self.RequestAccess),
127            'ReleaseAccess': soap_handler("ReleaseAccess", self.ReleaseAccess),
128            'StartSegment': soap_handler("StartSegment", self.StartSegment),
129            'TerminateSegment': soap_handler("TerminateSegment", 
130                self.TerminateSegment),
131            }
132        self.xmlrpc_services =  {\
133            'RequestAccess': xmlrpc_handler('RequestAccess',
134                self.RequestAccess),
135            'ReleaseAccess': xmlrpc_handler('ReleaseAccess',
136                self.ReleaseAccess),
137            'StartSegment': xmlrpc_handler("StartSegment", self.StartSegment),
138            'TerminateSegment': xmlrpc_handler('TerminateSegment',
139                self.TerminateSegment),
140            }
141
142    # RequestAccess and ReleaseAccess come from the base
143
144    def extract_parameters(self, top):
145        """
146        DETER's internal networking currently supports a fixed capacity link
147        between two endpoints.  Those endpoints may specify a VPN (or list or
148        range) to use.  This extracts the and vpn preferences from the segments
149        (as attributes) and the capacity of the connection as given by the
150        substrate.  The two endpoints VLAN choices are intersected to get set
151        of VLANs that are acceptable (no VLAN requiremnets means any is
152        acceptable).
153        """
154        segments = filter(lambda x: isinstance(x, topdl.Segment), top.elements)
155
156        if len(segments) != 2 or len(top.substrates) != 1:
157            raise service_error(service_error.req,
158                    "Requests to DRAGON must have exactlty two segments " +\
159                            "and one substrate")
160
161        vlans = set()
162        for s in segments:
163            v = s.get_attribute('vlans')
164            vlans &= self.parse_vlans(v)
165
166        if len(vlans) == 0:
167            vlans = None
168
169        sub = top.substrates[0]
170        if sub.capacity:
171            cap = int(sub.capacity.rate / 1000.0)
172            if cap < 1: cap = 1
173        else:
174            cap = 100
175
176        return cap, vlans
177
178    def export_store_info(self, cf, vlan, connInfo):
179        """
180        For the export requests in the connection info, install the peer names
181        at the experiment controller via SetValue calls.
182        """
183
184        for c in connInfo:
185            for p in [ p for p in c.get('parameter', []) \
186                    if p.get('type', '') == 'output']:
187
188                if p.get('name', '') != 'vlan_id':
189                    self.log.error("Unknown export parameter: %s" % \
190                            p.get('name'))
191                    continue
192
193                k = p.get('key', None)
194                surl = p.get('store', None)
195                if surl and k:
196                    value = "%s" % vlan
197                    req = { 'name': k, 'value': value }
198                    self.call_SetValue(surl, req, cf)
199                else:
200                    self.log.error("Bad export request: %s" % p)
201
202    def StartSegment(self, req, fid):
203        err = None  # Any service_error generated after tmpdir is created
204        rv = None   # Return value from segment creation
205
206        try:
207            req = req['StartSegmentRequestBody']
208            topref = req['segmentdescription']['topdldescription']
209        except KeyError:
210            raise service_error(server_error.req, "Badly formed request")
211
212        auth_attr = req['allocID']['fedid']
213        aid = "%s" % auth_attr
214        attrs = req.get('fedAttr', [])
215        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
216                with_proof=True)
217        if not access_ok:
218            raise service_error(service_error.access, "Access denied")
219        else:
220            # See if this is a replay of an earlier succeeded StartSegment -
221            # sometimes SSL kills 'em.  If so, replay the response rather than
222            # redoing the allocation.
223            self.state_lock.acquire()
224            retval = self.state[aid].get('started', None)
225            self.state_lock.release()
226            if retval:
227                self.log.warning("Duplicate StartSegment for %s: " % aid + \
228                        "replaying response")
229                return retval
230
231        certfile = "%s/%s.pem" % (self.certdir, aid)
232
233        if topref: topo = topdl.Topology(**topref)
234        else:
235            raise service_error(service_error.req, 
236                    "Request missing segmentdescription'")
237
238        connInfo = req.get('connection', [])
239
240        cap, vlans = self.extract_parameters(topo)
241
242        # No vlans passes in, consider any vlan acceptable
243        if not vlans: 
244            vlans = self.vlans
245
246        avail = self.vlans | vlans
247
248        if len(avail) != 0:
249            vlan_no = avail.pop()
250            self.vlans.discard(vlan_no)
251        else:
252            raise service_error(service_error.federant, "No vlan available")
253
254        self.export_store_info(certfile, vlan_no, connInfo)
255
256
257        # This annotation isn't strictly necessary, but may help in debugging
258        rtopo = topo.clone()
259        for s in rtopo.substrates:
260            s.set_attribute('vlan', vlan_no)
261
262        # Grab the log (this is some anal locking, but better safe than
263        # sorry)
264        self.state_lock.acquire()
265        self.state[aid]['vlan'] = vlan_no
266        logv = "Allocated vlan: %d" % vlan_no
267        # It's possible that the StartSegment call gets retried (!).
268        # if the 'started' key is in the allocation, we'll return it rather
269        # than redo the setup.
270        self.state[aid]['started'] = { 
271                'allocID': req['allocID'],
272                'allocationLog': logv,
273                'segmentdescription': { 'topdldescription': rtopo.to_dict() },
274                'proof': proof.to_dict(),
275                }
276        retval = copy.deepcopy(self.state[aid]['started'])
277        self.write_state()
278        self.state_lock.release()
279
280        return retval
281
282    def TerminateSegment(self, req, fid):
283        try:
284            req = req['TerminateSegmentRequestBody']
285        except KeyError:
286            raise service_error(server_error.req, "Badly formed request")
287
288        auth_attr = req['allocID']['fedid']
289        aid = "%s" % auth_attr
290
291        self.log.debug("Terminate request for %s" %aid)
292        access_ok, proof = self.auth.check_attribute(fid, auth_attr, 
293                with_proof=True)
294        if not access_ok:
295            raise service_error(service_error.access, "Access denied")
296
297        self.state_lock.acquire()
298        if self.state.has_key(aid):
299            vlan_no = self.state[aid].get('vlan', None)
300        else:
301            vlan_no = None
302        self.state_lock.release()
303        self.log.debug("Stop segment for vlan: %s" % vlan_no)
304
305        if not vlan_no:
306            raise service_error(service_error.internal, 
307                    "Can't find assigfned vlan for for %s" % aid)
308   
309        self.vlans.add(vlan_no)
310        self.state_lock.acquire()
311        self.state[aid]['vlan'] = None
312        self.state_lock.release()
313        return { 'allocID': req['allocID'], 'proof': proof.to_dict() }
Note: See TracBrowser for help on using the repository browser.