start to define an abstract interface for the driver
[sfa.git] / sfa / managers / registry_manager.py
1 import types
2 import time 
3 # for get_key_from_incoming_ip
4 import tempfile
5 import os
6 import commands
7
8 from sfa.util.faults import RecordNotFound, AccountNotEnabled, PermissionError, MissingAuthority, \
9     UnknownSfaType, ExistingRecord, NonExistingRecord
10 from sfa.util.prefixTree import prefixTree
11 from sfa.util.record import SfaRecord
12 from sfa.util.table import SfaTable
13 from sfa.util.xrn import Xrn, get_authority, hrn_to_urn, urn_to_hrn
14 from sfa.util.plxrn import hrn_to_pl_login_base
15 from sfa.util.version import version_core
16
17 from sfa.trust.gid import GID 
18 from sfa.trust.credential import Credential
19 from sfa.trust.certificate import Certificate, Keypair, convert_public_key
20 from sfa.trust.gid import create_uuid
21
22 class RegistryManager:
23
24     def __init__ (self): pass
25
26     # The GENI GetVersion call
27     def GetVersion(self, api):
28         peers = dict ( [ (hrn,interface._ServerProxy__host) for (hrn,interface) in api.registries.iteritems() 
29                        if hrn != api.hrn])
30         xrn=Xrn(api.hrn)
31         return version_core({'interface':'registry',
32                              'hrn':xrn.get_hrn(),
33                              'urn':xrn.get_urn(),
34                              'peers':peers})
35     
36     def GetCredential(self, api, xrn, type, is_self=False):
37         # convert xrn to hrn     
38         if type:
39             hrn = urn_to_hrn(xrn)[0]
40         else:
41             hrn, type = urn_to_hrn(xrn)
42             
43         # Is this a root or sub authority
44         auth_hrn = api.auth.get_authority(hrn)
45         if not auth_hrn or hrn == api.config.SFA_INTERFACE_HRN:
46             auth_hrn = hrn
47         # get record info
48         auth_info = api.auth.get_auth_info(auth_hrn)
49         table = SfaTable()
50         records = table.findObjects({'type': type, 'hrn': hrn})
51         if not records:
52             raise RecordNotFound(hrn)
53         record = records[0]
54     
55         # verify_cancreate_credential requires that the member lists
56         # (researchers, pis, etc) be filled in
57         if not api.driver.is_enabled_entity (record, api.aggregates):
58               raise AccountNotEnabled(": PlanetLab account %s is not enabled. Please contact your site PI" %(record['email']))
59     
60         # get the callers gid
61         # if this is a self cred the record's gid is the caller's gid
62         if is_self:
63             caller_hrn = hrn
64             caller_gid = record.get_gid_object()
65         else:
66             caller_gid = api.auth.client_cred.get_gid_caller() 
67             caller_hrn = caller_gid.get_hrn()
68         
69         object_hrn = record.get_gid_object().get_hrn()
70         rights = api.auth.determine_user_rights(caller_hrn, record)
71         # make sure caller has rights to this object
72         if rights.is_empty():
73             raise PermissionError(caller_hrn + " has no rights to " + record['name'])
74     
75         object_gid = GID(string=record['gid'])
76         new_cred = Credential(subject = object_gid.get_subject())
77         new_cred.set_gid_caller(caller_gid)
78         new_cred.set_gid_object(object_gid)
79         new_cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename())
80         #new_cred.set_pubkey(object_gid.get_pubkey())
81         new_cred.set_privileges(rights)
82         new_cred.get_privileges().delegate_all_privileges(True)
83         if 'expires' in record:
84             new_cred.set_expiration(int(record['expires']))
85         auth_kind = "authority,ma,sa"
86         # Parent not necessary, verify with certs
87         #new_cred.set_parent(api.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind))
88         new_cred.encode()
89         new_cred.sign()
90     
91         return new_cred.save_to_string(save_parents=True)
92     
93     
94     def Resolve(self, api, xrns, type=None, full=True):
95     
96         if not isinstance(xrns, types.ListType):
97             xrns = [xrns]
98             # try to infer type if not set and we get a single input
99             if not type:
100                 type = Xrn(xrns).get_type()
101         hrns = [urn_to_hrn(xrn)[0] for xrn in xrns] 
102         # load all known registry names into a prefix tree and attempt to find
103         # the longest matching prefix
104         # create a dict where key is a registry hrn and its value is a
105         # hrns at that registry (determined by the known prefix tree).  
106         xrn_dict = {}
107         registries = api.registries
108         tree = prefixTree()
109         registry_hrns = registries.keys()
110         tree.load(registry_hrns)
111         for xrn in xrns:
112             registry_hrn = tree.best_match(urn_to_hrn(xrn)[0])
113             if registry_hrn not in xrn_dict:
114                 xrn_dict[registry_hrn] = []
115             xrn_dict[registry_hrn].append(xrn)
116             
117         records = [] 
118         for registry_hrn in xrn_dict:
119             # skip the hrn without a registry hrn
120             # XX should we let the user know the authority is unknown?       
121             if not registry_hrn:
122                 continue
123     
124             # if the best match (longest matching hrn) is not the local registry,
125             # forward the request
126             xrns = xrn_dict[registry_hrn]
127             if registry_hrn != api.hrn:
128                 credential = api.getCredential()
129                 interface = api.registries[registry_hrn]
130                 server_proxy = api.server_proxy(interface, credential)
131                 peer_records = server_proxy.Resolve(xrns, credential)
132                 records.extend([SfaRecord(dict=record).as_dict() for record in peer_records])
133     
134         # try resolving the remaining unfound records at the local registry
135         local_hrns = list ( set(hrns).difference([record['hrn'] for record in records]) )
136         # 
137         table = SfaTable()
138         local_records = table.findObjects({'hrn': local_hrns})
139         # xxx driver todo
140         if full:
141             api.driver.fill_record_info(local_records, api.aggregates)
142         
143         # convert local record objects to dicts
144         records.extend([dict(record) for record in local_records])
145         if type:
146             records = filter(lambda rec: rec['type'] in [type], records)
147     
148         if not records:
149             raise RecordNotFound(str(hrns))
150     
151         return records
152     
153     def List(self, api, xrn, origin_hrn=None):
154         hrn, type = urn_to_hrn(xrn)
155         # load all know registry names into a prefix tree and attempt to find
156         # the longest matching prefix
157         records = []
158         registries = api.registries
159         registry_hrns = registries.keys()
160         tree = prefixTree()
161         tree.load(registry_hrns)
162         registry_hrn = tree.best_match(hrn)
163        
164         #if there was no match then this record belongs to an unknow registry
165         if not registry_hrn:
166             raise MissingAuthority(xrn)
167         # if the best match (longest matching hrn) is not the local registry,
168         # forward the request
169         records = []    
170         if registry_hrn != api.hrn:
171             credential = api.getCredential()
172             interface = api.registries[registry_hrn]
173             server_proxy = api.server_proxy(interface, credential)
174             record_list = server_proxy.List(xrn, credential)
175             records = [SfaRecord(dict=record).as_dict() for record in record_list]
176         
177         # if we still have not found the record yet, try the local registry
178         if not records:
179             if not api.auth.hierarchy.auth_exists(hrn):
180                 raise MissingAuthority(hrn)
181     
182             table = SfaTable()
183             records = table.find({'authority': hrn})
184     
185         return records
186     
187     
188     def CreateGid(self, api, xrn, cert):
189         # get the authority
190         authority = Xrn(xrn=xrn).get_authority_hrn()
191         auth_info = api.auth.get_auth_info(authority)
192         if not cert:
193             pkey = Keypair(create=True)
194         else:
195             certificate = Certificate(string=cert)
196             pkey = certificate.get_pubkey()    
197         gid = api.auth.hierarchy.create_gid(xrn, create_uuid(), pkey) 
198         return gid.save_to_string(save_parents=True)
199         
200     def Register(self, api, record):
201     
202         hrn, type = record['hrn'], record['type']
203         urn = hrn_to_urn(hrn,type)
204         # validate the type
205         if type not in ['authority', 'slice', 'node', 'user']:
206             raise UnknownSfaType(type) 
207         
208         # check if record already exists
209         table = SfaTable()
210         existing_records = table.find({'type': type, 'hrn': hrn})
211         if existing_records:
212             raise ExistingRecord(hrn)
213            
214         record = SfaRecord(dict = record)
215         record['authority'] = get_authority(record['hrn'])
216         auth_info = api.auth.get_auth_info(record['authority'])
217         pub_key = None
218         # make sure record has a gid
219         if 'gid' not in record:
220             uuid = create_uuid()
221             pkey = Keypair(create=True)
222             if 'key' in record and record['key']:
223                 # use only first key in record
224                 if isinstance(record['key'], types.ListType):
225                     pub_key = record['key'][0]
226                 else:
227                     pub_key = record['key']
228                 pkey = convert_public_key(pub_key)
229     
230             gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey)
231             gid = gid_object.save_to_string(save_parents=True)
232             record['gid'] = gid
233             record.set_gid(gid)
234     
235         if type in ["authority"]:
236             # update the tree
237             if not api.auth.hierarchy.auth_exists(hrn):
238                 api.auth.hierarchy.create_auth(hrn_to_urn(hrn,'authority'))
239     
240             # get the GID from the newly created authority
241             gid = auth_info.get_gid_object()
242             record.set_gid(gid.save_to_string(save_parents=True))
243             pointer = api.driver.register (hrn, record, pub_key)
244     
245         elif (type == "slice"):
246             pointer = api.driver.register (hrn, record, pub_key)
247     
248         elif  (type == "user"):
249             pointer = api.driver.register (hrn, record, pub_key)
250     
251         elif (type == "node"):
252             pointer = api.driver.register (hrn, record, pub_key)
253
254         record.set_pointer(pointer)
255         record_id = table.insert(record)
256         record['record_id'] = record_id
257     
258         # update membership for researchers, pis, owners, operators
259         api.driver.update_membership(None, record)
260     
261         return record.get_gid_object().save_to_string(save_parents=True)
262     
263     def Update(self, api, record_dict):
264         new_record = SfaRecord(dict = record_dict)
265         type = new_record['type']
266         hrn = new_record['hrn']
267         urn = hrn_to_urn(hrn,type)
268         table = SfaTable()
269         # make sure the record exists
270         records = table.findObjects({'type': type, 'hrn': hrn})
271         if not records:
272             raise RecordNotFound(hrn)
273         record = records[0]
274         record['last_updated'] = time.gmtime()
275     
276         # Update_membership needs the membership lists in the existing record
277         # filled in, so it can see if members were added or removed
278         api.driver.fill_record_info(record, api.aggregates)
279     
280         # Use the pointer from the existing record, not the one that the user
281         # gave us. This prevents the user from inserting a forged pointer
282         pointer = record['pointer']
283         # update the PLC information that was specified with the record
284     
285         if (type == "authority"):
286             api.driver.UpdateSite(pointer, new_record)
287     
288         elif type == "slice":
289             pl_record=api.driver.sfa_fields_to_pl_fields(type, hrn, new_record)
290             if 'name' in pl_record:
291                 pl_record.pop('name')
292                 api.driver.UpdateSlice(pointer, pl_record)
293     
294         elif type == "user":
295             # SMBAKER: UpdatePerson only allows a limited set of fields to be
296             #    updated. Ideally we should have a more generic way of doing
297             #    this. I copied the field names from UpdatePerson.py...
298             update_fields = {}
299             all_fields = new_record
300             for key in all_fields.keys():
301                 if key in ['first_name', 'last_name', 'title', 'email',
302                            'password', 'phone', 'url', 'bio', 'accepted_aup',
303                            'enabled']:
304                     update_fields[key] = all_fields[key]
305             api.driver.UpdatePerson(pointer, update_fields)
306     
307             if 'key' in new_record and new_record['key']:
308                 # must check this key against the previous one if it exists
309                 persons = api.driver.GetPersons([pointer], ['key_ids'])
310                 person = persons[0]
311                 keys = person['key_ids']
312                 keys = api.driver.GetKeys(person['key_ids'])
313                 key_exists = False
314                 if isinstance(new_record['key'], types.ListType):
315                     new_key = new_record['key'][0]
316                 else:
317                     new_key = new_record['key']
318                 
319                 # Delete all stale keys
320                 for key in keys:
321                     if new_record['key'] != key['key']:
322                         api.driver.DeleteKey(key['key_id'])
323                     else:
324                         key_exists = True
325                 if not key_exists:
326                     api.driver.AddPersonKey(pointer, {'key_type': 'ssh', 'key': new_key})
327     
328                 # update the openssl key and gid
329                 pkey = convert_public_key(new_key)
330                 uuid = create_uuid()
331                 gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey)
332                 gid = gid_object.save_to_string(save_parents=True)
333                 record['gid'] = gid
334                 record = SfaRecord(dict=record)
335                 table.update(record)
336     
337         elif type == "node":
338             api.driver.UpdateNode(pointer, new_record)
339     
340         else:
341             raise UnknownSfaType(type)
342     
343         # update membership for researchers, pis, owners, operators
344         api.driver.update_membership(record, new_record)
345         
346         return 1 
347     
348     # expecting an Xrn instance
349     def Remove(self, api, xrn, origin_hrn=None):
350     
351         table = SfaTable()
352         filter = {'hrn': xrn.get_hrn()}
353         hrn=xrn.get_hrn()
354         type=xrn.get_type()
355         if type and type not in ['all', '*']:
356             filter['type'] = type
357     
358         records = table.find(filter)
359         if not records: raise RecordNotFound(hrn)
360         record = records[0]
361         type = record['type']
362     
363         credential = api.getCredential()
364         registries = api.registries
365     
366         # Try to remove the object from the PLCDB of federated agg.
367         # This is attempted before removing the object from the local agg's PLCDB and sfa table
368         if hrn.startswith(api.hrn) and type in ['user', 'slice', 'authority']:
369             for registry in registries:
370                 if registry not in [api.hrn]:
371                     try:
372                         result=registries[registry].remove_peer_object(credential, record, origin_hrn)
373                     except:
374                         pass
375         if type == "user":
376             persons = api.driver.GetPersons(record['pointer'])
377             # only delete this person if he has site ids. if he doesnt, it probably means
378             # he was just removed from a site, not actually deleted
379             if persons and persons[0]['site_ids']:
380                 api.driver.DeletePerson(record['pointer'])
381         elif type == "slice":
382             if api.driver.GetSlices(record['pointer']):
383                 api.driver.DeleteSlice(record['pointer'])
384         elif type == "node":
385             if api.driver.GetNodes(record['pointer']):
386                 api.driver.DeleteNode(record['pointer'])
387         elif type == "authority":
388             if api.driver.GetSites(record['pointer']):
389                 api.driver.DeleteSite(record['pointer'])
390         else:
391             raise UnknownSfaType(type)
392     
393         table.remove(record)
394     
395         return 1
396
397     def get_key_from_incoming_ip (self, api):
398         # verify that the callers's ip address exist in the db and is an interface
399         # for a node in the db
400         (ip, port) = api.remote_addr
401         interfaces = api.driver.GetInterfaces({'ip': ip}, ['node_id'])
402         if not interfaces:
403             raise NonExistingRecord("no such ip %(ip)s" % locals())
404         nodes = api.driver.GetNodes([interfaces[0]['node_id']], ['node_id', 'hostname'])
405         if not nodes:
406             raise NonExistingRecord("no such node using ip %(ip)s" % locals())
407         node = nodes[0]
408        
409         # look up the sfa record
410         table = SfaTable()
411         records = table.findObjects({'type': 'node', 'pointer': node['node_id']})
412         if not records:
413             raise RecordNotFound("pointer:" + str(node['node_id']))  
414         record = records[0]
415         
416         # generate a new keypair and gid
417         uuid = create_uuid()
418         pkey = Keypair(create=True)
419         urn = hrn_to_urn(record['hrn'], record['type'])
420         gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey)
421         gid = gid_object.save_to_string(save_parents=True)
422         record['gid'] = gid
423         record.set_gid(gid)
424
425         # update the record
426         table.update(record)
427   
428         # attempt the scp the key
429         # and gid onto the node
430         # this will only work for planetlab based components
431         (kfd, key_filename) = tempfile.mkstemp() 
432         (gfd, gid_filename) = tempfile.mkstemp() 
433         pkey.save_to_file(key_filename)
434         gid_object.save_to_file(gid_filename, save_parents=True)
435         host = node['hostname']
436         key_dest="/etc/sfa/node.key"
437         gid_dest="/etc/sfa/node.gid" 
438         scp = "/usr/bin/scp" 
439         #identity = "/etc/planetlab/root_ssh_key.rsa"
440         identity = "/etc/sfa/root_ssh_key"
441         scp_options=" -i %(identity)s " % locals()
442         scp_options+="-o StrictHostKeyChecking=no " % locals()
443         scp_key_command="%(scp)s %(scp_options)s %(key_filename)s root@%(host)s:%(key_dest)s" %\
444                          locals()
445         scp_gid_command="%(scp)s %(scp_options)s %(gid_filename)s root@%(host)s:%(gid_dest)s" %\
446                          locals()    
447
448         all_commands = [scp_key_command, scp_gid_command]
449         
450         for command in all_commands:
451             (status, output) = commands.getstatusoutput(command)
452             if status:
453                 raise Exception, output
454
455         for filename in [key_filename, gid_filename]:
456             os.unlink(filename)
457
458         return 1