2 # Registry is a GeniServer that implements the Registry interface
9 from util.credential import Credential
10 from util.hierarchy import Hierarchy
11 from util.trustedroot import TrustedRootList
12 from util.cert import Keypair, Certificate
13 from util.gid import GID
14 from util.geniserver import GeniServer
15 from util.record import GeniRecord
16 from util.rights import RightList
17 from util.genitable import GeniTable
18 from util.geniticket import Ticket
19 from util.excep import *
20 from util.misc import *
22 from util.config import *
25 # Convert geni fields to PLC fields for use when registering up updating
26 # registry record in the PLC database
28 # @param type type of record (user, slice, ...)
29 # @param hrn human readable name
30 # @param geni_fields dictionary of geni fields
31 # @param pl_fields dictionary of PLC fields (output)
33 def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
35 if not "email" in pl_fields:
36 if not "email" in geni_fields:
37 raise MissingGeniInfo("email")
38 pl_fields["email"] = geni_fields["email"]
40 if not "first_name" in pl_fields:
41 pl_fields["first_name"] = "geni"
43 if not "last_name" in pl_fields:
44 pl_fields["last_name"] = hrn
47 if not "instantiation" in pl_fields:
48 pl_fields["instantiation"] = "delegated" # "plc-instantiated"
49 if not "name" in pl_fields:
50 pl_fields["name"] = hrn_to_pl_slicename(hrn)
51 if not "max_nodes" in pl_fields:
52 pl_fields["max_nodes"] = 10
55 if not "hostname" in pl_fields:
56 if not "dns" in geni_fields:
57 raise MissingGeniInfo("dns")
58 pl_fields["hostname"] = geni_fields["dns"]
60 if not "model" in pl_fields:
61 pl_fields["model"] = "geni"
64 pl_fields["login_base"] = hrn_to_pl_login_base(hrn)
66 if not "name" in pl_fields:
67 pl_fields["name"] = hrn
69 if not "abbreviated_name" in pl_fields:
70 pl_fields["abbreviated_name"] = hrn
72 if not "enabled" in pl_fields:
73 pl_fields["enabled"] = True
75 if not "is_public" in pl_fields:
76 pl_fields["is_public"] = True
79 # Registry is a GeniServer that serves registry and slice operations at PLC.
81 class Registry(GeniServer):
83 # Create a new registry object.
85 # @param ip the ip address to listen on
86 # @param port the port to listen on
87 # @param key_file private key filename of registry
88 # @param cert_file certificate filename containing public key (could be a GID file)
90 def __init__(self, ip, port, key_file, cert_file):
91 GeniServer.__init__(self, ip, port, key_file, cert_file)
93 # get PL account settings from config module
94 self.pl_auth = get_pl_auth()
96 # connect to planetlab
97 if "Url" in self.pl_auth:
98 self.connect_remote_shell()
100 self.connect_local_shell()
103 # Connect to a remote shell via XMLRPC
105 def connect_remote_shell(self):
107 self.shell = remoteshell.RemoteShell()
110 # Connect to a local shell via local API functions
112 def connect_local_shell(self):
114 self.shell = PLC.Shell.Shell(globals = globals())
117 # Register the server RPCs for the registry
119 def register_functions(self):
120 GeniServer.register_functions(self)
122 self.server.register_function(self.create_gid)
123 self.server.register_function(self.get_self_credential)
124 self.server.register_function(self.get_credential)
125 self.server.register_function(self.get_gid)
126 self.server.register_function(self.register)
127 self.server.register_function(self.remove)
128 self.server.register_function(self.update)
129 self.server.register_function(self.list)
130 self.server.register_function(self.resolve)
133 # Given an authority name, return the information for that authority. This
134 # is basically a stub that calls the hierarchy module.
136 # @param auth_hrn human readable name of authority
138 def get_auth_info(self, auth_hrn):
139 return self.hierarchy.get_auth_info(auth_hrn)
142 # Given an authority name, return the database table for that authority. If
143 # the database table does not exist, then one will be automatically
146 # @param auth_name human readable name of authority
148 def get_auth_table(self, auth_name):
149 auth_info = self.get_auth_info(auth_name)
151 table = GeniTable(hrn=auth_name,
152 cninfo=auth_info.get_dbinfo())
154 # if the table doesn't exist, then it means we haven't put any records
155 # into this authority yet.
157 if not table.exists():
158 report.trace("Registry: creating table for authority " + auth_name)
164 # Verify that an authority belongs to this registry. This is basically left
165 # up to the implementation of the hierarchy module. If the specified name
166 # does not belong to this registry, an exception is thrown indicating the
167 # caller should contact someone else.
169 # @param auth_name human readable name of authority
171 def verify_auth_belongs_to_me(self, name):
172 # get_auth_info will throw an exception if the authority does not
174 self.get_auth_info(name)
177 # Verify that an object belongs to this registry. By extension, this implies
178 # that the authority that owns the object belongs to this registry. If the
179 # object does not belong to this registry, then an exception is thrown.
181 # @param name human readable name of object
183 def verify_object_belongs_to_me(self, name):
184 auth_name = get_authority(name)
186 # the root authority belongs to the registry by default?
187 # TODO: is this true?
189 self.verify_auth_belongs_to_me(auth_name)
192 # Verify that the object_gid that was specified in the credential allows
193 # permission to the object 'name'. This is done by a simple prefix test.
194 # For example, an object_gid for planetlab.us.arizona would match the
195 # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
197 # @param name human readable name to test
199 def verify_object_permission(self, name):
200 object_hrn = self.object_gid.get_hrn()
201 if object_hrn == name:
203 if name.startswith(object_hrn + "."):
205 raise PermissionError(name)
208 # Fill in the planetlab-specific fields of a Geni record. This involves
209 # calling the appropriate PLC methods to retrieve the database record for
212 # PLC data is filled into the pl_info field of the record.
214 # @param record record to fill in fields (in/out param)
216 def fill_record_pl_info(self, record):
217 type = record.get_type()
218 pointer = record.get_pointer()
220 # records with pointer==-1 do not have plc info associated with them.
221 # for example, the top level authority records which are
222 # authorities, but not PL "sites"
224 record.set_pl_info({})
227 if (type == "sa") or (type == "ma"):
228 pl_res = self.shell.GetSites(self.pl_auth, [pointer])
229 elif (type == "slice"):
230 pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
231 elif (type == "user"):
232 pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
233 elif (type == "node"):
234 pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
236 raise UnknownGeniType(type)
239 # the planetlab record no longer exists
240 # TODO: delete the geni record ?
241 raise PlanetLabRecordDoesNotExist(record.get_name())
243 record.set_pl_info(pl_res[0])
246 # Look up user records given PLC user-ids. This is used as part of the
247 # process for reverse-mapping PLC records into Geni records.
249 # @param auth_table database table for the authority that holds the user records
250 # @param user_id_list list of user ids
251 # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
253 # TODO: This function currently only searches one authority because it would
254 # be inefficient to brute-force search all authorities for a user id. The
255 # solution would likely be to implement a reverse mapping of user-id to
258 def lookup_users(self, auth_table, user_id_list, role="*"):
260 for person_id in user_id_list:
261 user_records = auth_table.find("user", person_id, "pointer")
262 for user_record in user_records:
263 self.fill_record_info(user_record)
265 user_roles = user_record.get_pl_info().get("roles")
266 if (role=="*") or (role in user_roles):
267 record_list.append(user_record.get_name())
271 # Fill in the geni-specific fields of the record.
273 # Note: It is assumed the fill_record_pl_info() has already been performed
276 def fill_record_geni_info(self, record):
278 type = record.get_type()
280 if (type == "slice"):
281 auth_table = self.get_auth_table(get_authority(record.get_name()))
282 person_ids = record.pl_info.get("person_ids", [])
283 researchers = self.lookup_users(auth_table, person_ids)
284 geni_info['researcher'] = researchers
287 auth_table = self.get_auth_table(record.get_name())
288 person_ids = record.pl_info.get("person_ids", [])
289 pis = self.lookup_users(auth_table, person_ids, "pi")
290 geni_info['pi'] = pis
291 # TODO: OrganizationName
294 auth_table = self.get_auth_table(record.get_name())
295 person_ids = record.pl_info.get("person_ids", [])
296 operators = self.lookup_users(auth_table, person_ids, "tech")
297 geni_info['operator'] = operators
298 # TODO: OrganizationName
300 auth_table = self.get_auth_table(record.get_name())
301 person_ids = record.pl_info.get("person_ids", [])
302 owners = self.lookup_users(auth_table, person_ids, "admin")
303 geni_info['owner'] = owners
305 elif (type == "node"):
306 geni_info['dns'] = record.pl_info.get("hostname", "")
307 # TODO: URI, LatLong, IP, DNS
309 elif (type == "user"):
310 geni_info['email'] = record.pl_info.get("email", "")
311 # TODO: PostalAddress, Phone
313 record.set_geni_info(geni_info)
316 # Given a Geni record, fill in the PLC-specific and Geni-specific fields
319 def fill_record_info(self, record):
320 self.fill_record_pl_info(record)
321 self.fill_record_geni_info(record)
326 # Register an object with the registry. In addition to being stored in the
327 # Geni database, the appropriate records will also be created in the
330 # @param cred credential string
331 # @param record_dict dictionary containing record fields
333 def register(self, cred, record_dict):
334 self.decode_authentication(cred, "register")
336 record = GeniRecord(dict = record_dict)
337 type = record.get_type()
338 name = record.get_name()
340 auth_name = get_authority(name)
341 self.verify_object_permission(auth_name)
342 auth_info = self.get_auth_info(auth_name)
343 table = self.get_auth_table(auth_name)
347 # check if record already exists
348 existing_records = table.resolve(type, name)
350 raise ExistingRecord(name)
352 if (type == "sa") or (type=="ma"):
354 if not self.hierarchy.auth_exists(name):
355 self.hierarchy.create_auth(name)
357 # authorities are special since they are managed by the registry
358 # rather than by the caller. We create our own GID for the
359 # authority rather than relying on the caller to supply one.
361 # get the GID from the newly created authority
362 child_auth_info = self.get_auth_info(name)
363 gid = auth_info.get_gid_object()
364 record.set_gid(gid.save_to_string(save_parents=True))
366 geni_fields = record.get_geni_info()
367 site_fields = record.get_pl_info()
369 # if registering a sa, see if a ma already exists
370 # if registering a ma, see if a sa already exists
372 other_rec = table.resolve("ma", record.get_name())
374 other_rec = table.resolve("sa", record.get_name())
377 print "linking ma and sa to the same plc site"
378 pointer = other_rec[0].get_pointer()
380 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
381 print "adding site with fields", site_fields
382 pointer = self.shell.AddSite(self.pl_auth, site_fields)
384 record.set_pointer(pointer)
386 elif (type == "slice"):
387 geni_fields = record.get_geni_info()
388 slice_fields = record.get_pl_info()
390 geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
392 pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
393 record.set_pointer(pointer)
395 elif (type == "user"):
396 geni_fields = record.get_geni_info()
397 user_fields = record.get_pl_info()
399 geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
401 pointer = self.shell.AddPerson(self.pl_auth, user_fields)
402 record.set_pointer(pointer)
404 elif (type == "node"):
405 geni_fields = record.get_geni_info()
406 node_fields = record.get_pl_info()
408 geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
410 login_base = hrn_to_pl_login_base(auth_name)
412 print "calling addnode with", login_base, node_fields
413 pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
414 record.set_pointer(pointer)
417 raise UnknownGeniType(type)
421 return record.get_gid_object().save_to_string(save_parents=True)
426 # Remove an object from the registry. If the object represents a PLC object,
427 # then the PLC records will also be removed.
429 # @param cred credential string
430 # @param record_dict dictionary containing record fields. The only relevant
431 # fields of the record are 'name' and 'type', which are used to lookup
432 # the current copy of the record in the Geni database, to make sure
433 # that the appopriate record is removed.
435 def remove(self, cred, record_dict):
436 self.decode_authentication(cred, "remove")
438 record = GeniRecord(dict = record_dict)
439 type = record.get_type()
441 self.verify_object_permission(record.get_name())
443 auth_name = get_authority(record.get_name())
444 table = self.get_auth_table(auth_name)
446 # let's not trust that the caller has a well-formed record (a forged
447 # pointer field could be a disaster), so look it up ourselves
448 record_list = table.resolve(type, record.get_name())
450 raise RecordNotFound(name)
451 record = record_list[0]
455 self.shell.DeletePerson(self.pl_auth, record.get_pointer())
456 elif type == "slice":
457 self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
459 self.shell.DeleteNode(self.pl_auth, record.get_pointer())
460 elif (type == "sa") or (type == "ma"):
462 other_rec = table.resolve("ma", record.get_name())
464 other_rec = table.resolve("sa", record.get_name())
467 # sa and ma both map to a site, so if we are deleting one
468 # but the other still exists, then do not delete the site
469 print "not removing site", record.get_name(), "because either sa or ma still exists"
472 print "removing site", record.get_name()
473 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
475 raise UnknownGeniType(type)
484 # Update an object in the registry. Currently, this only updates the
485 # PLC information associated with the record. The Geni fields (name, type,
488 # The record is expected to have the pl_info field filled in with the data
489 # that should be updated.
491 # TODO: The geni_info member of the record should be parsed and the pl_info
492 # adjusted as necessary (add/remove users from a slice, etc)
494 # @param cred credential string specifying rights of the caller
495 # @param record a record dictionary to be updated
497 def update(self, cred, record_dict):
498 self.decode_authentication(cred, "update")
500 record = GeniRecord(dict = record_dict)
501 type = record.get_type()
503 self.verify_object_permission(record.get_name())
505 auth_name = get_authority(record.get_name())
506 table = self.get_auth_table(auth_name)
508 # make sure the record exists
509 existing_record_list = table.resolve(type, record.get_name())
510 if not existing_record_list:
511 raise RecordNotFound(record.get_name())
513 existing_record = existing_record_list[0]
514 pointer = existing_record.get_pointer()
516 # update the PLC information that was specified with the record
518 if (type == "sa") or (type == "ma"):
519 self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
521 elif type == "slice":
522 self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
525 # SMBAKER: UpdatePerson only allows a limited set of fields to be
526 # updated. Ideally we should have a more generic way of doing
527 # this. I copied the field names from UpdatePerson.py...
529 all_fields = record.get_pl_info()
530 for key in all_fields.keys():
531 if key in ['first_name', 'last_name', 'title', 'email',
532 'password', 'phone', 'url', 'bio', 'accepted_aup',
\r
534 update_fields[key] = all_fields[key]
535 self.shell.UpdatePerson(self.pl_auth, pointer, update_fields)
538 self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
541 raise UnknownGeniType(type)
544 # List the records in an authority. The objectGID in the supplied credential
545 # should name the authority that will be listed.
547 # TODO: List doesn't take an hrn and uses the hrn contained in the
548 # objectGid of the credential. Does this mean the only way to list an
549 # authority is by having a credential for that authority?
551 # @param cred credential string specifying rights of the caller
553 # @return list of record dictionaries
554 def list(self, cred):
555 self.decode_authentication(cred, "list")
557 auth_name = self.object_gid.get_hrn()
558 table = self.get_auth_table(auth_name)
560 records = table.list()
563 for record in records:
565 self.fill_record_info(record)
566 good_records.append(record)
567 except PlanetLabRecordDoesNotExist:
568 # silently drop the ones that are missing in PL.
569 # is this the right thing to do?
570 report.error("ignoring geni record " + record.get_name() + " because pl record does not exist")
574 for record in good_records:
575 dicts.append(record.as_dict())
582 # Resolve a record. This is an internal version of the Resolve API call
583 # and returns records in record object format rather than dictionaries
584 # that may be sent over XMLRPC.
586 # @param type type of record to resolve (user | sa | ma | slice | node)
587 # @param name human readable name of object
588 # @param must_exist if True, throw an exception if no records are found
590 # @return a list of record objects, or an empty list []
592 def resolve_raw(self, type, name, must_exist=True):
593 auth_name = get_authority(name)
595 table = self.get_auth_table(auth_name)
597 records = table.resolve(type, name)
599 if (not records) and must_exist:
600 raise RecordNotFound(name)
603 for record in records:
605 self.fill_record_info(record)
606 good_records.append(record)
607 except PlanetLabRecordDoesNotExist:
608 # silently drop the ones that are missing in PL.
609 # is this the right thing to do?
610 report.error("ignoring geni record " + record.get_name() + " because pl record does not exist")
618 # This is a wrapper around resolve_raw that converts records objects into
619 # dictionaries before returning them to the user.
621 # @param cred credential string authorizing the caller
622 # @param name human readable name to resolve
624 # @return a list of record dictionaries, or an empty list
626 def resolve(self, cred, name):
627 self.decode_authentication(cred, "resolve")
629 records = self.resolve_raw("*", name)
631 for record in records:
632 dicts.append(record.as_dict())
639 # Retrieve the GID for an object. This function looks up a record in the
640 # registry and returns the GID of the record if it exists.
641 # TODO: Is this function needed? It's a shortcut for Resolve()
643 # @param name hrn to look up
645 # @return the string representation of a GID object
647 def get_gid(self, name):
648 self.verify_object_belongs_to_me(name)
649 records = self.resolve_raw("*", name)
651 for record in records:
652 gid = record.get_gid()
653 gid_string_list.append(gid.save_to_string(save_parents=True))
654 return gid_string_list
657 # Determine tje rights that an object should have. The rights are entirely
658 # dependent on the type of the object. For example, users automatically
659 # get "refresh", "resolve", and "info".
661 # @param type the type of the object (user | sa | ma | slice | node)
662 # @param name human readable name of the object (not used at this time)
664 # @return RightList object containing rights
666 def determine_rights(self, type, name):
669 # rights seem to be somewhat redundant with the type of the credential.
670 # For example, a "sa" credential implies the authority right, because
671 # a sa credential cannot be issued to a user who is not an owner of
682 elif type == "slice":
687 elif type == "component":
693 # GENI API: Get_self_credential
695 # Get_self_credential a degenerate version of get_credential used by a
696 # client to get his initial credential when he doesn't have one. This is
697 # the same as get_credential(..., cred=None,...).
699 # The registry ensures that the client is the principal that is named by
700 # (type, name) by comparing the public key in the record's GID to the
701 # private key used to encrypt the client-side of the HTTPS connection. Thus
702 # it is impossible for one principal to retrieve another principal's
703 # credential without having the appropriate private key.
705 # @param type type of object (user | slice | sa | ma | node
706 # @param name human readable name of object
708 # @return the string representation of a credential object
710 def get_self_credential(self, type, name):
711 self.verify_object_belongs_to_me(name)
713 auth_hrn = get_authority(name)
714 auth_info = self.get_auth_info(auth_hrn)
716 # find a record that matches
717 records = self.resolve_raw(type, name, must_exist=True)
720 gid = record.get_gid_object()
721 peer_cert = self.server.peer_cert
722 if not peer_cert.is_pubkey(gid.get_pubkey()):
723 raise ConnectionKeyGIDMismatch(gid.get_subject())
725 # create the credential
726 gid = record.get_gid_object()
727 cred = Credential(subject = gid.get_subject())
728 cred.set_gid_caller(gid)
729 cred.set_gid_object(gid)
730 cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
731 cred.set_pubkey(gid.get_pubkey())
733 rl = self.determine_rights(type, name)
734 cred.set_privileges(rl)
736 cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn))
741 return cred.save_to_string(save_parents=True)
744 # GENI API: Get_credential
746 # Retrieve a credential for an object.
748 # If cred==None, then the behavior reverts to get_self_credential()
750 # @param cred credential object specifying rights of the caller
751 # @param type type of object (user | slice | sa | ma | node)
752 # @param name human readable name of object
754 # @return the string representation of a credental object
756 def get_credential(self, cred, type, name):
758 return get_self_credential(self, type, name)
760 self.decode_authentication(cred, "getcredential")
762 self.verify_object_belongs_to_me(name)
764 auth_hrn = get_authority(name)
765 auth_info = self.get_auth_info(auth_hrn)
767 records = self.resolve_raw(type, name, must_exist=True)
770 # TODO: Check permission that self.client_cred can access the object
772 object_gid = record.get_gid_object()
773 new_cred = Credential(subject = object_gid.get_subject())
774 new_cred.set_gid_caller(self.client_gid)
775 new_cred.set_gid_object(object_gid)
776 new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
777 new_cred.set_pubkey(object_gid.get_pubkey())
779 rl = self.determine_rights(type, name)
780 new_cred.set_privileges(rl)
782 new_cred.set_parent(self.hierarchy.get_auth_cred(auth_hrn))
787 return new_cred.save_to_string(save_parents=True)
790 # GENI_API: Create_gid
792 # Create a new GID. For MAs and SAs that are physically located on the
793 # registry, this allows a owner/operator/PI to create a new GID and have it
794 # signed by his respective authority.
796 # @param cred credential of caller
797 # @param name hrn for new GID
798 # @param uuid unique identifier for new GID
799 # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
801 # @return the string representation of a GID object
803 def create_gid(self, cred, name, uuid, pubkey_str):
804 self.decode_authentication(cred, "getcredential")
806 self.verify_object_belongs_to_me(name)
808 self.verify_object_permission(name)
814 pkey.load_pubkey_from_string(pubkey_str)
815 gid = self.hierarchy.create_gid(name, uuid, pkey)
817 return gid.save_to_string(save_parents=True)