2 # Registry is a GeniServer that implements the Registry interface
9 from util.hierarchy import Hierarchy
10 from util.trustedroot import TrustedRootList
11 from util.cert import Keypair, Certificate
12 from util.gid import GID
13 from util.geniserver import GeniServer
14 from util.record import GeniRecord
15 from util.genitable import GeniTable
16 from util.geniticket import Ticket
17 from util.excep import *
18 from util.misc import *
21 # Convert geni fields to PLC fields for use when registering up updating
22 # registry record in the PLC database
24 # @param type type of record (user, slice, ...)
25 # @param hrn human readable name
26 # @param geni_fields dictionary of geni fields
27 # @param pl_fields dictionary of PLC fields (output)
29 def geni_fields_to_pl_fields(type, hrn, geni_fields, pl_fields):
31 if not "email" in pl_fields:
32 if not "email" in geni_fields:
33 raise MissingGeniInfo("email")
34 pl_fields["email"] = geni_fields["email"]
36 if not "first_name" in pl_fields:
37 pl_fields["first_name"] = "geni"
39 if not "last_name" in pl_fields:
40 pl_fields["last_name"] = hrn
43 if not "instantiation" in pl_fields:
44 pl_fields["instantiation"] = "delegated" # "plc-instantiated"
45 if not "name" in pl_fields:
46 pl_fields["name"] = hrn_to_pl_slicename(hrn)
47 if not "max_nodes" in pl_fields:
48 pl_fields["max_nodes"] = 10
51 if not "hostname" in pl_fields:
52 if not "dns" in geni_fields:
53 raise MissingGeniInfo("dns")
54 pl_fields["hostname"] = geni_fields["dns"]
56 if not "model" in pl_fields:
57 pl_fields["model"] = "geni"
60 pl_fields["login_base"] = hrn_to_pl_login_base(hrn)
62 if not "name" in pl_fields:
63 pl_fields["name"] = hrn
65 if not "abbreviated_name" in pl_fields:
66 pl_fields["abbreviated_name"] = hrn
68 if not "enabled" in pl_fields:
69 pl_fields["enabled"] = True
71 if not "is_public" in pl_fields:
72 pl_fields["is_public"] = True
75 # Registry is a GeniServer that serves registry and slice operations at PLC.
77 class Registry(GeniServer):
79 # Create a new registry object.
81 # @param ip the ip address to listen on
82 # @param port the port to listen on
83 # @param key_file private key filename of registry
84 # @param cert_file certificate filename containing public key (could be a GID file)
86 def __init__(self, ip, port, key_file, cert_file):
87 GeniServer.__init__(self, ip, port, key_file, cert_file)
89 # get PL account settings from config module
90 self.pl_auth = get_pl_auth()
92 # connect to planetlab
93 if "Url" in self.pl_auth:
94 self.connect_remote_shell()
96 self.connect_local_shell()
99 # Connect to a remote shell via XMLRPC
101 def connect_remote_shell(self):
103 self.shell = remoteshell.RemoteShell()
106 # Connect to a local shell via local API functions
108 def connect_local_shell(self):
110 self.shell = PLC.Shell.Shell(globals = globals())
113 # Register the server RPCs for the registry
115 def register_functions(self):
116 GeniServer.register_functions(self)
118 self.server.register_function(self.create_gid)
119 self.server.register_function(self.get_self_credential)
120 self.server.register_function(self.get_credential)
121 self.server.register_function(self.get_gid)
122 self.server.register_function(self.register)
123 self.server.register_function(self.remove)
124 self.server.register_function(self.update)
125 self.server.register_function(self.list)
126 self.server.register_function(self.resolve)
129 # Given an authority name, return the information for that authority. This
130 # is basically a stub that calls the hierarchy module.
132 # @param auth_hrn human readable name of authority
134 def get_auth_info(self, auth_hrn):
135 return AuthHierarchy.get_auth_info(auth_hrn)
138 # Given an authority name, return the database table for that authority. If
139 # the database table does not exist, then one will be automatically
142 # @param auth_name human readable name of authority
144 def get_auth_table(self, auth_name):
145 auth_info = self.get_auth_info(auth_name)
147 table = GeniTable(hrn=auth_name,
148 cninfo=auth_info.get_dbinfo())
150 # if the table doesn't exist, then it means we haven't put any records
151 # into this authority yet.
153 if not table.exists():
154 report.trace("Registry: creating table for authority " + auth_name)
160 # Verify that an authority belongs to this registry. This is basically left
161 # up to the implementation of the hierarchy module. If the specified name
162 # does not belong to this registry, an exception is thrown indicating the
163 # caller should contact someone else.
165 # @param auth_name human readable name of authority
167 def verify_auth_belongs_to_me(self, name):
168 # get_auth_info will throw an exception if the authority does not
170 self.get_auth_info(name)
173 # Verify that an object belongs to this registry. By extension, this implies
174 # that the authority that owns the object belongs to this registry. If the
175 # object does not belong to this registry, then an exception is thrown.
177 # @param name human readable name of object
179 def verify_object_belongs_to_me(self, name):
180 auth_name = get_authority(name)
182 # the root authority belongs to the registry by default?
183 # TODO: is this true?
185 self.verify_auth_belongs_to_me(auth_name)
188 # Verify that the object_gid that was specified in the credential allows
189 # permission to the object 'name'. This is done by a simple prefix test.
190 # For example, an object_gid for planetlab.us.arizona would match the
191 # objects planetlab.us.arizona.slice1 and planetlab.us.arizona.
193 # @param name human readable name to test
195 def verify_object_permission(self, name):
196 object_hrn = self.object_gid.get_hrn()
197 if object_hrn == name:
199 if name.startswith(object_hrn + "."):
201 raise PermissionError(name)
204 # Fill in the planetlab-specific fields of a Geni record. This involves
205 # calling the appropriate PLC methods to retrieve the database record for
208 # PLC data is filled into the pl_info field of the record.
210 # @param record record to fill in fields (in/out param)
212 def fill_record_pl_info(self, record):
213 type = record.get_type()
214 pointer = record.get_pointer()
216 # records with pointer==-1 do not have plc info associated with them.
217 # for example, the top level authority records which are
218 # authorities, but not PL "sites"
220 record.set_pl_info({})
223 if (type == "sa") or (type == "ma"):
224 pl_res = self.shell.GetSites(self.pl_auth, [pointer])
225 elif (type == "slice"):
226 pl_res = self.shell.GetSlices(self.pl_auth, [pointer])
227 elif (type == "user"):
228 pl_res = self.shell.GetPersons(self.pl_auth, [pointer])
229 elif (type == "node"):
230 pl_res = self.shell.GetNodes(self.pl_auth, [pointer])
232 raise UnknownGeniType(type)
235 # the planetlab record no longer exists
236 # TODO: delete the geni record ?
237 raise PlanetLabRecordDoesNotExist(record.get_name())
239 record.set_pl_info(pl_res[0])
242 # Look up user records given PLC user-ids. This is used as part of the
243 # process for reverse-mapping PLC records into Geni records.
245 # @param auth_table database table for the authority that holds the user records
246 # @param user_id_list list of user ids
247 # @param role either "*" or a string describing the role to look for ("pi", "user", ...)
249 # TODO: This function currently only searches one authority because it would
250 # be inefficient to brute-force search all authorities for a user id. The
251 # solution would likely be to implement a reverse mapping of user-id to
254 def lookup_users(self, auth_table, user_id_list, role="*"):
256 for person_id in user_id_list:
257 user_records = auth_table.find("user", person_id, "pointer")
258 for user_record in user_records:
259 self.fill_record_info(user_record)
261 user_roles = user_record.get_pl_info().get("roles")
262 if (role=="*") or (role in user_roles):
263 record_list.append(user_record.get_name())
267 # Fill in the geni-specific fields of the record.
269 # Note: It is assumed the fill_record_pl_info() has already been performed
272 def fill_record_geni_info(self, record):
274 type = record.get_type()
276 if (type == "slice"):
277 auth_table = self.get_auth_table(get_authority(record.get_name()))
278 person_ids = record.pl_info.get("person_ids", [])
279 researchers = self.lookup_users(auth_table, person_ids)
280 geni_info['researcher'] = researchers
283 auth_table = self.get_auth_table(record.get_name())
284 person_ids = record.pl_info.get("person_ids", [])
285 pis = self.lookup_users(auth_table, person_ids, "pi")
286 geni_info['pi'] = pis
287 # TODO: OrganizationName
290 auth_table = self.get_auth_table(record.get_name())
291 person_ids = record.pl_info.get("person_ids", [])
292 operators = self.lookup_users(auth_table, person_ids, "tech")
293 geni_info['operator'] = operators
294 # TODO: OrganizationName
296 auth_table = self.get_auth_table(record.get_name())
297 person_ids = record.pl_info.get("person_ids", [])
298 owners = self.lookup_users(auth_table, person_ids, "admin")
299 geni_info['owner'] = owners
301 elif (type == "node"):
302 geni_info['dns'] = record.pl_info.get("hostname", "")
303 # TODO: URI, LatLong, IP, DNS
305 elif (type == "user"):
306 geni_info['email'] = record.pl_info.get("email", "")
307 # TODO: PostalAddress, Phone
309 record.set_geni_info(geni_info)
312 # Given a Geni record, fill in the PLC-specific and Geni-specific fields
315 def fill_record_info(self, record):
316 self.fill_record_pl_info(record)
317 self.fill_record_geni_info(record)
322 # Register an object with the registry. In addition to being stored in the
323 # Geni database, the appropriate records will also be created in the
326 # @param cred credential string
327 # @param record_dict dictionary containing record fields
329 def register(self, cred, record_dict):
330 self.decode_authentication(cred, "register")
332 record = GeniRecord(dict = record_dict)
333 type = record.get_type()
334 name = record.get_name()
336 auth_name = get_authority(name)
337 self.verify_object_permission(auth_name)
338 auth_info = self.get_auth_info(auth_name)
339 table = self.get_auth_table(auth_name)
343 # check if record already exists
344 existing_records = table.resolve(type, name)
346 raise ExistingRecord(name)
348 if (type == "sa") or (type=="ma"):
350 if not AuthHierarchy.auth_exists(name):
351 AuthHierarchy.create_auth(name)
353 # authorities are special since they are managed by the registry
354 # rather than by the caller. We create our own GID for the
355 # authority rather than relying on the caller to supply one.
357 # get the GID from the newly created authority
358 child_auth_info = self.get_auth_info(name)
359 gid = auth_info.get_gid_object()
360 record.set_gid(gid.save_to_string(save_parents=True))
362 geni_fields = record.get_geni_info()
363 site_fields = record.get_pl_info()
365 # if registering a sa, see if a ma already exists
366 # if registering a ma, see if a sa already exists
368 other_rec = table.resolve("ma", record.get_name())
370 other_rec = table.resolve("sa", record.get_name())
373 print "linking ma and sa to the same plc site"
374 pointer = other_rec[0].get_pointer()
376 geni_fields_to_pl_fields(type, name, geni_fields, site_fields)
377 print "adding site with fields", site_fields
378 pointer = self.shell.AddSite(self.pl_auth, site_fields)
380 record.set_pointer(pointer)
382 elif (type == "slice"):
383 geni_fields = record.get_geni_info()
384 slice_fields = record.get_pl_info()
386 geni_fields_to_pl_fields(type, name, geni_fields, slice_fields)
388 pointer = self.shell.AddSlice(self.pl_auth, slice_fields)
389 record.set_pointer(pointer)
391 elif (type == "user"):
392 geni_fields = record.get_geni_info()
393 user_fields = record.get_pl_info()
395 geni_fields_to_pl_fields(type, name, geni_fields, user_fields)
397 pointer = self.shell.AddPerson(self.pl_auth, user_fields)
398 record.set_pointer(pointer)
400 elif (type == "node"):
401 geni_fields = record.get_geni_info()
402 node_fields = record.get_pl_info()
404 geni_fields_to_pl_fields(type, name, geni_fields, node_fields)
406 login_base = hrn_to_pl_login_base(auth_name)
408 print "calling addnode with", login_base, node_fields
409 pointer = self.shell.AddNode(self.pl_auth, login_base, node_fields)
410 record.set_pointer(pointer)
413 raise UnknownGeniType(type)
417 return record.get_gid_object().save_to_string(save_parents=True)
422 # Remove an object from the registry. If the object represents a PLC object,
423 # then the PLC records will also be removed.
425 # @param cred credential string
426 # @param record_dict dictionary containing record fields. The only relevant
427 # fields of the record are 'name' and 'type', which are used to lookup
428 # the current copy of the record in the Geni database, to make sure
429 # that the appopriate record is removed.
431 def remove(self, cred, record_dict):
432 self.decode_authentication(cred, "remove")
434 record = GeniRecord(dict = record_dict)
435 type = record.get_type()
437 self.verify_object_permission(record.get_name())
439 auth_name = get_authority(record.get_name())
440 table = self.get_auth_table(auth_name)
442 # let's not trust that the caller has a well-formed record (a forged
443 # pointer field could be a disaster), so look it up ourselves
444 record_list = table.resolve(type, record.get_name())
446 raise RecordNotFound(name)
447 record = record_list[0]
451 self.shell.DeletePerson(self.pl_auth, record.get_pointer())
452 elif type == "slice":
453 self.shell.DeleteSlice(self.pl_auth, record.get_pointer())
455 self.shell.DeleteNode(self.pl_auth, record.get_pointer())
456 elif (type == "sa") or (type == "ma"):
458 other_rec = table.resolve("ma", record.get_name())
460 other_rec = table.resolve("sa", record.get_name())
463 # sa and ma both map to a site, so if we are deleting one
464 # but the other still exists, then do not delete the site
465 print "not removing site", record.get_name(), "because either sa or ma still exists"
468 print "removing site", record.get_name()
469 self.shell.DeleteSite(self.pl_auth, record.get_pointer())
471 raise UnknownGeniType(type)
480 # Update an object in the registry. Currently, this only updates the
481 # PLC information associated with the record. The Geni fields (name, type,
484 # The record is expected to have the pl_info field filled in with the data
485 # that should be updated.
487 # TODO: The geni_info member of the record should be parsed and the pl_info
488 # adjusted as necessary (add/remove users from a slice, etc)
490 # @param cred credential string specifying rights of the caller
491 # @param record a record dictionary to be updated
493 def update(self, cred, record_dict):
494 self.decode_authentication(cred, "update")
496 record = GeniRecord(dict = record_dict)
497 type = record.get_type()
499 self.verify_object_permission(record.get_name())
501 auth_name = get_authority(record.get_name())
502 table = self.get_auth_table(auth_name)
504 # make sure the record exists
505 existing_record_list = table.resolve(type, record.get_name())
506 if not existing_record_list:
507 raise RecordNotFound(record.get_name())
509 existing_record = existing_record_list[0]
510 pointer = existing_record.get_pointer()
512 # update the PLC information that was specified with the record
514 if (type == "sa") or (type == "ma"):
515 self.shell.UpdateSite(self.pl_auth, pointer, record.get_pl_info())
517 elif type == "slice":
518 self.shell.UpdateSlice(self.pl_auth, pointer, record.get_pl_info())
521 # SMBAKER: UpdatePerson only allows a limited set of fields to be
522 # updated. Ideally we should have a more generic way of doing
523 # this. I copied the field names from UpdatePerson.py...
525 all_fields = record.get_pl_info()
526 for key in all_fields.keys():
527 if key in ['first_name', 'last_name', 'title', 'email',
528 'password', 'phone', 'url', 'bio', 'accepted_aup',
\r
530 update_fields[key] = all_fields[key]
531 self.shell.UpdatePerson(self.pl_auth, pointer, update_fields)
534 self.shell.UpdateNode(self.pl_auth, pointer, record.get_pl_info())
537 raise UnknownGeniType(type)
540 # List the records in an authority. The objectGID in the supplied credential
541 # should name the authority that will be listed.
543 # TODO: List doesn't take an hrn and uses the hrn contained in the
544 # objectGid of the credential. Does this mean the only way to list an
545 # authority is by having a credential for that authority?
547 # @param cred credential string specifying rights of the caller
549 # @return list of record dictionaries
550 def list(self, cred):
551 self.decode_authentication(cred, "list")
553 auth_name = self.object_gid.get_hrn()
554 table = self.get_auth_table(auth_name)
556 records = table.list()
559 for record in records:
561 self.fill_record_info(record)
562 good_records.append(record)
563 except PlanetLabRecordDoesNotExist:
564 # silently drop the ones that are missing in PL.
565 # is this the right thing to do?
566 report.error("ignoring geni record " + record.get_name() + " because pl record does not exist")
570 for record in good_records:
571 dicts.append(record.as_dict())
578 # Resolve a record. This is an internal version of the Resolve API call
579 # and returns records in record object format rather than dictionaries
580 # that may be sent over XMLRPC.
582 # @param type type of record to resolve (user | sa | ma | slice | node)
583 # @param name human readable name of object
584 # @param must_exist if True, throw an exception if no records are found
586 # @return a list of record objects, or an empty list []
588 def resolve_raw(self, type, name, must_exist=True):
589 auth_name = get_authority(name)
591 table = self.get_auth_table(auth_name)
593 records = table.resolve(type, name)
595 if (not records) and must_exist:
596 raise RecordNotFound(name)
599 for record in records:
601 self.fill_record_info(record)
602 good_records.append(record)
603 except PlanetLabRecordDoesNotExist:
604 # silently drop the ones that are missing in PL.
605 # is this the right thing to do?
606 report.error("ignoring geni record " + record.get_name() + " because pl record does not exist")
614 # This is a wrapper around resolve_raw that converts records objects into
615 # dictionaries before returning them to the user.
617 # @param cred credential string authorizing the caller
618 # @param name human readable name to resolve
620 # @return a list of record dictionaries, or an empty list
622 def resolve(self, cred, name):
623 self.decode_authentication(cred, "resolve")
625 records = self.resolve_raw("*", name)
627 for record in records:
628 dicts.append(record.as_dict())
635 # Retrieve the GID for an object. This function looks up a record in the
636 # registry and returns the GID of the record if it exists.
637 # TODO: Is this function needed? It's a shortcut for Resolve()
639 # @param name hrn to look up
641 # @return the string representation of a GID object
643 def get_gid(self, name):
644 self.verify_object_belongs_to_me(name)
645 records = self.resolve_raw("*", name)
647 for record in records:
648 gid = record.get_gid()
649 gid_string_list.append(gid.save_to_string(save_parents=True))
650 return gid_string_list
653 # Determine tje rights that an object should have. The rights are entirely
654 # dependent on the type of the object. For example, users automatically
655 # get "refresh", "resolve", and "info".
657 # @param type the type of the object (user | sa | ma | slice | node)
658 # @param name human readable name of the object (not used at this time)
660 # @return RightList object containing rights
662 def determine_rights(self, type, name):
665 # rights seem to be somewhat redundant with the type of the credential.
666 # For example, a "sa" credential implies the authority right, because
667 # a sa credential cannot be issued to a user who is not an owner of
678 elif type == "slice":
683 elif type == "component":
689 # GENI API: Get_self_credential
691 # Get_self_credential a degenerate version of get_credential used by a
692 # client to get his initial credential when he doesn't have one. This is
693 # the same as get_credential(..., cred=None,...).
695 # The registry ensures that the client is the principal that is named by
696 # (type, name) by comparing the public key in the record's GID to the
697 # private key used to encrypt the client-side of the HTTPS connection. Thus
698 # it is impossible for one principal to retrieve another principal's
699 # credential without having the appropriate private key.
701 # @param type type of object (user | slice | sa | ma | node
702 # @param name human readable name of object
704 # @return the string representation of a credential object
706 def get_self_credential(self, type, name):
707 self.verify_object_belongs_to_me(name)
709 auth_hrn = get_authority(name)
710 auth_info = self.get_auth_info(auth_hrn)
712 # find a record that matches
713 records = self.resolve_raw(type, name, must_exist=True)
716 gid = record.get_gid_object()
717 peer_cert = self.server.peer_cert
718 if not peer_cert.is_pubkey(gid.get_pubkey()):
719 raise ConnectionKeyGIDMismatch(gid.get_subject())
721 # create the credential
722 gid = record.get_gid_object()
723 cred = Credential(subject = gid.get_subject())
724 cred.set_gid_caller(gid)
725 cred.set_gid_object(gid)
726 cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
727 cred.set_pubkey(gid.get_pubkey())
729 rl = self.determine_rights(type, name)
730 cred.set_privileges(rl)
732 cred.set_parent(AuthHierarchy.get_auth_cred(auth_hrn))
737 return cred.save_to_string(save_parents=True)
740 # GENI API: Get_credential
742 # Retrieve a credential for an object.
744 # If cred==None, then the behavior reverts to get_self_credential()
746 # @param cred credential object specifying rights of the caller
747 # @param type type of object (user | slice | sa | ma | node)
748 # @param name human readable name of object
750 # @return the string representation of a credental object
752 def get_credential(self, cred, type, name):
754 return get_self_credential(self, type, name)
756 self.decode_authentication(cred, "getcredential")
758 self.verify_object_belongs_to_me(name)
760 auth_hrn = get_authority(name)
761 auth_info = self.get_auth_info(auth_hrn)
763 records = self.resolve_raw(type, name, must_exist=True)
766 # TODO: Check permission that self.client_cred can access the object
768 object_gid = record.get_gid_object()
769 new_cred = Credential(subject = object_gid.get_subject())
770 new_cred.set_gid_caller(self.client_gid)
771 new_cred.set_gid_object(object_gid)
772 new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn)
773 new_cred.set_pubkey(object_gid.get_pubkey())
775 rl = self.determine_rights(type, name)
776 new_cred.set_privileges(rl)
778 new_cred.set_parent(AuthHierarchy.get_auth_cred(auth_hrn))
783 return new_cred.save_to_string(save_parents=True)
786 # GENI_API: Create_gid
788 # Create a new GID. For MAs and SAs that are physically located on the
789 # registry, this allows a owner/operator/PI to create a new GID and have it
790 # signed by his respective authority.
792 # @param cred credential of caller
793 # @param name hrn for new GID
794 # @param uuid unique identifier for new GID
795 # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
797 # @return the string representation of a GID object
799 def create_gid(self, cred, name, uuid, pubkey_str):
800 self.decode_authentication(cred, "getcredential")
802 self.verify_object_belongs_to_me(name)
804 self.verify_object_permission(name)
810 pkey.load_pubkey_from_string(pubkey_str)
811 gid = AuthHierarchy.create_gid(name, uuid, pkey)
813 return gid.save_to_string(save_parents=True)