1ab5ef30dced6398cc0af057877f58a90adbe292
[sfa.git] / registry / registry.py
1 import tempfile
2 import os
3
4 import sys
5
6 from cert import *
7 from gid import *
8 from geniserver import *
9 from excep import *
10 from trustedroot import *
11 from hierarchy import *
12 from misc import *
13 from record import *
14 from genitable import *
15
16 def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
17     if type == "user":
18         if not "email" in pl_fields:
19             if not "email" in geni_fields:
20                 raise MissingGeniInfo("email")
21             pl_fields["email"] = geni_fields["email"]
22
23         if not "first_name" in pl_fields:
24             pl_fields["first_name"] = "geni"
25
26         if not "last_name" in pl_fields:
27             pl_fields["last_name"] = hrn
28
29     elif type == "slice":
30         if not "instantiation" in pl_fields:
31             pl_fields["instantiation"] = "plc-instantiated"
32         if not "name" in pl_fields:
33             pl_fields["name"] = hrn_to_pl_slicename(hrn)
34         if not "max_nodes" in pl_fields:
35             pl_fields["max_nodes"] = 10
36
37     elif type == "node":
38         if not "hostname" in pl_fields:
39             if not "dns" in geni_fields:
40                 raise MissingGeniInfo("dns")
41             pl_fields["hostname"] = geni_fields["dns"]
42
43         if not "model" in pl_fields:
44             pl_fields["model"] = "geni"
45
46     elif type == "sa":
47         pl_fields["login_base"] = hrn_to_pl_login_base(hrn)
48
49         if not "name" in pl_fields:
50             pl_fields["name"] = hrn
51
52         if not "abbreviated_name" in pl_fields:
53             pl_fields["abbreviated_name"] = hrn
54
55         if not "enabled" in pl_fields:
56             pl_fields["enabled"] = True
57
58         if not "is_public" in pl_fields:
59             pl_fields["is_public"] = True
60
61 class Registry(GeniServer):
62     def __init__(self, ip, port, key_file, cert_file):
63         GeniServer.__init__(self, ip, port, key_file, cert_file)
64
65         # get PL account settings from config module
66         self.pl_auth = get_pl_auth()
67
68         # connect to planetlab
69         if "Url" in self.pl_auth:
70             self.connect_remote_shell()
71         else:
72             self.connect_local_shell()
73
74     def connect_remote_shell(self):
75         import remoteshell
76         self.shell = remoteshell.RemoteShell()
77
78     def connect_local_shell(self):
79         import PLC.Shell
80         self.shell = PLC.Shell.Shell(globals = globals())
81
82     def register_functions(self):
83         GeniServer.register_functions(self)
84         self.server.register_function(self.create_gid)
85         self.server.register_function(self.get_self_credential)
86         self.server.register_function(self.get_credential)
87         self.server.register_function(self.get_gid)
88         self.server.register_function(self.register)
89         self.server.register_function(self.remove)
90         self.server.register_function(self.update)
91         self.server.register_function(self.list)
92         self.server.register_function(self.resolve)
93
94     def get_auth_info(self, name):
95         return AuthHierarchy.get_auth_info(name)
96
97     def get_auth_table(self, auth_name):
98         auth_info = self.get_auth_info(auth_name)
99
100         table = GeniTable(hrn=auth_name,
101                           cninfo=auth_info.get_dbinfo())
102
103         # if the table doesn't exist, then it means we haven't put any records
104         # into this authority yet.
105
106         if not table.exists():
107             report.trace("Registry: creating table for authority " + auth_name)
108             table.create()
109
110         return table
111
112     def verify_auth_belongs_to_me(self, name):
113         # get_auth_info will throw an exception if the authority does not
114         # exist
115         self.get_auth_info(name)
116
117     def verify_object_belongs_to_me(self, name):
118         auth_name = get_authority(name)
119         if not auth_name:
120             # the root authority belongs to the registry by default?
121             # TODO: is this true?
122             return
123         self.verify_auth_belongs_to_me(auth_name)
124
125     def verify_object_permission(self, name):
126         object_hrn = self.object_gid.get_hrn()
127         if object_hrn == name:
128             return
129         if name.startswith(object_hrn + "."):
130             return
131         raise PermissionError(name)
132
133     def fill_record_pl_info(self, record):
134         type = record.get_type()
135         pointer = record.get_pointer()
136
137         # records with pointer==-1 do not have plc info associated with them.
138         # for example, the top level authority records which are
139         # authorities, but not PL "sites"
140         if pointer == -1:
141             return
142
143         if (type == "sa") or (type == "ma"):
144             pl_res = self.shell.GetSites(self.pl_auth, [pointer])
145         elif (type == "slice"):
146             pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
147         elif (type == "user"):
148             pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
149         elif (type == "node"):
150             pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
151         else:
152             raise UnknownGeniType(type)
153
154         if not pl_res:
155             # the planetlab record no longer exists
156             # TODO: delete the geni record ?
157             raise PlanetLabRecordDoesNotExist(record.get_name())
158
159         record.set_pl_info(pl_res[0])
160
161     def fill_record_geni_info(self, record):
162         pass
163
164     def fill_record_info(self, record):
165         self.fill_record_pl_info(record)
166         self.fill_record_geni_info(record)
167
168     def register(self, cred, record_dict):
169         self.decode_authentication(cred, "register")
170
171         record = GeniRecord(dict = record_dict)
172         type = record.get_type()
173         name = record.get_name()
174
175         auth_name = get_authority(name)
176         self.verify_object_permission(auth_name)
177         auth_info = self.get_auth_info(auth_name)
178         table = self.get_auth_table(auth_name)
179
180         pkey = None
181
182         # check if record already exists
183         existing_records = table.resolve(type, name)
184         if existing_records:
185             raise ExistingRecord(name)
186
187         if (type == "sa") or (type=="ma"):
188             # update the tree
189             if not AuthHierarchy.auth_exists(name):
190                 AuthHierarchy.create_auth(name)
191
192             # authorities are special since they are managed by the registry
193             # rather than by the caller. We create our own GID for the
194             # authority rather than relying on the caller to supply one.
195
196             # get the GID from the newly created authority
197             child_auth_info = self.get_auth_info(name)
198             gid = auth_info.get_gid_object()
199             record.set_gid(gid.save_to_string(save_parents=True))
200
201             geni_fields = record.get_geni_info()
202             site_fields = record.get_pl_info()
203
204             # if registering a sa, see if a ma already exists
205             # if registering a ma, see if a sa already exists
206             if (type == "sa"):
207                 other_rec = table.resolve("ma", record.get_name())
208             elif (type == "ma"):
209                 other_rec = table.resolve("sa", record.get_name())
210
211             if other_rec:
212                 print "linking ma and sa to the same plc site"
213                 pointer = other_rec[0].get_pointer()
214             else:
215                 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
216                 print "adding site with fields", site_fields
217                 pointer = self.shell.AddSite(self.pl_auth, site_fields)
218
219             record.set_pointer(pointer)
220
221         elif (type == "slice"):
222             geni_fields = record.get_geni_info()
223             slice_fields = record.get_pl_info()
224
225             geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
226
227             pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
228             record.set_pointer(pointer)
229
230         elif (type == "user"):
231             geni_fields = record.get_geni_info()
232             user_fields = record.get_pl_info()
233
234             geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
235
236             pointer = self.shell.AddPerson(self.pl_auth, user_fields)
237             record.set_pointer(pointer)
238
239         elif (type == "node"):
240             geni_fields = record.get_geni_info()
241             node_fields = record.get_pl_info()
242
243             geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
244
245             login_base = hrn_to_pl_login_base(auth_name)
246
247             print "calling addnode with", login_base, node_fields
248             pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
249             record.set_pointer(pointer)
250
251         else:
252             raise UnknownGeniType(type)
253
254         table.insert(record)
255
256         return record.get_gid_object().save_to_string(save_parents=True)
257
258     def remove(self, cred, record_dict):
259         self.decode_authentication(cred, "remove")
260
261         record = GeniRecord(dict = record_dict)
262         type = record.get_type()
263
264         self.verify_object_permission(record.get_name())
265
266         auth_name = get_authority(record.get_name())
267         table = self.get_auth_table(auth_name)
268
269         # let's not trust that the caller has a well-formed record (a forged
270         # pointer field could be a disaster), so look it up ourselves
271         record_list = table.resolve(type, record.get_name())
272         if not record_list:
273             raise RecordNotFound(name)
274         record = record_list[0]
275
276         # TODO: sa, ma
277         if type == "user":
278             self.shell.DeletePerson(self.pl_auth, record.get_pointer())
279         elif type == "slice":
280             self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
281         elif type == "node":
282             self.shell.DeleteNode(self.pl_auth, record.get_pointer())
283         elif (type == "sa") or (type == "ma"):
284             if (type == "sa"):
285                 other_rec = table.resolve("ma", record.get_name())
286             elif (type == "ma"):
287                 other_rec = table.resolve("sa", record.get_name())
288
289             if other_rec:
290                 # sa and ma both map to a site, so if we are deleting one
291                 # but the other still exists, then do not delete the site
292                 print "not removing site", record.get_name(), "because either sa or ma still exists"
293                 pass
294             else:
295                 print "removing site", record.get_name()
296                 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
297         else:
298             raise UnknownGeniType(type)
299
300         table.remove(record)
301
302         return True
303
304     def update(self, cred, record_dict):
305         self.decode_authentication(cred, "update")
306
307         record = GeniRecord(dict = record_dict)
308         type = record.get_type()
309
310         self.verify_object_permission(record.get_name())
311
312         auth_name = get_authority(record.get_name())
313         table = self.get_auth_table(auth_name)
314
315         # make sure the record exists
316         existing_record_list = table.resolve(type, record.get_name())
317         if not existing_record_list:
318             raise RecordNotFound(record.get_name())
319
320         existing_record = existing_record_list[0]
321         pointer = existing_record.get_pointer()
322
323         if (type == "sa") or (type == "ma"):
324             self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
325
326         elif type == "slice":
327             self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
328
329         elif type == "user":
330             self.shell.UpdatePerson(self.pl_auth, pointer, record.get_pl_info())
331
332         elif type == "node":
333             self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
334
335         else:
336             raise UnknownGeniType(type)
337
338     # TODO: List doesn't take an hrn and uses the hrn contained in the
339     #    objectGid of the credential. Does this mean the only way to list an
340     #    authority is by having a credential for that authority? 
341     def list(self, cred):
342         self.decode_authentication(cred, "list")
343
344         auth_name = self.object_gid.get_hrn()
345         table = self.get_auth_table(auth_name)
346
347         records = table.list()
348
349         good_records = []
350         for record in records:
351             try:
352                 self.fill_record_info(record)
353                 good_records.append(record)
354             except PlanetLabRecordDoesNotExist:
355                 # silently drop the ones that are missing in PL.
356                 # is this the right thing to do?
357                 report.error("ignoring geni record " + record.get_name() + " because pl record does not exist")
358                 table.remove(record)
359
360         dicts = []
361         for record in good_records:
362             dicts.append(record.as_dict())
363
364         return dicts
365
366         return dict_list
367
368     def resolve_raw(self, type, name, must_exist=True):
369         auth_name = get_authority(name)
370
371         table = self.get_auth_table(auth_name)
372
373         records = table.resolve(type, name)
374
375         if (not records) and must_exist:
376             raise RecordNotFound(name)
377
378         good_records = []
379         for record in records:
380             try:
381                 self.fill_record_info(record)
382                 good_records.append(record)
383             except PlanetLabRecordDoesNotExist:
384                 # silently drop the ones that are missing in PL.
385                 # is this the right thing to do?
386                 report.error("ignoring geni record " + record.get_name() + " because pl record does not exist")
387                 table.remove(record)
388
389         return good_records
390
391     def resolve(self, cred, name):
392         self.decode_authentication(cred, "resolve")
393
394         records = self.resolve_raw("*", name)
395         dicts = []
396         for record in records:
397             dicts.append(record.as_dict())
398
399         return dicts
400
401     def get_gid(self, name):
402         self.verify_object_belongs_to_me(name)
403         records = self.resolve_raw("*", name)
404         gid_string_list = []
405         for record in records:
406             gid = record.get_gid()
407             gid_string_list.append(gid.save_to_string(save_parents=True))
408         return gid_string_list
409
410     def determine_rights(self, type, name):
411         rl = RightList()
412
413         # rights seem to be somewhat redundant with the type of the credential.
414         # For example, a "sa" credential implies the authority right, because
415         # a sa credential cannot be issued to a user who is not an owner of
416         # the authority
417
418         if type == "user":
419             rl.add("refresh")
420             rl.add("resolve")
421         elif type == "sa":
422             rl.add("authority")
423         elif type == "ma":
424             rl.add("authority")
425         elif type == "slice":
426             rl.add("embed")
427             rl.add("bind")
428             rl.add("control")
429         elif type == "component":
430             rl.add("operator")
431
432         return rl
433
434
435     def get_self_credential(self, type, name):
436         self.verify_object_belongs_to_me(name)
437
438         auth_hrn = get_authority(name)
439         auth_info = self.get_auth_info(auth_hrn)
440
441         # find a record that matches
442         records = self.resolve_raw(type, name, must_exist=True)
443         record = records[0]
444
445         gid = record.get_gid_object()
446         peer_cert = self.server.peer_cert
447         if not peer_cert.is_pubkey(gid.get_pubkey()):
448            raise ConnectionKeyGIDMismatch(gid.get_subject())
449
450         # create the credential
451         gid = record.get_gid_object()
452         cred = Credential(subject = gid.get_subject())
453         cred.set_gid_caller(gid)
454         cred.set_gid_object(gid)
455         cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
456         cred.set_pubkey(gid.get_pubkey())
457
458         rl = self.determine_rights(type, name)
459         cred.set_privileges(rl)
460
461         cred.set_parent(AuthHierarchy.get_auth_cred(auth_hrn))
462
463         cred.encode()
464         cred.sign()
465
466         return cred.save_to_string(save_parents=True)
467
468     def get_credential(self, cred, type, name):
469         if not cred:
470             return get_self_credential(self, type, name)
471
472         self.decode_authentication(cred, "getcredential")
473
474         self.verify_object_belongs_to_me(name)
475
476         auth_hrn = get_authority(name)
477         auth_info = self.get_auth_info(auth_hrn)
478
479         records = self.resolve_raw(type, name, must_exist=True)
480         record = records[0]
481
482         # TODO: Check permission that self.client_cred can access the object
483
484         object_gid = record.get_gid_object()
485         new_cred = Credential(subject = object_gid.get_subject())
486         new_cred.set_gid_caller(self.client_gid)
487         new_cred.set_gid_object(object_gid)
488         new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
489         new_cred.set_pubkey(object_gid.get_pubkey())
490
491         rl = self.determine_rights(type, name)
492         new_cred.set_privileges(rl)
493
494         new_cred.set_parent(AuthHierarchy.get_auth_cred(auth_hrn))
495
496         new_cred.encode()
497         new_cred.sign()
498
499         return new_cred.save_to_string(save_parents=True)
500
501     def create_gid(self, cred, name, uuid, pubkey_str):
502         self.decode_authentication(cred, "getcredential")
503
504         self.verify_object_belongs_to_me(name)
505
506         self.verify_object_permission(name)
507
508         if uuid == None:
509             uuid = create_uuid()
510
511         pkey = Keypair()
512         pkey.load_pubkey_from_string(pubkey_str)
513         gid = AuthHierarchy.create_gid(name, uuid, pkey)
514
515         return gid.save_to_string(save_parents=True)
516
517
518 if __name__ == "__main__":
519     global AuthHierarchy
520     global TrustedRoots
521
522     key_file = "server.key"
523     cert_file = "server.cert"
524
525     # if no key is specified, then make one up
526     if (not os.path.exists(key_file)) or (not os.path.exists(cert_file)):
527         key = Keypair(create=True)
528         key.save_to_file(key_file)
529
530         cert = Certificate(subject="registry")
531         cert.set_issuer(key=key, subject="registry")
532         cert.set_pubkey(key)
533         cert.sign()
534         cert.save_to_file(cert_file)
535
536     AuthHierarchy = Hierarchy()
537
538     TrustedRoots = TrustedRootList()
539
540     s = Registry("", 12345, key_file, cert_file)
541     s.trusted_cert_list = TrustedRoots.get_list()
542     s.run()
543