From: Tony Mack Date: Wed, 2 Jun 2010 20:42:30 +0000 (+0000) Subject: merging with geni-api branch X-Git-Tag: sfa-1.0-0~191 X-Git-Url: http://git.onelab.eu/?p=sfa.git;a=commitdiff_plain;h=952322d76247f8991f3c2688ed7e1f5a22ca4572 merging with geni-api branch --- diff --git a/sfa/client/sfi.py b/sfa/client/sfi.py index 9e18bd75..8592ec1a 100755 --- a/sfa/client/sfi.py +++ b/sfa/client/sfi.py @@ -8,6 +8,7 @@ import os, os.path import tempfile import traceback import socket +import random from lxml import etree from StringIO import StringIO from types import StringTypes, ListType @@ -20,11 +21,12 @@ from sfa.util.namespace import * from sfa.util.xmlrpcprotocol import ServerException import sfa.util.xmlrpcprotocol as xmlrpcprotocol from sfa.util.config import Config +import zlib # utility methods here # display methods -def display_rspec(rspec, format = 'rspec'): +def display_rspec(rspec, format='rspec'): if format in ['dns']: tree = etree.parse(StringIO(rspec)) root = tree.getroot() @@ -46,12 +48,12 @@ def display_list(results): print result -def display_records(recordList, dump = False): +def display_records(recordList, dump=False): ''' Print all fields in the record''' for record in recordList: display_record(record, dump) -def display_record(record, dump = False): +def display_record(record, dump=False): if dump: record.dump() else: @@ -81,7 +83,7 @@ def save_rspec_to_file(rspec, filename): def save_records_to_file(filename, recordList): index = 0 for record in recordList: - if index>0: + if index > 0: save_record_to_file(filename + "." + str(index), record) else: save_record_to_file(filename, record) @@ -89,15 +91,15 @@ def save_records_to_file(filename, recordList): def save_record_to_file(filename, record): if record['type'] in ['user']: - record = UserRecord(dict = record) + record = UserRecord(dict=record) elif record['type'] in ['slice']: - record = SliceRecord(dict = record) + record = SliceRecord(dict=record) elif record['type'] in ['node']: - record = NodeRecord(dict = record) + record = NodeRecord(dict=record) elif record['type'] in ['authority', 'ma', 'sa']: - record = AuthorityRecord(dict = record) + record = AuthorityRecord(dict=record) else: - record = SfaRecord(dict = record) + record = SfaRecord(dict=record) str = record.save_to_string() file(filename, "w").write(str) return @@ -112,7 +114,8 @@ def load_record_from_file(filename): class Sfi: - + + geni_am = None slicemgr = None registry = None user = None @@ -120,7 +123,7 @@ class Sfi: options = None hashrequest = False - def create_cmd_parser(self,command, additional_cmdargs = None): + def create_cmd_parser(self, command, additional_cmdargs=None): cmdargs = {"gid": "", "list": "name", "show": "name", @@ -134,12 +137,20 @@ class Sfi: "create": "name rspec", "get_trusted_certs": "cred", "get_ticket": "name rspec", - "redeem_ticket": "ticket", + "redeem_ticket": "ticket", "delete": "name", "reset": "name", "start": "name", "stop": "name", - "delegate": "name" + "delegate": "name", + "GetVersion": "name", + "ListResources": "name", + "CreateSliver": "name", + "get_geni_aggregates": "name", + "DeleteSliver": "name", + "SliverStatus": "name", + "RenewSliver": "name", + "Shutdown": "name" } if additional_cmdargs: @@ -149,7 +160,7 @@ class Sfi: print "Invalid command\n" print "Commands: ", for key in cmdargs.keys(): - print key+",", + print key + ",", print "" sys.exit(2) @@ -157,24 +168,24 @@ class Sfi: % (command, cmdargs[command])) if command in ("resources"): - parser.add_option("-f", "--format", dest="format",type="choice", - help="display format ([xml]|dns|ip)",default="xml", - choices=("xml","dns","ip")) + parser.add_option("-f", "--format", dest="format", type="choice", + help="display format ([xml]|dns|ip)", default="xml", + choices=("xml", "dns", "ip")) parser.add_option("-a", "--aggregate", dest="aggregate", default=None, help="aggregate hrn") if command in ("create", "get_ticket"): - parser.add_option("-a", "--aggregate", dest="aggregate",default=None, + parser.add_option("-a", "--aggregate", dest="aggregate", default=None, help="aggregate hrn") if command in ("start", "stop", "reset", "delete", "slices"): - parser.add_option("-c", "--component", dest="component",default=None, + parser.add_option("-c", "--component", dest="component", default=None, help="component hrn") if command in ("list", "show", "remove"): - parser.add_option("-t", "--type", dest="type",type="choice", + parser.add_option("-t", "--type", dest="type", type="choice", help="type filter ([all]|user|slice|sa|ma|node|aggregate)", - choices=("all","user","slice","sa","ma","node","aggregate"), + choices=("all", "user", "slice", "sa", "ma", "node", "aggregate"), default="all") if command in ("resources", "show", "list"): @@ -183,8 +194,8 @@ class Sfi: if command in ("show", "list"): parser.add_option("-f", "--format", dest="format", type="choice", - help="display format ([text]|xml)",default="text", - choices=("text","xml")) + help="display format ([text]|xml)", default="text", + choices=("text", "xml")) if command in ("delegate"): parser.add_option("-u", "--user", @@ -192,6 +203,7 @@ class Sfi: help="delegate user credential") parser.add_option("-s", "--slice", dest="delegate_slice", help="delegate slice credential", metavar="HRN", default=None) + return parser @@ -200,14 +212,16 @@ class Sfi: # Generate command line parser parser = OptionParser(usage="sfi [options] command [command_options] [command_args]", description="Commands: gid,list,show,remove,add,update,nodes,slices,resources,create,delete,start,stop,reset") + parser.add_option("-g", "--geni_am", dest="geni_am", + help="geni am", metavar="URL", default=None) parser.add_option("-r", "--registry", dest="registry", help="root registry", metavar="URL", default=None) parser.add_option("-s", "--slicemgr", dest="sm", help="slice manager", metavar="URL", default=None) - default_sfi_dir=os.path.expanduser("~/.sfi/") + default_sfi_dir = os.path.expanduser("~/.sfi/") parser.add_option("-d", "--dir", dest="sfi_dir", help="config & working directory - default is " + default_sfi_dir, - metavar="PATH", default = default_sfi_dir) + metavar="PATH", default=default_sfi_dir) parser.add_option("-u", "--user", dest="user", help="user name", metavar="HRN", default=None) parser.add_option("-a", "--auth", dest="auth", @@ -237,7 +251,7 @@ class Sfi: try: config = Config (config_file) except: - print "Failed to read configuration file",config_file + print "Failed to read configuration file", config_file print "Make sure to remove the export clauses and to add quotes" if not self.options.verbose: print "Re-run with -v for more details" @@ -245,42 +259,48 @@ class Sfi: traceback.print_exc() sys.exit(1) - errors=0 + errors = 0 # Set SliceMgr URL if (self.options.sm is not None): sm_url = self.options.sm - elif hasattr(config,"SFI_SM"): + elif hasattr(config, "SFI_SM"): sm_url = config.SFI_SM else: - print "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s"%config_file - errors +=1 + print "You need to set e.g. SFI_SM='http://your.slicemanager.url:12347/' in %s" % config_file + errors += 1 # Set Registry URL if (self.options.registry is not None): reg_url = self.options.registry - elif hasattr(config,"SFI_REGISTRY"): + elif hasattr(config, "SFI_REGISTRY"): reg_url = config.SFI_REGISTRY else: - print "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s"%config_file - errors +=1 - + print "You need to set e.g. SFI_REGISTRY='http://your.registry.url:12345/' in %s" % config_file + errors += 1 + + + if (self.options.geni_am is not None): + geni_am_url = self.options.geni_am + elif hasattr(config, "SFI_GENI_AM"): + geni_am_url = config.SFI_GENI_AM + # Set user HRN if (self.options.user is not None): self.user = self.options.user - elif hasattr(config,"SFI_USER"): + elif hasattr(config, "SFI_USER"): self.user = config.SFI_USER else: - print "You need to set e.g. SFI_USER='plc.princeton.username' in %s"%config_file - errors +=1 + print "You need to set e.g. SFI_USER='plc.princeton.username' in %s" % config_file + errors += 1 # Set authority HRN if (self.options.auth is not None): self.authority = self.options.auth - elif hasattr(config,"SFI_AUTH"): + elif hasattr(config, "SFI_AUTH"): self.authority = config.SFI_AUTH else: - print "You need to set e.g. SFI_AUTH='plc.princeton' in %s"%config_file - errors +=1 + print "You need to set e.g. SFI_AUTH='plc.princeton' in %s" % config_file + errors += 1 if errors: sys.exit(1) @@ -298,7 +318,9 @@ class Sfi: self.cert = Certificate(filename=cert_file) # Establish connection to server(s) self.registry = xmlrpcprotocol.get_server(reg_url, key_file, cert_file, self.options.debug) - self.slicemgr = xmlrpcprotocol.get_server(sm_url, key_file, cert_file, self.options.debug) + self.slicemgr = xmlrpcprotocol.get_server(sm_url, key_file, cert_file, self.options.debug) + self.geni_am = xmlrpcprotocol.get_server(geni_am_url, key_file, cert_file, self.options.debug) + return # @@ -316,7 +338,7 @@ class Sfi: def get_key_file(self): - file=os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey") + file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".pkey") #file = os.path.join(self.options.sfi_dir, get_leaf(self.user) + ".pkey") if (os.path.isfile(file)): return file @@ -325,14 +347,14 @@ class Sfi: sys.exit(-1) return - def get_cert_file(self,key_file): + def get_cert_file(self, key_file): #file = os.path.join(self.options.sfi_dir, get_leaf(self.user) + ".cert") - file=os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert") + file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cert") if (os.path.isfile(file)): return file else: - k = Keypair(filename = key_file) + k = Keypair(filename=key_file) cert = Certificate(subject=self.user) cert.set_pubkey(k) cert.set_issuer(k, self.user) @@ -344,7 +366,7 @@ class Sfi: def get_gid(self): #file = os.path.join(self.options.sfi_dir, get_leaf(self.user) + ".gid") - file=os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".gid") + file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".gid") if (os.path.isfile(file)): gid = GID(filename=file) return gid @@ -359,17 +381,17 @@ class Sfi: def get_user_cred(self): #file = os.path.join(self.options.sfi_dir, get_leaf(self.user) + ".cred") - file=os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred") + file = os.path.join(self.options.sfi_dir, self.user.replace(self.authority + '.', '') + ".cred") if (os.path.isfile(file)): user_cred = Credential(filename=file) return user_cred else: # bootstrap user credential cert_string = self.cert.save_to_string(save_parents=True) - user_name=self.user.replace(self.authority+".", '') + user_name = self.user.replace(self.authority + ".", '') if user_name.count(".") > 0: user_name = user_name.replace(".", '_') - self.user=self.authority + "." + user_name + self.user = self.authority + "." + user_name user_cred = self.registry.get_self_credential(cert_string, "user", self.user) if user_cred: @@ -387,7 +409,7 @@ class Sfi: print "no authority specified. Use -a or set SF_AUTH" sys.exit(-1) - file = os.path.join(self.options.sfi_dir, get_leaf("authority") +".cred") + file = os.path.join(self.options.sfi_dir, get_leaf("authority") + ".cred") if (os.path.isfile(file)): auth_cred = Credential(filename=file) return auth_cred @@ -405,7 +427,7 @@ class Sfi: print "Failed to get authority credential" sys.exit(-1) - def get_slice_cred(self,name): + def get_slice_cred(self, name): file = os.path.join(self.options.sfi_dir, "slice_" + get_leaf(name) + ".cred") if (os.path.isfile(file)): slice_cred = Credential(filename=file) @@ -425,7 +447,7 @@ class Sfi: print "Failed to get slice credential" sys.exit(-1) - def delegate_cred(self,cred, hrn, type = 'authority'): + def delegate_cred(self, cred, hrn, type='authority'): # the gid and hrn of the object we are delegating user_cred = Credential(string=cred) object_gid = user_cred.get_gid_object() @@ -447,23 +469,31 @@ class Sfi: delegee_hrn = delegee_gid.get_hrn() # the key and hrn of the user who will be delegating - user_key = Keypair(filename = self.get_key_file()) + user_key = Keypair(filename=self.get_key_file()) user_hrn = user_cred.get_gid_caller().get_hrn() dcred = Credential(subject=object_hrn + " delegated to " + delegee_hrn) dcred.set_gid_caller(delegee_gid) dcred.set_gid_object(object_gid) dcred.set_privileges(user_cred.get_privileges()) - dcred.set_delegate(True) - dcred.set_pubkey(object_gid.get_pubkey()) - dcred.set_issuer(user_key, user_hrn) + dcred.get_privileges().delegate_all_privileges(True) + + + # Save the issuer's gid to a file + fname = self.options.sfi_dir + os.sep + "gid_%d" % random.randint(0, 999999999) + f = open(fname, "w") + f.write(user_cred.get_gid_caller().save_to_string()) + f.close() + dcred.set_issuer_keys(self.get_key_file(), fname) + os.remove(fname) + dcred.set_parent(user_cred) dcred.encode() dcred.sign() return dcred.save_to_string(save_parents=True) - def get_rspec_file(self,rspec): + def get_rspec_file(self, rspec): if (os.path.isabs(rspec)): file = rspec else: @@ -474,7 +504,7 @@ class Sfi: print "No such rspec file", rspec sys.exit(1) - def get_record_file(self,record): + def get_record_file(self, record): if (os.path.isabs(record)): file = record else: @@ -485,8 +515,8 @@ class Sfi: print "No such registry record file", record sys.exit(1) - def load_publickey_string(self,fn): - f = file(fn,"r") + def load_publickey_string(self, fn): + f = file(fn, "r") key_string = f.read() # if the filename is a private key file, then extract the public key @@ -518,8 +548,8 @@ class Sfi: # Registry-related commands # - def dispatch(self,command, cmd_opts, cmd_args): - getattr(self,command)(cmd_opts, cmd_args) + def dispatch(self, command, cmd_opts, cmd_args): + getattr(self, command)(cmd_opts, cmd_args) def gid(self, opts, args): gid = self.get_gid() @@ -527,7 +557,7 @@ class Sfi: return # list entires in named authority registry - def list(self,opts, args): + def list(self, opts, args): user_cred = self.get_user_cred().save_to_string(save_parents=True) hrn = args[0] try: @@ -548,7 +578,7 @@ class Sfi: return # show named registry record - def show(self,opts, args): + def show(self, opts, args): user_cred = self.get_user_cred().save_to_string(save_parents=True) hrn = args[0] records = self.registry.resolve(user_cred, hrn) @@ -557,16 +587,16 @@ class Sfi: print "No record of type", opts.type for record in records: if record['type'] in ['user']: - record = UserRecord(dict = record) + record = UserRecord(dict=record) elif record['type'] in ['slice']: - record = SliceRecord(dict = record) + record = SliceRecord(dict=record) elif record['type'] in ['node']: - record = NodeRecord(dict = record) + record = NodeRecord(dict=record) elif record['type'] in ['authority', 'ma', 'sa']: - record = AuthorityRecord(dict = record) + record = AuthorityRecord(dict=record) else: - record = SfaRecord(dict = record) - if (opts.format=="text"): + record = SfaRecord(dict=record) + if (opts.format == "text"): record.dump() else: print record.save_to_string() @@ -578,7 +608,7 @@ class Sfi: save_records_to_file(file, records) return - def delegate(self,opts, args): + def delegate(self, opts, args): user_cred = self.get_user_cred() if opts.delegate_user: object_cred = user_cred @@ -592,7 +622,7 @@ class Sfi: object_gid = object_cred.get_gid_object() object_hrn = object_gid.get_hrn() - if not object_cred.get_delegate(): + if not object_cred.get_privileges().get_all_delegate(): print "Error: Object credential", object_hrn, "does not have delegate bit set" return @@ -603,19 +633,20 @@ class Sfi: print "Error: Didn't find a user record for", args[0] return - # the gid of the user who will be delegated too + # the gid of the user who will be delegated to delegee_gid = GID(string=records[0]['gid']) delegee_hrn = delegee_gid.get_hrn() # the key and hrn of the user who will be delegating - user_key = Keypair(filename = self.get_key_file()) + user_key = Keypair(filename=self.get_key_file()) user_hrn = user_cred.get_gid_caller().get_hrn() subject_string = "%s delegated to %s" % (object_hrn, delegee_hrn) dcred = Credential(subject=subject_string) dcred.set_gid_caller(delegee_gid) dcred.set_gid_object(object_gid) + privs = object_cred.get_privileges() dcred.set_privileges(object_cred.get_privileges()) - dcred.set_delegate(True) + dcred.get_privileges().delegate_all_privileges(True) dcred.set_pubkey(object_gid.get_pubkey()) dcred.set_issuer(user_key, user_hrn) dcred.set_parent(object_cred) @@ -629,13 +660,13 @@ class Sfi: dest_fn = os.path_join(self.options.sfi_dir, get_leaf(delegee_hrn) + "_slice_" + get_leaf(object_hrn) + ".cred") - dcred.save_to_file(dest_fn, save_parents = True) + dcred.save_to_file(dest_fn, save_parents=True) print "delegated credential for", object_hrn, "to", delegee_hrn, "and wrote to", dest_fn # removed named registry record # - have to first retrieve the record to be removed - def remove(self,opts, args): + def remove(self, opts, args): auth_cred = self.get_auth_cred().save_to_string(save_parents=True) hrn = args[0] type = opts.type @@ -644,7 +675,7 @@ class Sfi: return self.registry.remove(auth_cred, type, hrn) # add named registry record - def add(self,opts, args): + def add(self, opts, args): auth_cred = self.get_auth_cred().save_to_string(save_parents=True) record_filepath = args[0] rec_file = self.get_record_file(record_filepath) @@ -652,7 +683,7 @@ class Sfi: return self.registry.register(auth_cred, record) # update named registry entry - def update(self,opts, args): + def update(self, opts, args): user_cred = self.get_user_cred() rec_file = self.get_record_file(args[0]) record = load_record_from_file(rec_file) @@ -696,12 +727,27 @@ class Sfi: """ user_cred = self.get_user_cred().save_to_string(save_parents=True) hrn = None - if args: + if args: hrn = args[0] + result = self.registry.get_aggregates(user_cred, hrn) display_list(result) return + def get_geni_aggregates(self, opts, args): + """ + return a list of details about known aggregates + """ + user_cred = self.get_user_cred().save_to_string(save_parents=True) + hrn = None + if args: + hrn = args[0] + + result = self.registry.get_geni_aggregates(user_cred, hrn) + display_list(result) + return + + def registries(self, opts, args): """ return a list of details about known registries @@ -722,7 +768,7 @@ class Sfi: # list available nodes -- use 'resources' w/ no argument instead # list instantiated slices - def slices(self,opts, args): + def slices(self, opts, args): user_cred = self.get_user_cred().save_to_string(save_parents=True) server = self.slicemgr # direct connection to the nodes component manager interface @@ -733,7 +779,7 @@ class Sfi: return # show rspec for named slice - def resources(self,opts, args): + def resources(self, opts, args): user_cred = self.get_user_cred().save_to_string(save_parents=True) server = self.slicemgr if opts.aggregate: @@ -763,12 +809,12 @@ class Sfi: return # created named slice with given rspec - def create(self,opts, args): + def create(self, opts, args): slice_hrn = args[0] user_cred = self.get_user_cred() slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) rspec_file = self.get_rspec_file(args[1]) - rspec=open(rspec_file).read() + rspec = open(rspec_file).read() server = self.slicemgr if opts.aggregate: aggregates = self.registry.get_aggregates(user_cred, opts.aggregate) @@ -785,7 +831,7 @@ class Sfi: user_cred = self.get_user_cred() slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) rspec_file = self.get_rspec_file(rspec_path) - rspec=open(rspec_file).read() + rspec = open(rspec_file).read() server = self.slicemgr if opts.aggregate: aggregates = self.registry.get_aggregates(user_cred, opts.aggregate) @@ -807,7 +853,7 @@ class Sfi: # use this to get the right slice credential ticket = SfaTicket(filename=ticket_file) ticket.decode() - slice_hrn=ticket.gidObject.get_hrn() + slice_hrn = ticket.gidObject.get_hrn() #slice_hrn = ticket.attributes['slivers'][0]['hrn'] user_cred = self.get_user_cred() slice_cred = self.get_slice_cred(slice_hrn).save_to_string(save_parents=True) @@ -824,7 +870,7 @@ class Sfi: try: cm_port = "12346" url = "https://%(hostname)s:%(cm_port)s" % locals() - print "Calling redeem_ticket at %(url)s " % locals(), + print "Calling redeem_ticket at %(url)s " % locals(), cm = xmlrpcprotocol.get_server(url, self.key_file, self.cert_file, self.options.debug) cm.redeem_ticket(slice_cred, ticket.save_to_string(save_parents=True)) print "Success" @@ -837,7 +883,7 @@ class Sfi: return # delete named slice - def delete(self,opts, args): + def delete(self, opts, args): slice_hrn = args[0] server = self.slicemgr # direct connection to the nodes component manager interface @@ -848,7 +894,7 @@ class Sfi: return server.delete_slice(slice_cred, slice_hrn) # start named slice - def start(self,opts, args): + def start(self, opts, args): slice_hrn = args[0] server = self.slicemgr # direct connection to the nodes component manager interface @@ -859,7 +905,7 @@ class Sfi: return server.start_slice(slice_cred, slice_hrn) # stop named slice - def stop(self,opts, args): + def stop(self, opts, args): slice_hrn = args[0] server = self.slicemgr # direct connection to the nodes component manager interface @@ -870,7 +916,7 @@ class Sfi: return server.stop_slice(slice_cred, slice_hrn) # reset named slice - def reset(self,opts, args): + def reset(self, opts, args): slice_hrn = args[0] server = self.slicemgr # direct connection to the nodes component manager interface @@ -878,6 +924,63 @@ class Sfi: server = self.get_component_server_from_hrn(opts.component) slice_cred = self.get_slice_cred(args[0]).save_to_string(save_parents=True) return server.reset_slice(slice_cred, slice_hrn) + + + # GENI AM related calls + + def GetVersion(self, opts, args): + server = self.geni_am + print server.GetVersion() + + def ListResources(self, opts, args): + user_cred = self.get_user_cred().save_to_string(save_parents=True) + server = self.geni_am + call_options = {'geni_compressed': True} + xrn = None + cred = user_cred + if args: + xrn = args[0] + cred = self.get_slice_cred(xrn).save_to_string(save_parents=True) + + if xrn: + call_options['geni_slice_urn'] = xrn + + rspec = server.ListResources([cred], call_options) + rspec = zlib.decompress(rspec.decode('base64')) + print rspec + + def CreateSliver(self, opts, args): + slice_xrn = args[0] + slice_cred = self.get_slice_cred(slice_xrn).save_to_string(save_parents=True) + rspec_file = self.get_rspec_file(args[1]) + rspec = open(rspec_file).read() + server = self.geni_am + return server.CreateSliver(slice_xrn, [slice_cred], rspec, []) + + def DeleteSliver(self, opts, args): + slice_xrn = args[0] + slice_cred = self.get_slice_cred(slice_xrn).save_to_string(save_parents=True) + server = self.geni_am + return server.DeleteSliver(slice_xrn, [slice_cred]) + + def SliverStatus(self, opts, args): + slice_xrn = args[0] + slice_cred = self.get_slice_cred(slice_xrn).save_to_string(save_parents=True) + server = self.geni_am + print server.SliverStatus(slice_xrn, [slice_cred]) + + def RenewSliver(self, opts, args): + slice_xrn = args[0] + slice_cred = self.get_slice_cred(slice_xrn).save_to_string(save_parents=True) + time = args[1] + server = self.geni_am + return server.RenewSliver(slice_xrn, [slice_cred], time) + + def Shutdown(self, opts, args): + slice_xrn = args[0] + slice_cred = self.get_slice_cred(slice_xrn).save_to_string(save_parents=True) + server = self.geni_am + return server.Shutdown(slice_xrn, [slice_cred]) # # Main: parse arguments and dispatch to command @@ -888,11 +991,11 @@ class Sfi: self.options = options if options.hashrequest: - self.hashrequest=True + self.hashrequest = True if len(args) <= 0: print "No command given. Use -h for help." - return -1 + return - 1 command = args[0] (cmd_opts, cmd_args) = self.create_cmd_parser(command).parse_args(args[1:]) @@ -900,12 +1003,12 @@ class Sfi: print "Registry %s, sm %s, dir %s, user %s, auth %s" % (options.registry, options.sm, options.sfi_dir, options.user, options.auth) - print "Command %s" %command + print "Command %s" % command if command in ("resources"): - print "resources cmd_opts %s" %cmd_opts.format - elif command in ("list","show","remove"): - print "cmd_opts.type %s" %cmd_opts.type - print "cmd_args %s" %cmd_args + print "resources cmd_opts %s" % cmd_opts.format + elif command in ("list", "show", "remove"): + print "cmd_opts.type %s" % cmd_opts.type + print "cmd_args %s" % cmd_args self.set_servers() @@ -918,5 +1021,5 @@ class Sfi: return -if __name__=="__main__": +if __name__ == "__main__": Sfi().main() diff --git a/sfa/init.d/sfa b/sfa/init.d/sfa index b3b041ca..6e81c63e 100755 --- a/sfa/init.d/sfa +++ b/sfa/init.d/sfa @@ -75,6 +75,10 @@ start() { action "SFA SliceMgr" daemon /usr/bin/sfa-server.py -s -d $OPTIONS fi + if [ "$SFA_GENI_AGGREGATE_ENABLED" ]; then + action $"SFA GENI Aggregate" daemon /usr/bin/sfa-server.py -g -d $OPTIONS + fi + RETVAL=$? [ $RETVAL -eq 0 ] && touch /var/lock/subsys/sfa diff --git a/sfa/managers/aggregate_manager_pl.py b/sfa/managers/aggregate_manager_pl.py index 417cd7c5..76c576bd 100644 --- a/sfa/managers/aggregate_manager_pl.py +++ b/sfa/managers/aggregate_manager_pl.py @@ -45,7 +45,7 @@ def __get_hostnames(nodes): hostnames.append(node.hostname) return hostnames -def create_slice(api, xrn, xml): +def create_slice(api, xrn, xml, reg_objects=None): """ Verify HRN and initialize the slice record in PLC if necessary. """ @@ -58,9 +58,10 @@ def create_slice(api, xrn, xml): registry = api.registries[api.hrn] credential = api.getCredential() site_id, remote_site_id = slices.verify_site(registry, credential, hrn, - peer, sfa_peer) + peer, sfa_peer, reg_objects) + slice = slices.verify_slice(registry, credential, hrn, site_id, - remote_site_id, peer, sfa_peer) + remote_site_id, peer, sfa_peer, reg_objects) network = Network(api) @@ -68,7 +69,6 @@ def create_slice(api, xrn, xml): current = __get_hostnames(slice.get_nodes()) network.addRSpec(xml, api.config.SFA_AGGREGATE_RSPEC_SCHEMA) - request = __get_hostnames(network.nodesWithSlivers()) # remove nodes not in rspec @@ -76,6 +76,8 @@ def create_slice(api, xrn, xml): # add nodes from rspec added_nodes = list(set(request).difference(current)) + + if peer: api.plshell.UnBindObjectFromPeer(api.plauth, 'slice', slice.id, peer) @@ -146,7 +148,7 @@ def start_slice(api, xrn): raise RecordNotFound(hrn) slice_id = slices[0] attributes = api.plshell.GetSliceTags(api.plauth, {'slice_id': slice_id, 'name': 'enabled'}, ['slice_attribute_id']) - attribute_id = attreibutes[0]['slice_attribute_id'] + attribute_id = attributes[0]['slice_attribute_id'] api.plshell.UpdateSliceTag(api.plauth, attribute_id, "1" ) return 1 diff --git a/sfa/managers/registry_manager_pl.py b/sfa/managers/registry_manager_pl.py index 24867383..50776c0c 100644 --- a/sfa/managers/registry_manager_pl.py +++ b/sfa/managers/registry_manager_pl.py @@ -10,12 +10,15 @@ from sfa.trust.credential import * from sfa.trust.certificate import * from sfa.util.faults import * + + def get_credential(api, xrn, type, is_self=False): # convert xrn to hrn if type: hrn = urn_to_hrn(xrn)[0] else: hrn, type = urn_to_hrn(xrn) + # Is this a root or sub authority auth_hrn = api.auth.get_authority(hrn) if not auth_hrn or hrn == api.config.SFA_INTERFACE_HRN: @@ -54,17 +57,42 @@ def get_credential(api, xrn, type, is_self=False): new_cred = Credential(subject = object_gid.get_subject()) new_cred.set_gid_caller(caller_gid) new_cred.set_gid_object(object_gid) - new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn) - new_cred.set_pubkey(object_gid.get_pubkey()) + new_cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename()) + #new_cred.set_pubkey(object_gid.get_pubkey()) new_cred.set_privileges(rights) - new_cred.set_delegate(True) + new_cred.get_privileges().delegate_all_privileges(True) auth_kind = "authority,ma,sa" - new_cred.set_parent(api.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind)) + # Parent not necessary, verify with certs + #new_cred.set_parent(api.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind)) new_cred.encode() new_cred.sign() return new_cred.save_to_string(save_parents=True) + +# The GENI GetVersion call +def GetVersion(): + version = {} + version['geni_api'] = 1 + return version + + + +# The GENI resolve call +def Resolve(api, xrn, creds): + records = resolve(api, xrn) + + if len(records) == 0: + return {} + + record = records[0] + if record.type == 'slice': + return {'geni_urn': xrn, 'geni_creator': " ".join(record.PI)} + if record.type == 'user': + return {'geni_urn': xrn, 'geni_certificate': record.gid} + + + def resolve(api, xrns, type=None, origin_hrn=None, full=True): # load all know registry names into a prefix tree and attempt to find @@ -156,7 +184,7 @@ def list(api, xrn, origin_hrn=None): def register(api, record): hrn, type = record['hrn'], record['type'] - + urn = hrn_to_urn(hrn,type) # validate the type if type not in ['authority', 'slice', 'node', 'user']: raise UnknownSfaType(type) @@ -185,7 +213,7 @@ def register(api, record): pub_key = record['key'] pkey = convert_public_key(pub_key) - gid_object = api.auth.hierarchy.create_gid(hrn, uuid, pkey) + gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey) gid = gid_object.save_to_string(save_parents=True) record['gid'] = gid record.set_gid(gid) @@ -193,7 +221,7 @@ def register(api, record): if type in ["authority"]: # update the tree if not api.auth.hierarchy.auth_exists(hrn): - api.auth.hierarchy.create_auth(hrn) + api.auth.hierarchy.create_auth(hrn_to_urn(hrn,'authority')) # get the GID from the newly created authority gid = auth_info.get_gid_object() @@ -266,6 +294,7 @@ def update(api, record_dict): new_record = SfaRecord(dict = record_dict) type = new_record['type'] hrn = new_record['hrn'] + urn = hrn_to_urn(hrn,type) api.auth.verify_object_permission(hrn) table = SfaTable() # make sure the record exists @@ -330,7 +359,7 @@ def update(api, record_dict): # update the openssl key and gid pkey = convert_public_key(new_key) uuid = create_uuid() - gid_object = api.auth.hierarchy.create_gid(hrn, uuid, pkey) + gid_object = api.auth.hierarchy.create_gid(urn, uuid, pkey) gid = gid_object.save_to_string(save_parents=True) record['gid'] = gid record = SfaRecord(dict=record) diff --git a/sfa/methods/CreateSliver.py b/sfa/methods/CreateSliver.py new file mode 100644 index 00000000..651a77da --- /dev/null +++ b/sfa/methods/CreateSliver.py @@ -0,0 +1,62 @@ +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter +from sfatables.runtime import SFATablesRules +import sys +from sfa.trust.credential import Credential +from sfa.util.sfalogging import logger + +class CreateSliver(Method): + """ + Allocate resources to a slice. This operation is expected to + start the allocated resources asynchornously after the operation + has successfully completed. Callers can check on the status of + the resources using SliverStatus. + + @param slice_urn (string) URN of slice to allocate to + @param credentials ([string]) of credentials + @param rspec (string) rspec to allocate + + """ + interfaces = ['geni_am'] + accepts = [ + Parameter(str, "Slice URN"), + Parameter(type([str]), "List of credentials"), + Parameter(str, "RSpec"), + Parameter(type([]), "List of user information") + ] + returns = Parameter(str, "Allocated RSpec") + + def __run_sfatables(self, manager, rules, hrn, origin_hrn, rspec): + if rules.sorted_rule_list: + contexts = rules.contexts + request_context = manager.fetch_context(hrn, origin_hrn, contexts) + rules.set_context(request_context) + newrspec = rules.apply(rspec) + else: + newrspec = rspec + return newrspec + + + def call(self, slice_xrn, creds, rspec, users): + hrn, type = urn_to_hrn(slice_xrn) + + self.api.logger.info("interface: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, hrn, self.name)) + + # Find the valid credentials + ValidCreds = self.api.auth.checkCredentials(creds, 'createsliver', hrn) + origin_hrn = Credential(string=ValidCreds[0]).get_gid_caller().get_hrn() + + manager_base = 'sfa.managers' + + if self.api.interface in ['geni_am']: + mgr_type = self.api.config.SFA_GENI_AGGREGATE_TYPE + manager_module = manager_base + ".geni_am_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + rspec = self.__run_sfatables(manager, SFATablesRules('INCOMING'), + hrn, origin_hrn, rspec) + + return manager.CreateSliver(self.api, slice_xrn, ValidCreds, rspec, users) + return '' + diff --git a/sfa/methods/DeleteSliver.py b/sfa/methods/DeleteSliver.py new file mode 100644 index 00000000..9d02ed9c --- /dev/null +++ b/sfa/methods/DeleteSliver.py @@ -0,0 +1,38 @@ +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter + +class DeleteSliver(Method): + """ + Delete sliver from a slice. Callers can check on the status of + the resources using SliverStatus. + + @param slice_urn (string) URN of slice to allocate to + @param credentials ([string]) of credentials + + """ + interfaces = ['geni_am'] + accepts = [ + Parameter(str, "Slice URN"), + Parameter(type([str]), "List of credentials"), + ] + returns = Parameter(bool, "Success or Failure") + + def call(self, slice_xrn, creds): + hrn, type = urn_to_hrn(slice_xrn) + self.api.logger.info("interface: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, hrn, self.name)) + + # Find the valid credentials + ValidCreds = self.api.auth.checkCredentials(creds, 'deletesliver', hrn) + + manager_base = 'sfa.managers' + + if self.api.interface in ['geni_am']: + mgr_type = self.api.config.SFA_GENI_AGGREGATE_TYPE + manager_module = manager_base + ".geni_am_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + return manager.DeleteSliver(self.api, slice_xrn, ValidCreds) + + return '' + diff --git a/sfa/methods/GetVersion.py b/sfa/methods/GetVersion.py new file mode 100644 index 00000000..e1ca8b3b --- /dev/null +++ b/sfa/methods/GetVersion.py @@ -0,0 +1,33 @@ +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter + + +class GetVersion(Method): + """ + Returns this GENI Aggregate Manager's Version Information + @return version + """ + interfaces = ['geni_am','registry'] + accepts = [] + returns = Parameter(dict, "Version information") + + def call(self): + self.api.logger.info("interface: %s\tmethod-name: %s" % (self.api.interface, self.name)) + + manager_base = 'sfa.managers' + + if self.api.interface in ['geni_am']: + mgr_type = self.api.config.SFA_GENI_AGGREGATE_TYPE + manager_module = manager_base + ".geni_am_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + return manager.GetVersion() + if self.api.interface in ['registry']: + mgr_type = self.api.config.SFA_REGISTRY_TYPE + manager_module = manager_base + ".slice_manager_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + return manager.GetVersion() + + return {} + diff --git a/sfa/methods/ListResources.py b/sfa/methods/ListResources.py new file mode 100644 index 00000000..b0173812 --- /dev/null +++ b/sfa/methods/ListResources.py @@ -0,0 +1,59 @@ +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter, Mixed +from sfa.trust.credential import Credential +from sfatables.runtime import SFATablesRules +import sys +import zlib + +class ListResources(Method): + """ + Returns information about available resources or resources allocated to this slice + @param credential list + @param options dictionary + @return string + """ + interfaces = ['geni_am'] + accepts = [ + Parameter(type([str]), "List of credentials"), + Parameter(dict, "Options") + ] + returns = Parameter(str, "List of resources") + + def call(self, creds, options): + self.api.logger.info("interface: %s\tmethod-name: %s" % (self.api.interface, self.name)) + + # Find the valid credentials + hrn = None + if options.has_key('geni_slice_urn'): + xrn = options['geni_slice_urn'] + hrn, _ = urn_to_hrn(xrn) + + ValidCreds = self.api.auth.checkCredentials(creds, '', hrn) + origin_hrn = Credential(string=ValidCreds[0]).get_gid_caller().get_hrn() + + + manager_base = 'sfa.managers' + + if self.api.interface in ['geni_am']: + mgr_type = self.api.config.SFA_GENI_AGGREGATE_TYPE + manager_module = manager_base + ".geni_am_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + rspec = manager.ListResources(self.api, ValidCreds, options) + outgoing_rules = SFATablesRules('OUTGOING') + + + filtered_rspec = rspec + if outgoing_rules.sorted_rule_list: + context = {'sfa':{'user':{'hrn':origin_hrn}, 'slice':{'hrn':None}}} + outgoing_rules.set_context(context) + filtered_rspec = outgoing_rules.apply(rspec) + + if options.has_key('geni_compressed') and options['geni_compressed'] == True: + filtered_rspec = zlib.compress(rspec).encode('base64') + + + return filtered_rspec + + diff --git a/sfa/methods/RenewSliver.py b/sfa/methods/RenewSliver.py new file mode 100644 index 00000000..bce8a495 --- /dev/null +++ b/sfa/methods/RenewSliver.py @@ -0,0 +1,46 @@ +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter +from sfa.trust.credential import Credential +from dateutil.parser import parse + +class RenewSliver(Method): + """ + Renews the resources in a sliver, extending the lifetime of the slice. + @param slice_urn (string) URN of slice to renew + @param credentials ([string]) of credentials + @param expiration_time (string) requested time of expiration + + """ + interfaces = ['geni_am'] + accepts = [ + Parameter(str, "Slice URN"), + Parameter(type([str]), "List of credentials"), + Parameter(str, "Expiration time in RFC 3339 format") + ] + returns = Parameter(bool, "Success or Failure") + + def call(self, slice_xrn, creds, expiration_time): + hrn, type = urn_to_hrn(slice_xrn) + + self.api.logger.info("interface: %s\ttarget-hrn: %s\tcaller-creds: %s\tmethod-name: %s"%(self.api.interface, hrn, creds, self.name)) + + # Find the valid credentials + ValidCreds = self.api.auth.checkCredentials(creds, 'renewsliver', hrn) + + # Validate that the time does not go beyond the credential's expiration time + requested_time = parse(expiration_time) + if requested_time > Credential(string=ValidCreds[0]).get_lifetime(): + raise InsufficientRights('SliverStatus: Credential expires before requested expiration time') + + manager_base = 'sfa.managers' + + if self.api.interface in ['geni_am']: + mgr_type = self.api.config.SFA_GENI_AGGREGATE_TYPE + manager_module = manager_base + ".geni_am_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + return manager.RenewSliver(self.api, slice_xrn, ValidCreds, expiration_time) + + return '' + diff --git a/sfa/methods/ResolveGENI.py b/sfa/methods/ResolveGENI.py new file mode 100644 index 00000000..83b6c393 --- /dev/null +++ b/sfa/methods/ResolveGENI.py @@ -0,0 +1,30 @@ +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter +from sfa.trust.credential import Credential + +class ResolveGENI(Method): + """ + Lookup a URN and return information about the corresponding object. + @param urn + + """ + interfaces = ['registry'] + accepts = [ + Parameter(str, "URN"), + Parameter(type([str]), "List of credentials"), + ] + returns = Parameter(bool, "Success or Failure") + + def call(self, xrn): + + manager_base = 'sfa.managers' + + if self.api.interface in ['registry']: + mgr_type = self.api.config.SFA_REGISTRY_TYPE + manager_module = manager_base + ".registry_manager_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + return manager.Resolve(self.api, xrn, '') + + return {} \ No newline at end of file diff --git a/sfa/methods/Shutdown.py b/sfa/methods/Shutdown.py new file mode 100644 index 00000000..cd36532d --- /dev/null +++ b/sfa/methods/Shutdown.py @@ -0,0 +1,39 @@ +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter + + +class Shutdown(Method): + """ + Perform an emergency shut down of a sliver. This operation is intended for administrative use. + The sliver is shut down but remains available for further forensics. + + @param slice_urn (string) URN of slice to renew + @param credentials ([string]) of credentials + """ + interfaces = ['geni_am'] + accepts = [ + Parameter(str, "Slice URN"), + Parameter(type([str]), "List of credentials"), + ] + returns = Parameter(bool, "Success or Failure") + + def call(self, slice_xrn, creds): + hrn, type = urn_to_hrn(slice_xrn) + + self.api.logger.info("interface: %s\ttarget-hrn: %s\tcaller-creds: %s\tmethod-name: %s"%(self.api.interface, hrn, creds, self.name)) + + # Find the valid credentials + ValidCreds = self.api.auth.checkCredentials(creds, 'shutdown', hrn) + + manager_base = 'sfa.managers' + + if self.api.interface in ['geni_am']: + mgr_type = self.api.config.SFA_GENI_AGGREGATE_TYPE + manager_module = manager_base + ".geni_am_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + return manager.Shutdown(self.api, slice_xrn, ValidCreds) + + return '' + diff --git a/sfa/methods/SliverStatus.py b/sfa/methods/SliverStatus.py new file mode 100644 index 00000000..24d6ff2a --- /dev/null +++ b/sfa/methods/SliverStatus.py @@ -0,0 +1,35 @@ +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter + +class SliverStatus(Method): + """ + Get the status of a sliver + + @param slice_urn (string) URN of slice to allocate to + + """ + interfaces = ['geni_am'] + accepts = [ + Parameter(str, "Slice URN"), + ] + returns = Parameter(bool, "Success or Failure") + + def call(self, slice_xrn, creds): + hrn, type = urn_to_hrn(slice_xrn) + + ValidCreds = self.api.auth.checkCredentials(creds, 'sliverstatus', hrn) + + self.api.logger.info("interface: %s\ttarget-hrn: %s\tmethod-name: %s"%(self.api.interface, hrn, self.name)) + + manager_base = 'sfa.managers' + + if self.api.interface in ['geni_am']: + mgr_type = self.api.config.SFA_GENI_AGGREGATE_TYPE + manager_module = manager_base + ".geni_am_%s" % mgr_type + manager = __import__(manager_module, fromlist=[manager_base]) + return manager.SliverStatus(self.api, slice_xrn, ValidCreds) + + return '' + diff --git a/sfa/methods/get_geni_aggregates.py b/sfa/methods/get_geni_aggregates.py new file mode 100644 index 00000000..d09505f6 --- /dev/null +++ b/sfa/methods/get_geni_aggregates.py @@ -0,0 +1,60 @@ +### $Id: get_slices.py 14387 2009-07-08 18:19:11Z faiyaza $ +### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/methods/get_aggregates.py $ +from types import StringTypes +from sfa.util.faults import * +from sfa.util.namespace import * +from sfa.util.method import Method +from sfa.util.parameter import Parameter, Mixed +from sfa.trust.auth import Auth +from sfa.server.aggregate import Aggregates + +class get_geni_aggregates(Method): + """ + Get a list of connection information for all known GENI aggregates. + + @param cred credential string specifying the rights of the caller + @param a Human readable name (hrn or urn), or list of hrns or None + @return list of dictionaries with aggregate information. + """ + + interfaces = ['registry', 'aggregate', 'slicemgr'] + + accepts = [ + Parameter(str, "Credential string"), + Mixed(Parameter(str, "Human readable name (hrn or urn)"), + Parameter(None, "hrn not specified")) + ] + + + returns = [Parameter(dict, "Aggregate interface information")] + + def call(self, cred, xrn = None): + hrn, type = urn_to_hrn(xrn) + self.api.auth.check(cred, 'list') + + geni_aggs = Aggregates(self.api, '/etc/sfa/geni_aggregates.xml') + + geni_aggs = geni_aggs.interfaces.values() + + hrn_list = [] + if hrn: + if isinstance(hrn, StringTypes): + hrn_list = [hrn] + elif isinstance(hrn, list): + hrn_list = hrn + + if not hrn_list: + interfaces = geni_aggs + else: + interfaces = [interface for interface in geni_aggs if interface['hrn'] in hrn_list] + + + # Remove Aggregate's default sfa-aggregate + interfaces = interfaces[:-1] + + + # Add urns + for interface in interfaces: + interface['urn'] = hrn_to_urn(interface['hrn'], 'authority') + + return interfaces diff --git a/sfa/plc/api-dev.py b/sfa/plc/api-dev.py new file mode 100644 index 00000000..29b0cc80 --- /dev/null +++ b/sfa/plc/api-dev.py @@ -0,0 +1,562 @@ +# +# SFA XML-RPC and SOAP interfaces +# +### $Id: api.py 17793 2010-04-26 21:40:57Z tmack $ +### $URL: https://svn.planet-lab.org/svn/sfa/trunk/sfa/plc/api.py $ +# + +import sys +import os +import traceback +import string +import xmlrpclib +from sfa.trust.auth import Auth +from sfa.util.config import * +from sfa.util.faults import * +from sfa.util.debug import * +from sfa.trust.rights import * +from sfa.trust.credential import * +from sfa.trust.certificate import * +from sfa.util.namespace import * +from sfa.util.api import * +from sfa.util.nodemanager import NodeManager +from sfa.util.sfalogging import * +from collections import defaultdict + + +class RecordInfo(): + + shell = None + auth = None + + # pl records + sites = {} + slices = {} + persons = {} + nodes = {} + keys = {} + + # sfa records + sfa_authorities = {} + sfa_slices = {} + sfa_users = {} + sfa_nodes = {} + + records = [] + + def __init__(self, api, records): + self.api = api + self.shell = api.plshell + self.auth = api.plauth + + site_ids = [] + slice_ids = [] + person_ids = [] + node_ids = [] + + # put records into groups based on types + for record in records: + pointer = record['pointer'] + if record['type'] == 'authority': + self.sfa_authorities[pointer] = record + self.records.append(record) + site_ids.append(record['pointer']) + elif record['type'] == 'slice': + self.sfa_slices[pointer] = record + self.records.append(record) + slice_ids.append(record['pointer']) + elif record['type'] == 'user': + self.sfa_users[pointer] = record + self.records.append(record) + person_ids.append(record['pointer']) + elif record['type'] == 'node': + self.sfa_nodes[pointer] = record + self.records.append(record) + node_ids.append(record['pointer']) + + # get pl info for these records + self.update_pl_sites(site_ids) + self.update_pl_slices(slice_ids) + self.update_pl_persons(person_ids) + self.update_pl_nodes(node_ids) + + site_ids = [] + slice_ids = [] + person_ids = [] + node_ids = [] + # now get pl records for all ids associated with + # these records + for record in records: + if 'site_id' in record: + site_ids.append(record['site_id']) + if 'site_ids' in records: + site_ids.extend(record['site_ids']) + if 'person_ids' in record: + person_ids.extend(record['person_ids']) + if 'slice_ids' in record: + slice_ids.extend(record['slice_ids']) + if 'node_ids' in record: + node_ids.extend(record['node_ids']) + + # get pl info for these records + self.update_pl_sites(site_ids) + self.update_pl_slices(slice_ids) + self.update_pl_persons(person_ids) + self.update_pl_nodes(node_ids) + + # convert pl ids to hrns + self.update_hrns() + + # update sfa info + self.update_sfa_info(person_ids) + + def update_pl_sites(self, site_ids): + """ + Update site records with PL info + """ + if not site_ids: + return + sites = self.shell.GetSites(self.auth, site_ids) + for site in sites: + site_id = site['site_id'] + self.sites[site_id] = site + if site_id in self.sfa_authorities: + self.sfa_authorities[site_id].update(site) + + def update_pl_slices(self, slice_ids): + """ + Update slice records with PL info + """ + if not slice_ids: + return + slices = self.shell.GetSlices(self.auth, slice_ids) + for slice in slices: + slice_id = slice['slice_id'] + self.slices[slice_id] = slice + if slice_id in self.sfa_slices: + self.sfa_slices[slice_id].update(slice) + + def update_pl_persons(self, person_ids): + """ + Update person records with PL info + """ + key_ids = [] + if not person_ids: + return + persons = self.shell.GetPersons(self.auth, person_ids) + for person in persons: + person_id = person['person_id'] + self.persons[person_id] = person + key_ids.extend(person['key_ids']) + if person_id in self.sfa_users: + self.sfa_users[person_id].update(person) + self.update_pl_keys(key_ids) + + def update_pl_keys(self, key_ids): + """ + Update user records with PL public key info + """ + if not key_ids: + return + keys = self.shell.GetKeys(self.auth, key_ids) + for key in keys: + person_id = key['person_id'] + self.keys[key['key_id']] = key + if person_id in self.sfa_users: + person = self.sfa_users[person_id] + if not 'keys' in person: + person['keys'] = [key['key']] + else: + person['keys'].append(key['key']) + + def update_pl_nodes(self, node_ids): + """ + Update node records with PL info + """ + if not node_ids: + return + nodes = self.shell.GetNodes(self.auth, node_ids) + for node in nodes: + node_id = node['node_id'] + self.nodes[node['node_id']] = node + if node_id in self.sfa_nodes: + self.sfa_nodes[node_id].update(node) + + + def update_hrns(self): + """ + Convert pl ids to hrns + """ + for record in self.records: + # get all necessary data + type = record['type'] + pointer = record['pointer'] + auth_hrn = self.api.hrn + login_base = '' + if pointer == -1: + continue + + if 'site_id' in record: + site = self.sites[record['site_id']] + login_base = site['login_base'] + record['site'] = ".".join([auth_hrn, login_base]) + if 'person_ids' in record: + emails = [self.persons[person_id]['email'] for person_id in record['person_ids'] \ + if person_id in self.persons] + usernames = [email.split('@')[0] for email in emails] + person_hrns = [".".join([auth_hrn, login_base, username]) for username in usernames] + record['persons'] = person_hrns + if 'slice_ids' in record: + slicenames = [self.slices[slice_id]['name'] for slice_id in record['slice_ids'] \ + if slice_id in self.slices] + slice_hrns = [slicename_to_hrn(auth_hrn, slicename) for slicename in slicenames] + record['slices'] = slice_hrns + if 'node_ids' in record: + hostnames = [self.nodes[node_id]['hostname'] for node_id in record['node_ids'] \ + if node_id in self.nodes] + node_hrns = [hostname_to_hrn(auth_hrn, login_base, hostname) for hostname in hostnames] + record['nodes'] = node_hrns + if 'site_ids' in record: + login_bases = [self.sites[site_id]['login_base'] for site_id in record['site_ids'] \ + if site_id in self.sites] + site_hrns = [".".join([auth_hrn, lbase]) for lbase in login_bases] + record['sites'] = site_hrns + + def update_sfa_info(self, person_ids): + from sfa.util.table import SfaTable + table = SfaTable() + persons = table.find({'type': 'user', 'pointer': person_ids}) + # create a hrns keyed on the sfa record's pointer. + # Its possible for multiple records to have the same pointer so + # the dict's value will be a list of hrns. + person_dict = defaultdict(list) + for person in persons: + person_dict[person['pointer']].append(person['hrn']) + + def startswith(prefix, values): + return [value for value in values if value.startswith(prefix)] + + for record in self.records: + authority = record['authority'] + if record['pointer'] == -1: + continue + + if record['type'] == 'slice': + # all slice users are researchers + record['PI'] = [] + record['researchers'] = [] + for person_id in record['person_ids']: + record['researchers'].extend(person_dict[person_id]) + # also add the pis at the slice's site + site = self.sites[record['site_id']] + for person_id in site['person_ids']: + person = self.persons[person_id] + if 'pi' in person['roles']: + # PLCAPI doesn't support per site roles + # (a pi has the pi role at every site he belongs to). + # We shouldnt allow this in SFA + record['PI'].extend(startswith(authority, person_dict[person_id])) + + elif record['type'] == 'authority': + record['PI'] = [] + record['operator'] = [] + record['owner'] = [] + for person_id in record['person_ids']: + person = self.persons[person_id] + if 'pi' in person['roles']: + # only get PI's at this site + record['PI'].extend(startswith(record['hrn'], person_dict[person_id])) + if 'tech' in person['roles']: + # only get PI's at this site + record['operator'].extend(startswith(record['hrn'], person_dict[person_id])) + if 'admin' in person['roles']: + record['owner'].extend(startswith(record['hrn'], person_dict[person_id])) + + elif record['type'] == 'node': + record['dns'] = record['hostname'] + + elif record['type'] == 'user': + record['email'] = record['email'] + + + + + + def get_records(self): + return self.records + + +class SfaAPI(BaseAPI): + + # flat list of method names + import sfa.methods + methods = sfa.methods.all + + def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", + methods='sfa.methods', peer_cert = None, interface = None, + key_file = None, cert_file = None, cache = None): + BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, \ + peer_cert=peer_cert, interface=interface, key_file=key_file, \ + cert_file=cert_file, cache=cache) + + self.encoding = encoding + + from sfa.util.table import SfaTable + self.SfaTable = SfaTable + # Better just be documenting the API + if config is None: + return + + # Load configuration + self.config = Config(config) + self.auth = Auth(peer_cert) + self.interface = interface + self.key_file = key_file + self.key = Keypair(filename=self.key_file) + self.cert_file = cert_file + self.cert = Certificate(filename=self.cert_file) + self.credential = None + # Initialize the PLC shell only if SFA wraps a myPLC + rspec_type = self.config.get_aggregate_type() + if (rspec_type == 'pl' or rspec_type == 'vini'): + self.plshell = self.getPLCShell() + self.plshell_version = "4.3" + + self.hrn = self.config.SFA_INTERFACE_HRN + self.time_format = "%Y-%m-%d %H:%M:%S" + self.logger=get_sfa_logger() + + def getPLCShell(self): + self.plauth = {'Username': self.config.SFA_PLC_USER, + 'AuthMethod': 'password', + 'AuthString': self.config.SFA_PLC_PASSWORD} + + self.plshell_type = 'xmlrpc' + # connect via xmlrpc + url = self.config.SFA_PLC_URL + shell = xmlrpclib.Server(url, verbose = 0, allow_none = True) + return shell + + def getCredential(self): + if self.interface in ['registry']: + return self.getCredentialFromLocalRegistry() + else: + return self.getCredentialFromRegistry() + + def getCredentialFromRegistry(self): + """ + Get our credential from a remote registry + """ + type = 'authority' + path = self.config.SFA_DATA_DIR + filename = ".".join([self.interface, self.hrn, type, "cred"]) + cred_filename = path + os.sep + filename + try: + credential = Credential(filename = cred_filename) + return credential.save_to_string(save_parents=True) + except IOError: + from sfa.server.registry import Registries + registries = Registries(self) + registry = registries[self.hrn] + cert_string=self.cert.save_to_string(save_parents=True) + # get self credential + self_cred = registry.get_self_credential(cert_string, type, self.hrn) + # get credential + cred = registry.get_credential(self_cred, type, self.hrn) + + # save cred to file + Credential(string=cred).save_to_file(cred_filename, save_parents=True) + return cred + + def getCredentialFromLocalRegistry(self): + """ + Get our current credential directly from the local registry. + """ + + hrn = self.hrn + auth_hrn = self.auth.get_authority(hrn) + + # is this a root or sub authority + if not auth_hrn or hrn == self.config.SFA_INTERFACE_HRN: + auth_hrn = hrn + auth_info = self.auth.get_auth_info(auth_hrn) + table = self.SfaTable() + records = table.findObjects(hrn) + if not records: + raise RecordNotFound + record = records[0] + type = record['type'] + object_gid = record.get_gid_object() + new_cred = Credential(subject = object_gid.get_subject()) + new_cred.set_gid_caller(object_gid) + new_cred.set_gid_object(object_gid) + new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn) + new_cred.set_pubkey(object_gid.get_pubkey()) + r1 = determine_rights(type, hrn) + new_cred.set_privileges(r1) + + auth_kind = "authority,ma,sa" + + new_cred.set_parent(self.auth.hierarchy.get_auth_cred(auth_hrn, kind=auth_kind)) + + new_cred.encode() + new_cred.sign() + + return new_cred.save_to_string(save_parents=True) + + + def loadCredential (self): + """ + Attempt to load credential from file if it exists. If it doesnt get + credential from registry. + """ + + # see if this file exists + # XX This is really the aggregate's credential. Using this is easier than getting + # the registry's credential from iteslf (ssl errors). + ma_cred_filename = self.config.SFA_DATA_DIR + os.sep + self.interface + self.hrn + ".ma.cred" + try: + self.credential = Credential(filename = ma_cred_filename) + except IOError: + self.credential = self.getCredentialFromRegistry() + + ## + # Convert SFA fields to PLC fields for use when registering up updating + # registry record in the PLC database + # + # @param type type of record (user, slice, ...) + # @param hrn human readable name + # @param sfa_fields dictionary of SFA fields + # @param pl_fields dictionary of PLC fields (output) + + def sfa_fields_to_pl_fields(self, type, hrn, record): + + def convert_ints(tmpdict, int_fields): + for field in int_fields: + if field in tmpdict: + tmpdict[field] = int(tmpdict[field]) + + pl_record = {} + #for field in record: + # pl_record[field] = record[field] + + if type == "slice": + if not "instantiation" in pl_record: + pl_record["instantiation"] = "plc-instantiated" + pl_record["name"] = hrn_to_pl_slicename(hrn) + if "url" in record: + pl_record["url"] = record["url"] + if "description" in record: + pl_record["description"] = record["description"] + if "expires" in record: + pl_record["expires"] = int(record["expires"]) + + elif type == "node": + if not "hostname" in pl_record: + if not "hostname" in record: + raise MissingSfaInfo("hostname") + pl_record["hostname"] = record["hostname"] + if not "model" in pl_record: + pl_record["model"] = "geni" + + elif type == "authority": + pl_record["login_base"] = hrn_to_pl_login_base(hrn) + + if not "name" in pl_record: + pl_record["name"] = hrn + + if not "abbreviated_name" in pl_record: + pl_record["abbreviated_name"] = hrn + + if not "enabled" in pl_record: + pl_record["enabled"] = True + + if not "is_public" in pl_record: + pl_record["is_public"] = True + + return pl_record + + + def fill_record_info(self, records): + """ + Given a SFA record, fill in the PLC specific and SFA specific + fields in the record. + """ + if not isinstance(records, list): + records = [records] + + record_info = RecordInfo(self, records) + return record_info.get_records() + + def update_membership_list(self, oldRecord, record, listName, addFunc, delFunc): + # get a list of the HRNs tht are members of the old and new records + if oldRecord: + oldList = oldRecord.get(listName, []) + else: + oldList = [] + newList = record.get(listName, []) + + # if the lists are the same, then we don't have to update anything + if (oldList == newList): + return + + # build a list of the new person ids, by looking up each person to get + # their pointer + newIdList = [] + table = self.SfaTable() + records = table.find({'type': 'user', 'hrn': newList}) + for rec in records: + newIdList.append(rec['pointer']) + + # build a list of the old person ids from the person_ids field + if oldRecord: + oldIdList = oldRecord.get("person_ids", []) + containerId = oldRecord.get_pointer() + else: + # if oldRecord==None, then we are doing a Register, instead of an + # update. + oldIdList = [] + containerId = record.get_pointer() + + # add people who are in the new list, but not the oldList + for personId in newIdList: + if not (personId in oldIdList): + addFunc(self.plauth, personId, containerId) + + # remove people who are in the old list, but not the new list + for personId in oldIdList: + if not (personId in newIdList): + delFunc(self.plauth, personId, containerId) + + def update_membership(self, oldRecord, record): + if record.type == "slice": + self.update_membership_list(oldRecord, record, 'researcher', + self.plshell.AddPersonToSlice, + self.plshell.DeletePersonFromSlice) + elif record.type == "authority": + # xxx TODO + pass + + + +class ComponentAPI(BaseAPI): + + def __init__(self, config = "/etc/sfa/sfa_config.py", encoding = "utf-8", methods='sfa.methods', + peer_cert = None, interface = None, key_file = None, cert_file = None): + + BaseAPI.__init__(self, config=config, encoding=encoding, methods=methods, peer_cert=peer_cert, + interface=interface, key_file=key_file, cert_file=cert_file) + self.encoding = encoding + + # Better just be documenting the API + if config is None: + return + + self.nodemanager = NodeManager(self.config) + + def sliver_exists(self): + sliver_dict = self.nodemanager.GetXIDs() + if slicename in sliver_dict.keys(): + return True + else: + return False diff --git a/sfa/plc/api.py b/sfa/plc/api.py index cbe321b0..0f5cb011 100644 --- a/sfa/plc/api.py +++ b/sfa/plc/api.py @@ -76,6 +76,7 @@ class SfaAPI(BaseAPI): 'AuthMethod': 'password', 'AuthString': self.config.SFA_PLC_PASSWORD} + self.plshell_type = 'xmlrpc' # connect via xmlrpc url = self.config.SFA_PLC_URL @@ -135,8 +136,8 @@ class SfaAPI(BaseAPI): new_cred = Credential(subject = object_gid.get_subject()) new_cred.set_gid_caller(object_gid) new_cred.set_gid_object(object_gid) - new_cred.set_issuer(key=auth_info.get_pkey_object(), subject=auth_hrn) - new_cred.set_pubkey(object_gid.get_pubkey()) + new_cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename()) + r1 = determine_rights(type, hrn) new_cred.set_privileges(r1) diff --git a/sfa/plc/sfa-import-plc.py b/sfa/plc/sfa-import-plc.py index 0350bbb0..39cb28c0 100755 --- a/sfa/plc/sfa-import-plc.py +++ b/sfa/plc/sfa-import-plc.py @@ -82,8 +82,9 @@ def main(): AuthHierarchy = sfaImporter.AuthHierarchy TrustedRoots = sfaImporter.TrustedRoots table = SfaTable() + if not table.exists(): - table.create() + table.create() # create root authority sfaImporter.create_top_level_auth_records(root_auth) @@ -148,10 +149,11 @@ def main(): slices_dict = {} for slice in slices: slices_dict[slice['slice_id']] = slice - # start importing for site in sites: site_hrn = interface_hrn + "." + site['login_base'] + print "Importing site: %s" % site_hrn + # import if hrn is not in list of existing hrns or if the hrn exists # but its not a site record if site_hrn not in existing_hrns or \ diff --git a/sfa/plc/sfaImport.py b/sfa/plc/sfaImport.py index 9a5113e2..b95bc5a6 100644 --- a/sfa/plc/sfaImport.py +++ b/sfa/plc/sfaImport.py @@ -200,6 +200,7 @@ class sfaImport: plc_auth = self.plc_auth sitename = site['login_base'] sitename = cleanup_string(sitename) + print 'importing site %s' % sitename hrn = parent_hrn + "." + sitename urn = hrn_to_urn(hrn, 'authority') # Hardcode 'internet2' into the hrn for sites hosting diff --git a/sfa/plc/slices.py b/sfa/plc/slices.py index 3c535048..3625bf08 100644 --- a/sfa/plc/slices.py +++ b/sfa/plc/slices.py @@ -168,50 +168,62 @@ class Slices: return sfa_peer - def verify_site(self, registry, credential, slice_hrn, peer, sfa_peer): + def verify_site(self, registry, credential, slice_hrn, peer, sfa_peer, reg_objects=None): authority = get_authority(slice_hrn) authority_urn = hrn_to_urn(authority, 'authority') - site_records = registry.resolve(credential, authority_urn) + + if reg_objects: + site = reg_objects['site'] + else: + site_records = registry.resolve(credential, authority_urn) + site = {} + for site_record in site_records: + if site_record['type'] == 'authority': + site = site_record + if not site: + raise RecordNotFound(authority) - site = {} - for site_record in site_records: - if site_record['type'] == 'authority': - site = site_record - if not site: - raise RecordNotFound(authority) remote_site_id = site.pop('site_id') login_base = get_leaf(authority) sites = self.api.plshell.GetSites(self.api.plauth, login_base) + if not sites: site_id = self.api.plshell.AddSite(self.api.plauth, site) if peer: self.api.plshell.BindObjectToPeer(self.api.plauth, 'site', site_id, peer, remote_site_id) # mark this site as an sfa peer record - if sfa_peer: + if sfa_peer and not reg_objects: peer_dict = {'type': 'authority', 'hrn': authority, 'peer_authority': sfa_peer, 'pointer': site_id} registry.register_peer_object(credential, peer_dict) else: site_id = sites[0]['site_id'] remote_site_id = sites[0]['peer_site_id'] + old_site = sites[0] - #the site is alredy on the remote agg. Let us update(e.g. max_slices field) it with the latest info. - self.sync_site(old_site, site, peer) + #the site is already on the remote agg. Let us update(e.g. max_slices field) it with the latest info. + self.sync_site(old_site, site, peer) return (site_id, remote_site_id) - def verify_slice(self, registry, credential, slice_hrn, site_id, remote_site_id, peer, sfa_peer): + def verify_slice(self, registry, credential, slice_hrn, site_id, remote_site_id, peer, sfa_peer, reg_objects=None): slice = {} slice_record = None authority = get_authority(slice_hrn) - slice_records = registry.resolve(credential, slice_hrn) - for record in slice_records: - if record['type'] in ['slice']: - slice_record = record - if not slice_record: - raise RecordNotFound(hrn) + if reg_objects: + slice_record = reg_objects['slice_record'] + else: + slice_records = registry.resolve(credential, slice_hrn) + + for record in slice_records: + if record['type'] in ['slice']: + slice_record = record + if not slice_record: + raise RecordNotFound(hrn) + + slicename = hrn_to_pl_slicename(slice_hrn) parts = slicename.split("_") login_base = parts[0] @@ -245,33 +257,39 @@ class Slices: self.sync_slice(slice, slice_record, peer) slice['peer_slice_id'] = slice_record['pointer'] - self.verify_persons(registry, credential, slice_record, site_id, remote_site_id, peer, sfa_peer) + self.verify_persons(registry, credential, slice_record, site_id, remote_site_id, peer, sfa_peer, reg_objects) return slice - def verify_persons(self, registry, credential, slice_record, site_id, remote_site_id, peer, sfa_peer): + def verify_persons(self, registry, credential, slice_record, site_id, remote_site_id, peer, sfa_peer, reg_objects=None): # get the list of valid slice users from the registry and make # sure they are added to the slice slicename = hrn_to_pl_slicename(slice_record['hrn']) - researchers = slice_record.get('researcher', []) + if reg_objects: + researchers = reg_objects['users'].keys() + else: + researchers = slice_record.get('researcher', []) for researcher in researchers: - person_record = {} - person_records = registry.resolve(credential, researcher) - for record in person_records: - if record['type'] in ['user'] and record['enabled']: - person_record = record - if not person_record: - return 1 - person_dict = person_record + if reg_objects: + person_dict = reg_objects['users'][researcher] + else: + person_records = registry.resolve(credential, researcher) + for record in person_records: + if record['type'] in ['user'] and record['enabled']: + person_record = record + if not person_record: + return 1 + person_dict = person_record + local_person=False if peer: peer_id = self.api.plshell.GetPeers(self.api.plauth, {'shortname': peer}, ['peer_id'])[0]['peer_id'] persons = self.api.plshell.GetPersons(self.api.plauth, {'email': [person_dict['email']], 'peer_id': peer_id}, ['person_id', 'key_ids']) - if not persons: - persons = self.api.plshell.GetPersons(self.api.plauth, [person_dict['email']], ['person_id', 'key_ids']) - if persons: - local_person=True - + if not persons: + persons = self.api.plshell.GetPersons(self.api.plauth, [person_dict['email']], ['person_id', 'key_ids']) + if persons: + local_person=True + else: persons = self.api.plshell.GetPersons(self.api.plauth, [person_dict['email']], ['person_id', 'key_ids']) diff --git a/sfa/server/interface.py b/sfa/server/interface.py index 4ae8e9fc..65b8d835 100644 --- a/sfa/server/interface.py +++ b/sfa/server/interface.py @@ -13,7 +13,7 @@ import traceback import sfa.util.xmlrpcprotocol as xmlrpcprotocol import sfa.util.soapprotocol as soapprotocol - + # GeniLight client support is optional try: from egeni.geniLight_client import * @@ -100,6 +100,8 @@ class Interfaces(dict): return peer_gids trusted_certs_dir = self.api.config.get_trustedroots_dir() for new_hrn in new_hrns: + if not new_hrn: + continue # the gid for this interface should already be installed if new_hrn == self.api.config.SFA_INTERFACE_HRN: continue @@ -184,9 +186,10 @@ class Interfaces(dict): # make sure the required fields are present and not null if not all([interface.get(key) for key in required_fields]): continue - + hrn, address, port = interface['hrn'], interface['addr'], interface['port'] url = 'http://%(address)s:%(port)s' % locals() + # check which client we should use # sfa.util.xmlrpcprotocol is default client_type = 'xmlrpcprotocol' diff --git a/sfa/server/modpythonapi/AuthenticatedApi.py b/sfa/server/modpythonapi/AuthenticatedApi.py index c909346b..e86781f3 100755 --- a/sfa/server/modpythonapi/AuthenticatedApi.py +++ b/sfa/server/modpythonapi/AuthenticatedApi.py @@ -18,6 +18,7 @@ class AuthenticatedApi(BaseApi): BaseApi.__init__(self, encoding) if trustedRootsDir: self.trusted_cert_list = TrustedRootList(trustedRootsDir).get_list() + self.trusted_cert_file_list = TrustedRootList(trustedRootsDir).get_file_list() else: self.trusted_cert_list = None @@ -40,13 +41,7 @@ class AuthenticatedApi(BaseApi): def validateCred(self, cred): if self.trusted_cert_list: - cred.verify_chain(self.trusted_cert_list) - caller_gid = cred.get_gid_caller() - object_gid = cred.get_gid_object() - if caller_gid: - caller_gid.verify_chain(self.trusted_cert_list) - if object_gid: - object_gid.verify_chain(self.trusted_cert_list) + cred.verify(self.trusted_cert_file_list) def authenticateGid(self, gidStr, argList, requestHash): gid = GID(string = gidStr) diff --git a/sfa/server/sfa-server.py b/sfa/server/sfa-server.py index 5e0905f0..06393ce4 100755 --- a/sfa/server/sfa-server.py +++ b/sfa/server/sfa-server.py @@ -34,7 +34,7 @@ registry_port=12345 aggregate_port=12346 slicemgr_port=12347 component_port=12346 - +geni_am_port=12348 import os, os.path import sys from optparse import OptionParser @@ -147,7 +147,14 @@ def init_server(options, config): try: manager = __import__(manager_module, fromlist=[manager_base]) except: manager = None if manager and hasattr(manager, 'init_server'): - manager.init_server() + manager.init_server() + if options.gam: + mgr_type = config.SFA_GENI_AGGREGATE_TYPE + manager_module = manager_base + ".geni_am_%s" % mgr_type + try: manager = __import__(manager_module, fromlist=[manager_base]) + except: manager = None + if manager and hasattr(manager, 'init_server'): + manager.init_server() def sync_interfaces(server_key_file, server_cert_file): @@ -179,6 +186,8 @@ def main(): help="run aggregate manager", default=False) parser.add_option("-c", "--component", dest="cm", action="store_true", help="run component server", default=False) + parser.add_option("-g", "--geniam", dest="gam", action="store_true", + help="run GENI aggregate manager", default=False) parser.add_option("-v", "--verbose", dest="verbose", action="store_true", help="verbose mode", default=False) parser.add_option("-d", "--daemon", dest="daemon", action="store_true", @@ -219,5 +228,11 @@ def main(): c = Component("", component_port, server_key_file, server_cert_file) c.start() + # start GENI aggregate manager + if (options.gam): + from sfa.server.geni_aggregate import GENIAggregate + g = GENIAggregate("", geni_am_port, server_key_file, server_cert_file) + g.start() + if __name__ == "__main__": main() diff --git a/sfa/trust/auth.py b/sfa/trust/auth.py index 73fb5928..470dc5e2 100644 --- a/sfa/trust/auth.py +++ b/sfa/trust/auth.py @@ -5,17 +5,17 @@ ### $URL$ # -import time from sfa.trust.credential import Credential from sfa.trust.trustedroot import TrustedRootList -from sfa.trust.rights import RightList from sfa.util.faults import * from sfa.trust.hierarchy import Hierarchy from sfa.util.config import * from sfa.util.namespace import * -from sfa.trust.gid import GID from sfa.util.sfaticket import * +from sfa.util.sfalogging import logger + +import sys class Auth: """ @@ -31,6 +31,25 @@ class Auth: def load_trusted_certs(self): self.trusted_cert_list = TrustedRootList(self.config.get_trustedroots_dir()).get_list() + self.trusted_cert_file_list = TrustedRootList(self.config.get_trustedroots_dir()).get_file_list() + + + + def checkCredentials(self, creds, operation, hrn = None): + valid = [] + for cred in creds: + try: + self.check(cred, operation, hrn) + valid.append(cred) + except: + error = sys.exc_info()[:2] + continue + + if not len(valid): + raise InsufficientRights('Access denied: %s -- %s' % (error[0],error[1])) + + return valid + def check(self, cred, operation, hrn = None): """ @@ -58,14 +77,10 @@ class Auth: raise InsufficientRights(operation) if self.trusted_cert_list: - self.client_cred.verify_chain(self.trusted_cert_list) - if self.client_gid: - self.client_gid.verify_chain(self.trusted_cert_list) - if self.object_gid: - self.object_gid.verify_chain(self.trusted_cert_list) + self.client_cred.verify(self.trusted_cert_file_list) else: raise MissingTrustedRoots(self.config.get_trustedroots_dir()) - + # Make sure the credential's target matches the specified hrn. # This check does not apply to trusted peers trusted_peers = [gid.get_hrn() for gid in self.trusted_cert_list] @@ -107,13 +122,7 @@ class Auth: def validateCred(self, cred): if self.trusted_cert_list: - cred.verify_chain(self.trusted_cert_list) - caller_gid = cred.get_gid_caller() - object_gid = cred.get_gid_object() - if caller_gid: - caller_gid.verify_chain(self.trusted_cert_list) - if object_gid: - object_gid.verify_chain(self.trusted_cert_list) + cred.verify(self.trusted_cert_file_list) def authenticateGid(self, gidStr, argList, requestHash=None): gid = GID(string = gidStr) @@ -229,6 +238,7 @@ class Auth: rl = RightList() type = record['type'] + if type=="slice": researchers = record.get("researcher", []) pis = record.get("PI", []) @@ -243,11 +253,15 @@ class Auth: pis = record.get("PI", []) operators = record.get("operator", []) if (caller_hrn == self.config.SFA_INTERFACE_HRN): - rl.add("authority,sa,ma",) + rl.add("authority") + rl.add("sa") + rl.add("ma") if (caller_hrn in pis): - rl.add("authority,sa") + rl.add("authority") + rl.add("sa") if (caller_hrn in operators): - rl.add("authority,ma") + rl.add("authority") + rl.add("ma") elif type == "user": rl.add("refresh") diff --git a/sfa/trust/certificate.py b/sfa/trust/certificate.py index 76b99db5..9fd58ed3 100644 --- a/sfa/trust/certificate.py +++ b/sfa/trust/certificate.py @@ -22,8 +22,9 @@ import traceback from OpenSSL import crypto import M2Crypto from M2Crypto import X509 -from M2Crypto import EVP - +from tempfile import mkstemp +from sfa.util.sfalogging import logger +from sfa.util.namespace import urn_to_hrn from sfa.util.faults import * def convert_public_key(key): @@ -31,10 +32,10 @@ def convert_public_key(key): if not os.path.isfile(keyconvert_path): raise IOError, "Could not find keyconvert in %s" % keyconvert_path - # we can only convert rsa keys + # we can only convert rsa keys if "ssh-dss" in key: return None - + (ssh_f, ssh_fn) = tempfile.mkstemp() ssl_fn = tempfile.mktemp() os.write(ssh_f, key) @@ -42,13 +43,13 @@ def convert_public_key(key): cmd = keyconvert_path + " " + ssh_fn + " " + ssl_fn os.system(cmd) - + # this check leaves the temporary file containing the public key so # that it can be expected to see why it failed. # TODO: for production, cleanup the temporary files if not os.path.exists(ssl_fn): return None - + k = Keypair() try: k.load_pubkey_from_file(ssl_fn) @@ -68,140 +69,141 @@ def convert_public_key(key): # may represent only a public key (this usage is consistent with OpenSSL). class Keypair: - key = None # public/private keypair - m2key = None # public key (m2crypto format) - - ## - # Creates a Keypair object - # @param create If create==True, creates a new public/private key and - # stores it in the object - # @param string If string!=None, load the keypair from the string (PEM) - # @param filename If filename!=None, load the keypair from the file - - def __init__(self, create=False, string=None, filename=None): - if create: - self.create() - if string: - self.load_from_string(string) - if filename: - self.load_from_file(filename) - - ## - # Create a RSA public/private key pair and store it inside the keypair object - - def create(self): - self.key = crypto.PKey() - self.key.generate_key(crypto.TYPE_RSA, 1024) - - ## - # Save the private key to a file - # @param filename name of file to store the keypair in - - def save_to_file(self, filename): - open(filename, 'w').write(self.as_pem()) - - ## - # Load the private key from a file. Implicity the private key includes the public key. - - def load_from_file(self, filename): - buffer = open(filename, 'r').read() - self.load_from_string(buffer) - - ## - # Load the private key from a string. Implicitly the private key includes the public key. - - def load_from_string(self, string): - self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string) - self.m2key = M2Crypto.EVP.load_key_string(string) - - ## - # Load the public key from a string. No private key is loaded. - - def load_pubkey_from_file(self, filename): - # load the m2 public key - m2rsakey = M2Crypto.RSA.load_pub_key(filename) - self.m2key = M2Crypto.EVP.PKey() - self.m2key.assign_rsa(m2rsakey) - - # create an m2 x509 cert - m2name = M2Crypto.X509.X509_Name() - m2name.add_entry_by_txt(field="CN", type=0x1001, entry="junk", len=-1, loc=-1, set=0) - m2x509 = M2Crypto.X509.X509() - m2x509.set_pubkey(self.m2key) - m2x509.set_serial_number(0) - m2x509.set_issuer_name(m2name) - m2x509.set_subject_name(m2name) - ASN1 = M2Crypto.ASN1.ASN1_UTCTIME() - ASN1.set_time(500) - m2x509.set_not_before(ASN1) - m2x509.set_not_after(ASN1) - junk_key = Keypair(create=True) - m2x509.sign(pkey=junk_key.get_m2_pkey(), md="sha1") - - # convert the m2 x509 cert to a pyopenssl x509 - m2pem = m2x509.as_pem() - pyx509 = crypto.load_certificate(crypto.FILETYPE_PEM, m2pem) - - # get the pyopenssl pkey from the pyopenssl x509 - self.key = pyx509.get_pubkey() - - ## - # Load the public key from a string. No private key is loaded. - - def load_pubkey_from_string(self, string): - (f, fn) = tempfile.mkstemp() - os.write(f, string) - os.close(f) - self.load_pubkey_from_file(fn) - os.remove(fn) - - ## - # Return the private key in PEM format. - - def as_pem(self): - return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key) - - ## - # Return an M2Crypto key object - - def get_m2_pkey(self): - if not self.m2key: - self.m2key = M2Crypto.EVP.load_key_string(self.as_pem()) - return self.m2key - - ## - # Returns a string containing the public key represented by this object. - - def get_pubkey_string(self): - m2pkey = self.get_m2_pkey() - return base64.b64encode(m2pkey.as_der()) - - ## - # Return an OpenSSL pkey object - - def get_openssl_pkey(self): - return self.key - - ## - # Given another Keypair object, return TRUE if the two keys are the same. + key = None # public/private keypair + m2key = None # public key (m2crypto format) + + ## + # Creates a Keypair object + # @param create If create==True, creates a new public/private key and + # stores it in the object + # @param string If string!=None, load the keypair from the string (PEM) + # @param filename If filename!=None, load the keypair from the file + + def __init__(self, create=False, string=None, filename=None): + if create: + self.create() + if string: + self.load_from_string(string) + if filename: + self.load_from_file(filename) + + ## + # Create a RSA public/private key pair and store it inside the keypair object + + def create(self): + self.key = crypto.PKey() + self.key.generate_key(crypto.TYPE_RSA, 1024) + + ## + # Save the private key to a file + # @param filename name of file to store the keypair in + + def save_to_file(self, filename): + open(filename, 'w').write(self.as_pem()) + + ## + # Load the private key from a file. Implicity the private key includes the public key. + + def load_from_file(self, filename): + buffer = open(filename, 'r').read() + self.load_from_string(buffer) - def is_same(self, pkey): - return self.as_pem() == pkey.as_pem() - - def sign_string(self, data): - k = self.get_m2_pkey() - k.sign_init() - k.sign_update(data) - return base64.b64encode(k.sign_final()) - - def verify_string(self, data, sig): - k = self.get_m2_pkey() - k.verify_init() - k.verify_update(data) - return M2Crypto.m2.verify_final(k.ctx, base64.b64decode(sig), k.pkey) - - def compute_hash(self, value): - return self.sign_string(str(value)) + ## + # Load the private key from a string. Implicitly the private key includes the public key. + + def load_from_string(self, string): + self.key = crypto.load_privatekey(crypto.FILETYPE_PEM, string) + self.m2key = M2Crypto.EVP.load_key_string(string) + + ## + # Load the public key from a string. No private key is loaded. + + def load_pubkey_from_file(self, filename): + # load the m2 public key + m2rsakey = M2Crypto.RSA.load_pub_key(filename) + self.m2key = M2Crypto.EVP.PKey() + self.m2key.assign_rsa(m2rsakey) + + # create an m2 x509 cert + m2name = M2Crypto.X509.X509_Name() + m2name.add_entry_by_txt(field="CN", type=0x1001, entry="junk", len=-1, loc=-1, set=0) + m2x509 = M2Crypto.X509.X509() + m2x509.set_pubkey(self.m2key) + m2x509.set_serial_number(0) + m2x509.set_issuer_name(m2name) + m2x509.set_subject_name(m2name) + ASN1 = M2Crypto.ASN1.ASN1_UTCTIME() + ASN1.set_time(500) + m2x509.set_not_before(ASN1) + m2x509.set_not_after(ASN1) + junk_key = Keypair(create=True) + m2x509.sign(pkey=junk_key.get_m2_pkey(), md="sha1") + + # convert the m2 x509 cert to a pyopenssl x509 + m2pem = m2x509.as_pem() + pyx509 = crypto.load_certificate(crypto.FILETYPE_PEM, m2pem) + + # get the pyopenssl pkey from the pyopenssl x509 + self.key = pyx509.get_pubkey() + + ## + # Load the public key from a string. No private key is loaded. + + def load_pubkey_from_string(self, string): + (f, fn) = tempfile.mkstemp() + os.write(f, string) + os.close(f) + self.load_pubkey_from_file(fn) + os.remove(fn) + + ## + # Return the private key in PEM format. + + def as_pem(self): + return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.key) + + ## + # Return an M2Crypto key object + + def get_m2_pkey(self): + if not self.m2key: + self.m2key = M2Crypto.EVP.load_key_string(self.as_pem()) + return self.m2key + + ## + # Returns a string containing the public key represented by this object. + + def get_pubkey_string(self): + m2pkey = self.get_m2_pkey() + return base64.b64encode(m2pkey.as_der()) + + ## + # Return an OpenSSL pkey object + + def get_openssl_pkey(self): + return self.key + + + ## + # Given another Keypair object, return TRUE if the two keys are the same. + + def is_same(self, pkey): + return self.as_pem() == pkey.as_pem() + + def sign_string(self, data): + k = self.get_m2_pkey() + k.sign_init() + k.sign_update(data) + return base64.b64encode(k.sign_final()) + + def verify_string(self, data, sig): + k = self.get_m2_pkey() + k.verify_init() + k.verify_update(data) + return M2Crypto.m2.verify_final(k.ctx, base64.b64decode(sig), k.pkey) + + def compute_hash(self, value): + return self.sign_string(str(value)) ## # The certificate class implements a general purpose X509 certificate, making @@ -216,301 +218,337 @@ class Keypair: # whether to save the parent certificates as well. class Certificate: - digest = "md5" - - data = None - cert = None - issuerKey = None - issuerSubject = None - parent = None - - separator="-----parent-----" - - ## - # Create a certificate object. - # - # @param create If create==True, then also create a blank X509 certificate. - # @param subject If subject!=None, then create a blank certificate and set - # it's subject name. - # @param string If string!=None, load the certficate from the string. - # @param filename If filename!=None, load the certficiate from the file. - - def __init__(self, create=False, subject=None, string=None, filename=None): - if create or subject: - self.create() - if subject: - self.set_subject(subject) - if string: - self.load_from_string(string) - if filename: - self.load_from_file(filename) - - ## - # Create a blank X509 certificate and store it in this object. - - def create(self): - self.cert = crypto.X509() - self.cert.set_serial_number(1) - self.cert.gmtime_adj_notBefore(0) - self.cert.gmtime_adj_notAfter(60*60*24*365*5) # five years - - ## - # Given a pyOpenSSL X509 object, store that object inside of this - # certificate object. - - def load_from_pyopenssl_x509(self, x509): - self.cert = x509 - - ## - # Load the certificate from a string - - def load_from_string(self, string): - # if it is a chain of multiple certs, then split off the first one and - # load it - parts = string.split(Certificate.separator, 1) - self.cert = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0]) - - # if there are more certs, then create a parent and let the parent load - # itself from the remainder of the string - if len(parts) > 1: - self.parent = self.__class__() - self.parent.load_from_string(parts[1]) - - ## - # Load the certificate from a file - - def load_from_file(self, filename): - file = open(filename) - string = file.read() - self.load_from_string(string) - - ## - # Save the certificate to a string. - # - # @param save_parents If save_parents==True, then also save the parent certificates. - - def save_to_string(self, save_parents=False): - string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert) - if save_parents and self.parent: - string = string + Certificate.separator + self.parent.save_to_string(save_parents) - return string - - ## - # Save the certificate to a file. - # @param save_parents If save_parents==True, then also save the parent certificates. - - def save_to_file(self, filename, save_parents=False): - string = self.save_to_string(save_parents=save_parents) - open(filename, 'w').write(string) - - ## - # Sets the issuer private key and name - # @param key Keypair object containing the private key of the issuer - # @param subject String containing the name of the issuer - # @param cert (optional) Certificate object containing the name of the issuer - - def set_issuer(self, key, subject=None, cert=None): - self.issuerKey = key - if subject: - # it's a mistake to use subject and cert params at the same time - assert(not cert) - if isinstance(subject, dict) or isinstance(subject, str): - req = crypto.X509Req() - reqSubject = req.get_subject() - if (isinstance(subject, dict)): - for key in reqSubject.keys(): - setattr(reqSubject, key, name[key]) - else: - setattr(reqSubject, "CN", subject) - subject = reqSubject - # subject is not valid once req is out of scope, so save req - self.issuerReq = req - if cert: - # if a cert was supplied, then get the subject from the cert - subject = cert.cert.get_subject() - assert(subject) - self.issuerSubject = subject - - ## - # Get the issuer name - - def get_issuer(self, which="CN"): - x = self.cert.get_issuer() - return getattr(x, which) - - ## - # Set the subject name of the certificate - - def set_subject(self, name): - req = crypto.X509Req() - subj = req.get_subject() - if (isinstance(name, dict)): - for key in name.keys(): - setattr(subj, key, name[key]) - else: - setattr(subj, "CN", name) - self.cert.set_subject(subj) - ## - # Get the subject name of the certificate - - def get_subject(self, which="CN"): - x = self.cert.get_subject() - return getattr(x, which) - - ## - # Get the public key of the certificate. - # - # @param key Keypair object containing the public key - - def set_pubkey(self, key): - assert(isinstance(key, Keypair)) - self.cert.set_pubkey(key.get_openssl_pkey()) - - ## - # Get the public key of the certificate. - # It is returned in the form of a Keypair object. - - def get_pubkey(self): - m2x509 = X509.load_cert_string(self.save_to_string()) - pkey = Keypair() - pkey.key = self.cert.get_pubkey() - pkey.m2key = m2x509.get_pubkey() - return pkey - - ## - # Add an X509 extension to the certificate. Add_extension can only be called - # once for a particular extension name, due to limitations in the underlying - # library. - # - # @param name string containing name of extension - # @param value string containing value of the extension - - def add_extension(self, name, critical, value): - ext = crypto.X509Extension (name, critical, value) - self.cert.add_extensions([ext]) - - ## - # Get an X509 extension from the certificate - - def get_extension(self, name): - # pyOpenSSL does not have a way to get extensions - m2x509 = X509.load_cert_string(self.save_to_string()) - value = m2x509.get_ext(name).get_value() - return value - - ## - # Set_data is a wrapper around add_extension. It stores the parameter str in - # the X509 subject_alt_name extension. Set_data can only be called once, due - # to limitations in the underlying library. - - def set_data(self, str): - # pyOpenSSL only allows us to add extensions, so if we try to set the - # same extension more than once, it will not work - if self.data != None: - raise "cannot set subjectAltName more than once" - self.data = str - self.add_extension("subjectAltName", 0, "URI:http://" + str) - - ## - # Return the data string that was previously set with set_data - - def get_data(self): - if self.data: - return self.data - - try: - uri = self.get_extension("subjectAltName") - except LookupError: - self.data = None - return self.data - - if not uri.startswith("URI:http://"): - raise "bad encoding in subjectAltName" - self.data = uri[11:] - return self.data - - ## - # Sign the certificate using the issuer private key and issuer subject previous set with set_issuer(). - - def sign(self): - assert self.cert != None - assert self.issuerSubject != None - assert self.issuerKey != None - self.cert.set_issuer(self.issuerSubject) - self.cert.sign(self.issuerKey.get_openssl_pkey(), self.digest) + digest = "md5" + + cert = None + issuerKey = None + issuerSubject = None + parent = None + + separator="-----parent-----" + + ## + # Create a certificate object. + # + # @param create If create==True, then also create a blank X509 certificate. + # @param subject If subject!=None, then create a blank certificate and set + # it's subject name. + # @param string If string!=None, load the certficate from the string. + # @param filename If filename!=None, load the certficiate from the file. + + def __init__(self, create=False, subject=None, string=None, filename=None, intermediate=None): + self.data = {} + if create or subject: + self.create() + if subject: + self.set_subject(subject) + if string: + self.load_from_string(string) + if filename: + self.load_from_file(filename) + + if intermediate: + self.set_intermediate_ca(intermediate) + + ## + # Create a blank X509 certificate and store it in this object. + + def create(self): + self.cert = crypto.X509() + self.cert.set_serial_number(3) + self.cert.gmtime_adj_notBefore(0) + self.cert.gmtime_adj_notAfter(60*60*24*365*5) # five years + + ## + # Given a pyOpenSSL X509 object, store that object inside of this + # certificate object. + + def load_from_pyopenssl_x509(self, x509): + self.cert = x509 + + ## + # Load the certificate from a string + + def load_from_string(self, string): + # if it is a chain of multiple certs, then split off the first one and + # load it (support for the ---parent--- tag as well as normal chained certs) + + string = string.strip() + + + if not string.startswith('-----'): + string = '-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----' % string + + parts = [] + + if string.count('-----BEGIN CERTIFICATE-----') > 1 and \ + string.count(Certificate.separator) == 0: + parts = string.split('-----END CERTIFICATE-----',1) + parts[0] += '-----END CERTIFICATE-----' + else: + parts = string.split(Certificate.separator, 1) + + self.cert = crypto.load_certificate(crypto.FILETYPE_PEM, parts[0]) + + # if there are more certs, then create a parent and let the parent load + # itself from the remainder of the string + if len(parts) > 1 and parts[1] != '': + self.parent = self.__class__() + self.parent.load_from_string(parts[1]) + + ## + # Load the certificate from a file + + def load_from_file(self, filename): + file = open(filename) + string = file.read() + self.load_from_string(string) + + ## + # Save the certificate to a string. + # + # @param save_parents If save_parents==True, then also save the parent certificates. + + def save_to_string(self, save_parents=True): + string = crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert) + if save_parents and self.parent: + string = string + self.parent.save_to_string(save_parents) + return string + + ## + # Save the certificate to a file. + # @param save_parents If save_parents==True, then also save the parent certificates. + + def save_to_file(self, filename, save_parents=True, filep=None): + string = self.save_to_string(save_parents=save_parents) + if filep: + f = filep + else: + f = open(filename, 'w') + f.write(string) + f.close() + + ## + # Save the certificate to a random file in /tmp/ + # @param save_parents If save_parents==True, then also save the parent certificates. + def save_to_random_tmp_file(self, save_parents=True): + fp, filename = mkstemp(suffix='cert', text=True) + fp = os.fdopen(fp, "w") + self.save_to_file(filename, save_parents=True, filep=fp) + return filename + + ## + # Sets the issuer private key and name + # @param key Keypair object containing the private key of the issuer + # @param subject String containing the name of the issuer + # @param cert (optional) Certificate object containing the name of the issuer + + def set_issuer(self, key, subject=None, cert=None): + self.issuerKey = key + if subject: + # it's a mistake to use subject and cert params at the same time + assert(not cert) + if isinstance(subject, dict) or isinstance(subject, str): + req = crypto.X509Req() + reqSubject = req.get_subject() + if (isinstance(subject, dict)): + for key in reqSubject.keys(): + setattr(reqSubject, key, subject[key]) + else: + setattr(reqSubject, "CN", subject) + subject = reqSubject + # subject is not valid once req is out of scope, so save req + self.issuerReq = req + if cert: + # if a cert was supplied, then get the subject from the cert + subject = cert.cert.get_subject() + assert(subject) + self.issuerSubject = subject + + ## + # Get the issuer name + + def get_issuer(self, which="CN"): + x = self.cert.get_issuer() + return getattr(x, which) + + ## + # Set the subject name of the certificate + + def set_subject(self, name): + req = crypto.X509Req() + subj = req.get_subject() + if (isinstance(name, dict)): + for key in name.keys(): + setattr(subj, key, name[key]) + else: + setattr(subj, "CN", name) + self.cert.set_subject(subj) + ## + # Get the subject name of the certificate + + def get_subject(self, which="CN"): + x = self.cert.get_subject() + return getattr(x, which) + + ## + # Get the public key of the certificate. + # + # @param key Keypair object containing the public key + + def set_pubkey(self, key): + assert(isinstance(key, Keypair)) + self.cert.set_pubkey(key.get_openssl_pkey()) + + ## + # Get the public key of the certificate. + # It is returned in the form of a Keypair object. + + def get_pubkey(self): + m2x509 = X509.load_cert_string(self.save_to_string()) + pkey = Keypair() + pkey.key = self.cert.get_pubkey() + pkey.m2key = m2x509.get_pubkey() + return pkey + + def set_intermediate_ca(self, val): + self.intermediate = val + if val: + self.add_extension('basicConstraints', 1, 'CA:TRUE') + + + + ## + # Add an X509 extension to the certificate. Add_extension can only be called + # once for a particular extension name, due to limitations in the underlying + # library. + # + # @param name string containing name of extension + # @param value string containing value of the extension + + def add_extension(self, name, critical, value): + ext = crypto.X509Extension (name, critical, value) + self.cert.add_extensions([ext]) + + ## + # Get an X509 extension from the certificate + + def get_extension(self, name): + # pyOpenSSL does not have a way to get extensions + m2x509 = X509.load_cert_string(self.save_to_string()) + value = m2x509.get_ext(name).get_value() + return value + + ## + # Set_data is a wrapper around add_extension. It stores the parameter str in + # the X509 subject_alt_name extension. Set_data can only be called once, due + # to limitations in the underlying library. + + def set_data(self, str, field='subjectAltName'): + # pyOpenSSL only allows us to add extensions, so if we try to set the + # same extension more than once, it will not work + if self.data.has_key(field): + raise "cannot set ", field, " more than once" + self.data[field] = str + self.add_extension(field, 0, str) + + ## + # Return the data string that was previously set with set_data + + def get_data(self, field='subjectAltName'): + if self.data.has_key(field): + return self.data[field] + + try: + uri = self.get_extension(field) + self.data[field] = uri + except LookupError: + return None + + return self.data[field] + + ## + # Sign the certificate using the issuer private key and issuer subject previous set with set_issuer(). + + def sign(self): + assert self.cert != None + assert self.issuerSubject != None + assert self.issuerKey != None + self.cert.set_issuer(self.issuerSubject) + self.cert.sign(self.issuerKey.get_openssl_pkey(), self.digest) ## # Verify the authenticity of a certificate. # @param pkey is a Keypair object representing a public key. If Pkey # did not sign the certificate, then an exception will be thrown. - def verify(self, pkey): - # pyOpenSSL does not have a way to verify signatures - m2x509 = X509.load_cert_string(self.save_to_string()) - m2pkey = pkey.get_m2_pkey() - # verify it - return m2x509.verify(m2pkey) - - # XXX alternatively, if openssl has been patched, do the much simpler: - # try: - # self.cert.verify(pkey.get_openssl_key()) - # return 1 - # except: - # return 0 - - ## - # Return True if pkey is identical to the public key that is contained in the certificate. - # @param pkey Keypair object - - def is_pubkey(self, pkey): - return self.get_pubkey().is_same(pkey) - - ## - # Given a certificate cert, verify that this certificate was signed by the - # public key contained in cert. Throw an exception otherwise. - # - # @param cert certificate object - - def is_signed_by_cert(self, cert): - k = cert.get_pubkey() - result = self.verify(k) - return result - - ## - # Set the parent certficiate. - # - # @param p certificate object. - - def set_parent(self, p): + def verify(self, pkey): + # pyOpenSSL does not have a way to verify signatures + m2x509 = X509.load_cert_string(self.save_to_string()) + m2pkey = pkey.get_m2_pkey() + # verify it + return m2x509.verify(m2pkey) + + # XXX alternatively, if openssl has been patched, do the much simpler: + # try: + # self.cert.verify(pkey.get_openssl_key()) + # return 1 + # except: + # return 0 + + ## + # Return True if pkey is identical to the public key that is contained in the certificate. + # @param pkey Keypair object + + def is_pubkey(self, pkey): + return self.get_pubkey().is_same(pkey) + + ## + # Given a certificate cert, verify that this certificate was signed by the + # public key contained in cert. Throw an exception otherwise. + # + # @param cert certificate object + + def is_signed_by_cert(self, cert): + k = cert.get_pubkey() + result = self.verify(k) + return result + + ## + # Set the parent certficiate. + # + # @param p certificate object. + + def set_parent(self, p): self.parent = p - ## - # Return the certificate object of the parent of this certificate. + ## + # Return the certificate object of the parent of this certificate. - def get_parent(self): + def get_parent(self): return self.parent - ## - # Verification examines a chain of certificates to ensure that each parent - # signs the child, and that some certificate in the chain is signed by a - # trusted certificate. - # - # Verification is a basic recursion:
-   #     if this_certificate was signed by trusted_certs:
-   #         return
-   #     else
-   #         return verify_chain(parent, trusted_certs)
-   # 
- # - # At each recursion, the parent is tested to ensure that it did sign the - # child. If a parent did not sign a child, then an exception is thrown. If - # the bottom of the recursion is reached and the certificate does not match - # a trusted root, then an exception is thrown. - # - # @param Trusted_certs is a list of certificates that are trusted. - # - - def verify_chain(self, trusted_certs = None): + ## + # Verification examines a chain of certificates to ensure that each parent + # signs the child, and that some certificate in the chain is signed by a + # trusted certificate. + # + # Verification is a basic recursion:
+    #     if this_certificate was signed by trusted_certs:
+    #         return
+    #     else
+    #         return verify_chain(parent, trusted_certs)
+    # 
+ # + # At each recursion, the parent is tested to ensure that it did sign the + # child. If a parent did not sign a child, then an exception is thrown. If + # the bottom of the recursion is reached and the certificate does not match + # a trusted root, then an exception is thrown. + # + # @param Trusted_certs is a list of certificates that are trusted. + # + + def verify_chain(self, trusted_certs = None): # Verify a chain of certificates. Each certificate must be signed by # the public key contained in it's parent. The chain is recursed # until a certificate is found that is signed by a trusted root. @@ -525,12 +563,7 @@ class Certificate: #print "TRUSTED CERT", trusted_cert.dump() #print "Client is signed by Trusted?", self.is_signed_by_cert(trusted_cert) if self.is_signed_by_cert(trusted_cert): - # make sure sure the trusted cert's hrn is a prefix of the - # signed cert's hrn - if not self.get_subject().startswith(trusted_cert.get_subject()): - raise GidParentHrn(trusted_cert.get_subject()) - #print self.get_subject(), "is signed by a root" - return + return trusted_cert # if there is no parent, then no way to verify the chain if not self.parent: diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py index d8451d74..f3fb180b 100644 --- a/sfa/trust/credential.py +++ b/sfa/trust/credential.py @@ -1,34 +1,161 @@ ## # Implements SFA Credentials # -# Credentials are layered on top of certificates, and are essentially a -# certificate that stores a tuple of parameters. +# Credentials are signed XML files that assign a subject gid privileges to an object gid ## ### $Id$ ### $URL$ -import xmlrpclib +import os +import datetime +from xml.dom.minidom import Document, parseString +from tempfile import mkstemp -from sfa.trust.certificate import Certificate +from sfa.trust.credential_legacy import CredentialLegacy from sfa.trust.rights import * from sfa.trust.gid import * from sfa.util.faults import * +from sfa.util.sfalogging import logger +from dateutil.parser import parse + + + +# Two years, in seconds +DEFAULT_CREDENTIAL_LIFETIME = 60 * 60 * 24 * 365 * 2 + + +# TODO: +# . make privs match between PG and PL +# . Need to add support for other types of credentials, e.g. tickets + + + +signature_template = \ +''' + + + + + + + + + + + + + + + + + + + + + + +''' + +## +# Convert a string into a bool + +def str2bool(str): + if str.lower() in ['yes','true','1']: + return True + return False + + + +## +# Utility function to get the text of an XML element + +def getTextNode(element, subele): + sub = element.getElementsByTagName(subele)[0] + if len(sub.childNodes) > 0: + return sub.childNodes[0].nodeValue + else: + return None + +## +# Utility function to set the text of an XML element +# It creates the element, adds the text to it, +# and then appends it to the parent. + +def append_sub(doc, parent, element, text): + ele = doc.createElement(element) + ele.appendChild(doc.createTextNode(text)) + parent.appendChild(ele) + +## +# Signature contains information about an xmlsec1 signature +# for a signed-credential +# + +class Signature(object): + + + def __init__(self, string=None): + self.refid = None + self.issuer_gid = None + self.xml = None + if string: + self.xml = string + self.decode() + + + + def get_refid(self): + if not self.refid: + self.decode() + return self.refid + + def get_xml(self): + if not self.xml: + self.encode() + return self.xml + + def set_refid(self, id): + self.refid = id + + def get_issuer_gid(self): + if not self.gid: + self.decode() + return self.gid + + def set_issuer_gid(self, gid): + self.gid = gid + + def decode(self): + doc = parseString(self.xml) + sig = doc.getElementsByTagName("Signature")[0] + self.set_refid(sig.getAttribute("xml:id").strip("Sig_")) + keyinfo = sig.getElementsByTagName("X509Data")[0] + szgid = getTextNode(keyinfo, "X509Certificate") + szgid = "-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----" % szgid + self.set_issuer_gid(GID(string=szgid)) + + def encode(self): + self.xml = signature_template % (self.get_refid(), self.get_refid()) + + ## -# Credential is a tuple: -# (GIDCaller, GIDObject, LifeTime, Privileges, Delegate) +# A credential provides a caller gid with privileges to an object gid. +# A signed credential is signed by the object's authority. +# +# Credentials are encoded in one of two ways. The legacy style places +# it in the subjectAltName of an X509 certificate. The new credentials +# are placed in signed XML. # -# These fields are encoded using xmlrpc into the subjectAltName field of the -# x509 certificate. Note: Call encode() once the fields have been filled in -# to perform this encoding. +# WARNING: +# In general, a signed credential obtained externally should +# not be changed else the signature is no longer valid. So, once +# you have loaded an existing signed credential, do not call encode() or sign() on it. + + +class Credential(object): -class Credential(Certificate): - gidCaller = None - gidObject = None - lifeTime = None - privileges = None - delegate = False ## # Create a Credential object @@ -39,7 +166,88 @@ class Credential(Certificate): # @param filename If filename!=None, load the credential from the file def __init__(self, create=False, subject=None, string=None, filename=None): - Certificate.__init__(self, create, subject, string, filename) + self.gidCaller = None + self.gidObject = None + self.expiration = None + self.privileges = None + self.issuer_privkey = None + self.issuer_gid = None + self.issuer_pubkey = None + self.parent = None + self.signature = None + self.xml = None + self.refid = None + self.legacy = None + + + + + # Check if this is a legacy credential, translate it if so + if string or filename: + if string: + str = string + elif filename: + str = file(filename).read() + + if str.strip().startswith("-----"): + self.legacy = CredentialLegacy(False,string=str) + self.translate_legacy(str) + else: + self.xml = str + self.decode() + + # Find an xmlsec1 path + self.xmlsec_path = '' + paths = ['/usr/bin','/usr/local/bin','/bin','/opt/bin','/opt/local/bin'] + for path in paths: + if os.path.isfile(path + '/' + 'xmlsec1'): + self.xmlsec_path = path + '/' + 'xmlsec1' + break + + + def get_signature(self): + if not self.signature: + self.decode() + return self.signature + + def set_signature(self, sig): + self.signature = sig + + + ## + # Translate a legacy credential into a new one + # + # @param String of the legacy credential + + def translate_legacy(self, str): + legacy = CredentialLegacy(False,string=str) + self.gidCaller = legacy.get_gid_caller() + self.gidObject = legacy.get_gid_object() + lifetime = legacy.get_lifetime() + if not lifetime: + # Default to two years + self.set_lifetime(DEFAULT_CREDENTIAL_LIFETIME) + else: + self.set_lifetime(int(lifetime)) + self.lifeTime = legacy.get_lifetime() + self.set_privileges(legacy.get_privileges()) + self.get_privileges().delegate_all_privileges(legacy.get_delegate()) + + ## + # Need the issuer's private key and name + # @param key Keypair object containing the private key of the issuer + # @param gid GID of the issuing authority + + def set_issuer_keys(self, privkey, gid): + self.issuer_privkey = privkey + self.issuer_gid = gid + + + ## + # Set this credential's parent + def set_parent(self, cred): + self.parent = cred + self.updateRefID() ## # set the GID of the caller @@ -79,34 +287,25 @@ class Credential(Certificate): # set the lifetime of this credential # # @param lifetime lifetime of credential + # . if lifeTime is a datetime object, it is used for the expiration time + # . if lifeTime is an integer value, it is considered the number of seconds + # remaining before expiration def set_lifetime(self, lifeTime): - self.lifeTime = lifeTime + if isinstance(lifeTime, int): + self.expiration = datetime.timedelta(seconds=lifeTime) + datetime.datetime.utcnow() + else: + self.expiration = lifeTime ## - # get the lifetime of the credential + # get the lifetime of the credential (in datetime format) def get_lifetime(self): - if not self.lifeTime: - self.decode() - return self.lifeTime - - ## - # set the delegate bit - # - # @param delegate boolean (True or False) - - def set_delegate(self, delegate): - self.delegate = delegate - - ## - # get the delegate bit - - def get_delegate(self): - if not self.delegate: + if not self.expiration: self.decode() - return self.delegate + return self.expiration + ## # set the privileges # @@ -117,6 +316,7 @@ class Credential(Certificate): self.privileges = RightList(string = privs) else: self.privileges = privs + ## # return the privileges as a RightList object @@ -134,88 +334,405 @@ class Credential(Certificate): def can_perform(self, op_name): rights = self.get_privileges() + if not rights: return False + return rights.can_perform(op_name) + ## - # Encode the attributes of the credential into a string and store that - # string in the alt-subject-name field of the X509 object. This should be - # done immediately before signing the credential. + # Encode the attributes of the credential into an XML string + # This should be done immediately before signing the credential. + # WARNING: + # In general, a signed credential obtained externally should + # not be changed else the signature is no longer valid. So, once + # you have loaded an existing signed credential, do not call encode() or sign() on it. def encode(self): - dict = {"gidCaller": None, - "gidObject": None, - "lifeTime": self.lifeTime, - "privileges": None, - "delegate": self.delegate} - if self.gidCaller: - dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True) - if self.gidObject: - dict["gidObject"] = self.gidObject.save_to_string(save_parents=True) + # Create the XML document + doc = Document() + signed_cred = doc.createElement("signed-credential") + doc.appendChild(signed_cred) + + # Fill in the bit + cred = doc.createElement("credential") + cred.setAttribute("xml:id", self.get_refid()) + signed_cred.appendChild(cred) + append_sub(doc, cred, "type", "privilege") + append_sub(doc, cred, "serial", "8") + append_sub(doc, cred, "owner_gid", self.gidCaller.save_to_string()) + append_sub(doc, cred, "owner_urn", self.gidCaller.get_urn()) + append_sub(doc, cred, "target_gid", self.gidObject.save_to_string()) + append_sub(doc, cred, "target_urn", self.gidObject.get_urn()) + append_sub(doc, cred, "uuid", "") + if not self.expiration: + self.set_lifetime(DEFAULT_CREDENTIAL_LIFETIME) + self.expiration = self.expiration.replace(microsecond=0) + append_sub(doc, cred, "expires", self.expiration.isoformat()) + privileges = doc.createElement("privileges") + cred.appendChild(privileges) + if self.privileges: - dict["privileges"] = self.privileges.save_to_string() - str = xmlrpclib.dumps((dict,), allow_none=True) - self.set_data(str) + rights = self.get_privileges() + for right in rights.rights: + priv = doc.createElement("privilege") + append_sub(doc, priv, "name", right.kind) + append_sub(doc, priv, "can_delegate", str(right.delegate).lower()) + privileges.appendChild(priv) + + # Add the parent credential if it exists + if self.parent: + sdoc = parseString(self.parent.get_xml()) + p_cred = doc.importNode(sdoc.getElementsByTagName("credential")[0], True) + p = doc.createElement("parent") + p.appendChild(p_cred) + cred.appendChild(p) - ## - # Retrieve the attributes of the credential from the alt-subject-name field - # of the X509 certificate. This is automatically done by the various - # get_* methods of this class and should not need to be called explicitly. - def decode(self): - data = self.get_data() - if data: - dict = xmlrpclib.loads(self.get_data())[0][0] + # Create the tag + signatures = doc.createElement("signatures") + signed_cred.appendChild(signatures) + + # Add any parent signatures + if self.parent: + for cur_cred in self.get_credential_list()[1:]: + sdoc = parseString(cur_cred.get_signature().get_xml()) + ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) + signatures.appendChild(ele) + + # Get the finished product + self.xml = doc.toxml() + + + def save_to_random_tmp_file(self): + fp, filename = mkstemp(suffix='cred', text=True) + fp = os.fdopen(fp, "w") + self.save_to_file(filename, save_parents=True, filep=fp) + return filename + + def save_to_file(self, filename, save_parents=True, filep=None): + if not self.xml: + self.encode() + if filep: + f = filep else: - dict = {} + f = open(filename, "w") + f.write(self.xml) + f.close() - self.lifeTime = dict.get("lifeTime", None) - self.delegate = dict.get("delegate", None) + def save_to_string(self, save_parents=True): + if not self.xml: + self.encode() + return self.xml - privStr = dict.get("privileges", None) - if privStr: - self.privileges = RightList(string = privStr) - else: - self.privileges = None + def get_refid(self): + if not self.refid: + self.refid = 'ref0' + return self.refid - gidCallerStr = dict.get("gidCaller", None) - if gidCallerStr: - self.gidCaller = GID(string=gidCallerStr) - else: - self.gidCaller = None + def set_refid(self, rid): + self.refid = rid - gidObjectStr = dict.get("gidObject", None) - if gidObjectStr: - self.gidObject = GID(string=gidObjectStr) - else: - self.gidObject = None + ## + # Figure out what refids exist, and update this credential's id + # so that it doesn't clobber the others. Returns the refids of + # the parents. + + def updateRefID(self): + if not self.parent: + self.set_refid('ref0') + return [] + + refs = [] + + next_cred = self.parent + while next_cred: + refs.append(next_cred.get_refid()) + if next_cred.parent: + next_cred = next_cred.parent + else: + next_cred = None + + + # Find a unique refid for this credential + rid = self.get_refid() + while rid in refs: + val = int(rid[3:]) + rid = "ref%d" % (val + 1) + + # Set the new refid + self.set_refid(rid) + + # Return the set of parent credential ref ids + return refs + + def get_xml(self): + if not self.xml: + self.encode() + return self.xml ## - # Verify that a chain of credentials is valid (see cert.py:verify). In - # addition to the checks for ordinary certificates, verification also - # ensures that the delegate bit was set by each parent in the chain. If - # a delegate bit was not set, then an exception is thrown. + # Sign the XML file created by encode() # - # Each credential must be a subset of the rights of the parent. + # WARNING: + # In general, a signed credential obtained externally should + # not be changed else the signature is no longer valid. So, once + # you have loaded an existing signed credential, do not call encode() or sign() on it. + + def sign(self): + if not self.issuer_privkey or not self.issuer_gid: + return + doc = parseString(self.get_xml()) + sigs = doc.getElementsByTagName("signatures")[0] + + # Create the signature template to be signed + signature = Signature() + signature.set_refid(self.get_refid()) + sdoc = parseString(signature.get_xml()) + sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) + sigs.appendChild(sig_ele) + + self.xml = doc.toxml() + + + # Split the issuer GID into multiple certificates if it's a chain + chain = GID(filename=self.issuer_gid) + gid_files = [] + while chain: + gid_files.append(chain.save_to_random_tmp_file(False)) + if chain.get_parent(): + chain = chain.get_parent() + else: + chain = None + + + # Call out to xmlsec1 to sign it + ref = 'Sig_%s' % self.get_refid() + filename = self.save_to_random_tmp_file() + signed = os.popen('%s --sign --node-id "%s" --privkey-pem %s,%s %s' \ + % (self.xmlsec_path, ref, self.issuer_privkey, ",".join(gid_files), filename)).read() + os.remove(filename) + + for gid_file in gid_files: + os.remove(gid_file) + + self.xml = signed + + # This is no longer a legacy credential + if self.legacy: + self.legacy = None + + # Update signatures + self.decode() - def verify_chain(self, trusted_certs = None): - # do the normal certificate verification stuff - Certificate.verify_chain(self, trusted_certs) + + + ## + # Retrieve the attributes of the credential from the XML. + # This is automatically called by the various get_* methods of + # this class and should not need to be called explicitly. + + def decode(self): + if not self.xml: + return + doc = parseString(self.xml) + sigs = [] + signed_cred = doc.getElementsByTagName("signed-credential") + + # Is this a signed-cred or just a cred? + if len(signed_cred) > 0: + cred = signed_cred[0].getElementsByTagName("credential")[0] + signatures = signed_cred[0].getElementsByTagName("signatures") + if len(signatures) > 0: + sigs = signatures[0].getElementsByTagName("Signature") + else: + cred = doc.getElementsByTagName("credential")[0] + + + + self.set_refid(cred.getAttribute("xml:id")) + self.set_lifetime(parse(getTextNode(cred, "expires"))) + self.gidCaller = GID(string=getTextNode(cred, "owner_gid")) + self.gidObject = GID(string=getTextNode(cred, "target_gid")) + + + # Process privileges + privs = cred.getElementsByTagName("privileges")[0] + rlist = RightList() + for priv in privs.getElementsByTagName("privilege"): + kind = getTextNode(priv, "name") + deleg = str2bool(getTextNode(priv, "can_delegate")) + if kind == '*': + # Convert * into the default privileges for the credential's type + _ , type = urn_to_hrn(self.gidObject.get_urn()) + rl = rlist.determine_rights(type, self.gidObject.get_urn()) + for r in rl.rights: + rlist.add(r) + else: + rlist.add(Right(kind.strip(), deleg)) + self.set_privileges(rlist) + + + # Is there a parent? + parent = cred.getElementsByTagName("parent") + if len(parent) > 0: + parent_doc = parent[0].getElementsByTagName("credential")[0] + parent_xml = parent_doc.toxml() + self.parent = Credential(string=parent_xml) + self.updateRefID() + + # Assign the signatures to the credentials + for sig in sigs: + Sig = Signature(string=sig.toxml()) + + for cur_cred in self.get_credential_list(): + if cur_cred.get_refid() == Sig.get_refid(): + cur_cred.set_signature(Sig) + + + ## + # Verify + # trusted_certs: A list of trusted GID filenames (not GID objects!) + # Chaining is not supported within the GIDs by xmlsec1. + # + # Verify that: + # . All of the signatures are valid and that the issuers trace back + # to trusted roots (performed by xmlsec1) + # . The XML matches the credential schema + # . That the issuer of the credential is the authority in the target's urn + # . In the case of a delegated credential, this must be true of the root + # . That all of the gids presented in the credential are valid + # . The credential is not expired + # + # -- For Delegates (credentials with parents) + # . The privileges must be a subset of the parent credentials + # . The privileges must have "can_delegate" set for each delegated privilege + # . The target gid must be the same between child and parents + # . The expiry time on the child must be no later than the parent + # . The signer of the child must be the owner of the parent + # + # -- Verify does *NOT* + # . ensure that an xmlrpc client's gid matches a credential gid, that + # must be done elsewhere + # + # @param trusted_certs: The certificates of trusted CA certificates + + def verify(self, trusted_certs): + if not self.xml: + self.decode() + trusted_cert_objects = [GID(filename=f) for f in trusted_certs] + + # Use legacy verification if this is a legacy credential + if self.legacy: + self.legacy.verify_chain(trusted_cert_objects) + if self.legacy.client_gid: + self.legacy.client_gid.verify_chain(trusted_cert_objects) + if self.legacy.object_gid: + self.legacy.object_gid.verify_chain(trusted_cert_objects) + return True + + # make sure it is not expired + if self.get_lifetime() < datetime.datetime.utcnow(): + raise CredentialNotVerifiable("credential is expired") + + # Verify the signatures + filename = self.save_to_random_tmp_file() + cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs]) + + # Verify the gids of this cred and of its parents + + + + for cur_cred in self.get_credential_list(): + cur_cred.get_gid_object().verify_chain(trusted_cert_objects) + cur_cred.get_gid_caller().verify_chain(trusted_cert_objects) + + + refs = [] + refs.append("Sig_%s" % self.get_refid()) + + parentRefs = self.updateRefID() + for ref in parentRefs: + refs.append("Sig_%s" % ref) + + for ref in refs: + verified = os.popen('%s --verify --node-id "%s" %s %s 2>&1' \ + % (self.xmlsec_path, ref, cert_args, filename)).read() + if not verified.strip().startswith("OK"): + raise CredentialNotVerifiable("xmlsec1 error: " + verified) + os.remove(filename) + + # Verify the parents (delegation) if self.parent: - # make sure the parent delegated rights to the child - if not self.parent.get_delegate(): - raise MissingDelegateBit(self.parent.get_subject()) + self.verify_parent(self.parent) + + # Make sure the issuer is the target's authority + self.verify_issuer() + return True - # make sure the rights given to the child are a subset of the - # parents rights - if not self.parent.get_privileges().is_superset(self.get_privileges()): - raise ChildRightsNotSubsetOfParent(self.get_subject() - + " " + self.parent.get_privileges().save_to_string() - + " " + self.get_privileges().save_to_string()) + ## + # Creates a list of the credential and its parents, with the root + # (original delegated credential) as the last item in the list + def get_credential_list(self): + cur_cred = self + list = [] + while cur_cred: + list.append(cur_cred) + if cur_cred.parent: + cur_cred = cur_cred.parent + else: + cur_cred = None + return list + + ## + # Make sure the credential's target gid was signed by (or is the same) as the entity that signed + # the original credential. + def verify_issuer(self): + root_cred = self.get_credential_list()[-1] + root_target_gid = root_cred.get_gid_object() + root_cred_signer = root_cred.get_signature().get_issuer_gid() + + if root_target_gid.is_signed_by_cert(root_cred_signer) or \ + root_target_gid.save_to_string() == root_cred_signer.save_to_string(): + pass + else: + raise CredentialNotVerifiable("Could not verify credential signer") + - return + ## + # -- For Delegates (credentials with parents) verify that: + # . The privileges must be a subset of the parent credentials + # . The privileges must have "can_delegate" set for each delegated privilege + # . The target gid must be the same between child and parents + # . The expiry time on the child must be no later than the parent + # . The signer of the child must be the owner of the parent + + def verify_parent(self, parent_cred): + # make sure the rights given to the child are a subset of the + # parents rights (and check delegate bits) + if not parent_cred.get_privileges().is_superset(self.get_privileges()): + raise ChildRightsNotSubsetOfParent( + self.parent.get_privileges().save_to_string() + " " + + self.get_privileges().save_to_string()) + + # make sure my target gid is the same as the parent's + if not parent_cred.get_gid_object().save_to_string() == \ + self.get_gid_object().save_to_string(): + raise CredentialNotVerifiable("target gid not equal between parent and child") + + # make sure my expiry time is <= my parent's + if not parent_cred.get_lifetime() >= self.get_lifetime(): + raise CredentialNotVerifiable("delegated credential expires after parent") + + # make sure my signer is the parent's caller + if not parent_cred.get_gid_caller().save_to_string(False) == \ + self.get_signature().get_issuer_gid().save_to_string(False): + raise CredentialNotVerifiable("delegated credential not signed by parent caller") + + if parent_cred.parent: + parent_cred.verify_parent(parent_cred.parent) ## # Dump the contents of a credential to stdout in human-readable format @@ -237,9 +754,8 @@ class Credential(Certificate): if gidObject: gidObject.dump(8, dump_parents) - print " delegate:", self.get_delegate() if self.parent and dump_parents: - print "PARENT", - self.parent.dump(dump_parents) + print "PARENT", + self.parent.dump_parents() diff --git a/sfa/trust/credential_legacy.py b/sfa/trust/credential_legacy.py new file mode 100644 index 00000000..c33ed6f3 --- /dev/null +++ b/sfa/trust/credential_legacy.py @@ -0,0 +1,247 @@ +## +# Implements SFA Credentials +# +# Credentials are layered on top of certificates, and are essentially a +# certificate that stores a tuple of parameters. +## + +### $Id: credential.py 17477 2010-03-25 16:49:34Z jkarlin $ +### $URL: svn+ssh://svn.planet-lab.org/svn/sfa/branches/geni-api/sfa/trust/credential.py $ + +import xmlrpclib + +from sfa.trust.certificate import Certificate +from sfa.trust.rights import * +from sfa.trust.gid import * +from sfa.util.faults import * +from sfa.util.sfalogging import * + +## +# Credential is a tuple: +# (GIDCaller, GIDObject, LifeTime, Privileges, Delegate) +# +# These fields are encoded using xmlrpc into the subjectAltName field of the +# x509 certificate. Note: Call encode() once the fields have been filled in +# to perform this encoding. + +class CredentialLegacy(Certificate): + gidCaller = None + gidObject = None + lifeTime = None + privileges = None + delegate = False + + ## + # Create a Credential object + # + # @param create If true, create a blank x509 certificate + # @param subject If subject!=None, create an x509 cert with the subject name + # @param string If string!=None, load the credential from the string + # @param filename If filename!=None, load the credential from the file + + def __init__(self, create=False, subject=None, string=None, filename=None): + Certificate.__init__(self, create, subject, string, filename) + + ## + # set the GID of the caller + # + # @param gid GID object of the caller + + def set_gid_caller(self, gid): + self.gidCaller = gid + # gid origin caller is the caller's gid by default + self.gidOriginCaller = gid + + ## + # get the GID of the object + + def get_gid_caller(self): + if not self.gidCaller: + self.decode() + return self.gidCaller + + ## + # set the GID of the object + # + # @param gid GID object of the object + + def set_gid_object(self, gid): + self.gidObject = gid + + ## + # get the GID of the object + + def get_gid_object(self): + if not self.gidObject: + self.decode() + return self.gidObject + + ## + # set the lifetime of this credential + # + # @param lifetime lifetime of credential + + def set_lifetime(self, lifeTime): + self.lifeTime = lifeTime + + ## + # get the lifetime of the credential + + def get_lifetime(self): + if not self.lifeTime: + self.decode() + return self.lifeTime + + ## + # set the delegate bit + # + # @param delegate boolean (True or False) + + def set_delegate(self, delegate): + self.delegate = delegate + + ## + # get the delegate bit + + def get_delegate(self): + if not self.delegate: + self.decode() + return self.delegate + + ## + # set the privileges + # + # @param privs either a comma-separated list of privileges of a RightList object + + def set_privileges(self, privs): + if isinstance(privs, str): + self.privileges = RightList(string = privs) + else: + self.privileges = privs + + ## + # return the privileges as a RightList object + + def get_privileges(self): + if not self.privileges: + self.decode() + return self.privileges + + ## + # determine whether the credential allows a particular operation to be + # performed + # + # @param op_name string specifying name of operation ("lookup", "update", etc) + + def can_perform(self, op_name): + rights = self.get_privileges() + if not rights: + return False + return rights.can_perform(op_name) + + ## + # Encode the attributes of the credential into a string and store that + # string in the alt-subject-name field of the X509 object. This should be + # done immediately before signing the credential. + + def encode(self): + dict = {"gidCaller": None, + "gidObject": None, + "lifeTime": self.lifeTime, + "privileges": None, + "delegate": self.delegate} + if self.gidCaller: + dict["gidCaller"] = self.gidCaller.save_to_string(save_parents=True) + if self.gidObject: + dict["gidObject"] = self.gidObject.save_to_string(save_parents=True) + if self.privileges: + dict["privileges"] = self.privileges.save_to_string() + str = xmlrpclib.dumps((dict,), allow_none=True) + self.set_data('URI:http://' + str) + + ## + # Retrieve the attributes of the credential from the alt-subject-name field + # of the X509 certificate. This is automatically done by the various + # get_* methods of this class and should not need to be called explicitly. + + def decode(self): + data = self.get_data().lstrip('URI:http://') + + if data: + dict = xmlrpclib.loads(data)[0][0] + else: + dict = {} + + self.lifeTime = dict.get("lifeTime", None) + self.delegate = dict.get("delegate", None) + + privStr = dict.get("privileges", None) + if privStr: + self.privileges = RightList(string = privStr) + else: + self.privileges = None + + gidCallerStr = dict.get("gidCaller", None) + if gidCallerStr: + self.gidCaller = GID(string=gidCallerStr) + else: + self.gidCaller = None + + gidObjectStr = dict.get("gidObject", None) + if gidObjectStr: + self.gidObject = GID(string=gidObjectStr) + else: + self.gidObject = None + + ## + # Verify that a chain of credentials is valid (see cert.py:verify). In + # addition to the checks for ordinary certificates, verification also + # ensures that the delegate bit was set by each parent in the chain. If + # a delegate bit was not set, then an exception is thrown. + # + # Each credential must be a subset of the rights of the parent. + + def verify_chain(self, trusted_certs = None): + # do the normal certificate verification stuff + Certificate.verify_chain(self, trusted_certs) + + if self.parent: + # make sure the parent delegated rights to the child + if not self.parent.get_delegate(): + raise MissingDelegateBit(self.parent.get_subject()) + + # make sure the rights given to the child are a subset of the + # parents rights + if not self.parent.get_privileges().is_superset(self.get_privileges()): + raise ChildRightsNotSubsetOfParent(self.get_subject() + + " " + self.parent.get_privileges().save_to_string() + + " " + self.get_privileges().save_to_string()) + + return + + ## + # Dump the contents of a credential to stdout in human-readable format + # + # @param dump_parents If true, also dump the parent certificates + + def dump(self, dump_parents=False): + print "CREDENTIAL", self.get_subject() + + print " privs:", self.get_privileges().save_to_string() + + print " gidCaller:" + gidCaller = self.get_gid_caller() + if gidCaller: + gidCaller.dump(8, dump_parents) + + print " gidObject:" + gidObject = self.get_gid_object() + if gidObject: + gidObject.dump(8, dump_parents) + + print " delegate:", self.get_delegate() + + if self.parent and dump_parents: + print "PARENT", + self.parent.dump(dump_parents) + diff --git a/sfa/trust/gid.py b/sfa/trust/gid.py index b1f2c823..99fa0314 100644 --- a/sfa/trust/gid.py +++ b/sfa/trust/gid.py @@ -8,9 +8,10 @@ import xmlrpclib import uuid - from sfa.trust.certificate import Certificate from sfa.util.namespace import * +from sfa.util.sfalogging import logger + ## # Create a new uuid. Returns the UUID as a string. @@ -18,8 +19,8 @@ def create_uuid(): return str(uuid.uuid4().int) ## -# GID is a tuplie: -# (uuid, hrn, public_key) +# GID is a tuple: +# (uuid, urn, public_key) # # UUID is a unique identifier and is created by the python uuid module # (or the utility function create_uuid() in gid.py). @@ -58,16 +59,22 @@ class GID(Certificate): def __init__(self, create=False, subject=None, string=None, filename=None, uuid=None, hrn=None, urn=None): Certificate.__init__(self, create, subject, string, filename) + if subject: + logger.info("subject: %s" % subject) if uuid: - self.uuid = uuid + self.uuid = int(uuid) if hrn: self.hrn = hrn + self.urn = hrn_to_urn(hrn, 'unknown') if urn: self.urn = urn self.hrn, type = urn_to_hrn(urn) def set_uuid(self, uuid): - self.uuid = uuid + if isinstance(uuid, str): + self.uuid = int(uuid) + else: + self.uuid = uuid def get_uuid(self): if not self.uuid: @@ -91,6 +98,12 @@ class GID(Certificate): self.decode() return self.urn + def get_type(self): + if not self.urn: + self.decode() + _, t = urn_to_hrn(self.urn) + return t + ## # Encode the GID fields and package them into the subject-alt-name field # of the X509 certificate. This must be called prior to signing the @@ -101,11 +114,16 @@ class GID(Certificate): urn = self.urn else: urn = hrn_to_urn(self.hrn, None) - - dict = {"uuid": self.uuid, - "urn": self.urn} - str = xmlrpclib.dumps((dict,)) - self.set_data(str) + + str = "URI:" + urn + + if self.uuid: + str += ", " + "URI:" + uuid.UUID(int=self.uuid).urn + + self.set_data(str, 'subjectAltName') + + + ## # Decode the subject-alt-name field of the X509 certificate into the @@ -113,12 +131,19 @@ class GID(Certificate): # functions in this class. def decode(self): - data = self.get_data() + data = self.get_data('subjectAltName') + dict = {} if data: - dict = xmlrpclib.loads(self.get_data())[0][0] - else: - dict = {} - + if data.lower().startswith('uri:http://'): + dict = xmlrpclib.loads(data[11:])[0][0] + else: + spl = data.split(', ') + for val in spl: + if val.lower().startswith('uri:urn:uuid:'): + dict['uuid'] = uuid.UUID(val[4:]).int + elif val.lower().startswith('uri:urn:publicid:idn+'): + dict['urn'] = val[4:] + self.uuid = dict.get("uuid", None) self.urn = dict.get("urn", None) self.hrn = dict.get("hrn", None) @@ -152,12 +177,19 @@ class GID(Certificate): def verify_chain(self, trusted_certs = None): # do the normal certificate verification stuff - Certificate.verify_chain(self, trusted_certs) - + trusted_root = Certificate.verify_chain(self, trusted_certs) + if self.parent: # make sure the parent's hrn is a prefix of the child's hrn if not self.get_hrn().startswith(self.parent.get_hrn()): raise GidParentHrn(self.parent.get_subject()) + else: + # make sure that the trusted root's hrn is a prefix of the child's + trusted_gid = GID(string=trusted_root.save_to_string()) + trusted_hrn = trusted_gid.get_hrn() + cur_hrn = self.get_hrn() + if not self.get_hrn().startswith(trusted_hrn): + raise GidParentHrn(trusted_hrn + " " + self.get_hrn()) return diff --git a/sfa/trust/hierarchy.py b/sfa/trust/hierarchy.py index b3e730f3..e277ec51 100644 --- a/sfa/trust/hierarchy.py +++ b/sfa/trust/hierarchy.py @@ -60,6 +60,12 @@ class AuthInfo: self.gid_filename = fn self.gid_object = None + def get_privkey_filename(self): + return self.privkey_filename + + def get_gid_filename(self): + return self.gid_filename + ## # Get the GID in the form of a GID object @@ -159,8 +165,9 @@ class Hierarchy: # create the parent authority if necessary parent_hrn = get_authority(hrn) - if (parent_hrn) and (not self.auth_exists(parent_hrn)) and (create_parents): - self.create_auth(parent_hrn, create_parents) + parent_urn = hrn_to_urn(parent_hrn, 'authority') + if (parent_hrn) and (not self.auth_exists(parent_urn)) and (create_parents): + self.create_auth(parent_urn, create_parents) (directory, gid_filename, privkey_filename, dbinfo_filename) = \ self.get_auth_filenames(hrn) @@ -296,18 +303,20 @@ class Hierarchy: cred.set_gid_caller(gid) cred.set_gid_object(gid) cred.set_privileges(kind) - cred.set_delegate(True) - cred.set_pubkey(auth_info.get_gid_object().get_pubkey()) + cred.get_privileges().delegate_all_privileges(True) + #cred.set_pubkey(auth_info.get_gid_object().get_pubkey()) parent_hrn = get_authority(hrn) if not parent_hrn or hrn == self.config.SFA_INTERFACE_HRN: # if there is no parent hrn, then it must be self-signed. this # is where we terminate the recursion - cred.set_issuer(auth_info.get_pkey_object(), hrn) + cred.set_issuer_keys(auth_info.get_privkey_filename(), auth_info.get_gid_filename()) else: # we need the parent's private key in order to sign this GID parent_auth_info = self.get_auth_info(parent_hrn) - cred.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn) + cred.set_issuer_keys(parent_auth_info.get_privkey_filename(), parent_auth_info.get_gid_filename()) + + cred.set_parent(self.get_auth_cred(parent_hrn, kind)) cred.encode() diff --git a/sfa/trust/rights.py b/sfa/trust/rights.py index 6d7fc85b..59324e8d 100644 --- a/sfa/trust/rights.py +++ b/sfa/trust/rights.py @@ -10,22 +10,28 @@ # allows "listslices", "listcomponentresources", etc. ## + + ## # privilege_table is a list of priviliges and what operations are allowed # per privilege. +# Note that "*" is a privilege granted by ProtoGENI slice authorities, and we +# give it access to the GENI AM calls privilege_table = {"authority": ["register", "remove", "update", "resolve", "list", "getcredential", "*"], "refresh": ["remove", "update"], "resolve": ["resolve", "list", "getcredential"], - "sa": ["getticket", "redeemslice", "redeemticket", "createslice", "deleteslice", "updateslice", - "getsliceresources", "getticket", "loanresources", "stopslice", "startslice", - "deleteslice", "resetslice", "listslices", "listnodes", "getpolicy"], - "embed": ["getticket", "redeemslice", "redeemticket", "createslice", "deleteslice", "updateslice", "getsliceresources"], + "sa": ["getticket", "redeemslice", "redeemticket", "createslice", "createsliver", "deleteslice", "deletesliver", "updateslice", + "getsliceresources", "getticket", "loanresources", "stopslice", "startslice", "renewsliver", + "deleteslice", "deletesliver", "resetslice", "listslices", "listnodes", "getpolicy", "sliverstatus"], + "embed": ["getticket", "redeemslice", "redeemticket", "createslice", "createsliver", "renewsliver", "deleteslice", "deletesliver", "updateslice", "sliverstatus", "getsliceresources", "shutdown"], "bind": ["getticket", "loanresources", "redeemticket"], - "control": ["updateslice", "createslice", "stopslice", "startslice", "deleteslice", "resetslice", "getsliceresources", "getgids"], + "control": ["updateslice", "createslice", "createsliver", "renewsliver", "sliverstatus", "stopslice", "startslice", "deleteslice", "deletesliver", "resetslice", "getsliceresources", "getgids"], "info": ["listslices", "listnodes", "getpolicy"], "ma": ["setbootstate", "getbootstate", "reboot", "getgids", "gettrustedcerts"], - "operator": ["gettrustedcerts", "getgids"]} + "operator": ["gettrustedcerts", "getgids"], + "*": ["createsliver", "deletesliver", "sliverstatus", "renewsliver", "shutdown"]} + ## @@ -50,11 +56,15 @@ def determine_rights(type, name): rl.add("resolve") rl.add("info") elif type == "sa": - rl.add("authority,sa") + rl.add("authority") + rl.add("sa") elif type == "ma": - rl.add("authority,ma") + rl.add("authority") + rl.add("ma") elif type == "authority": - rl.add("authority,sa,ma") + rl.add("authority") + rl.add("sa") + rl.add("ma") elif type == "slice": rl.add("refresh") rl.add("embed") @@ -72,50 +82,54 @@ def determine_rights(type, name): class Right: - ## - # Create a new right. - # - # @param kind is a string naming the right. For example "control" + ## + # Create a new right. + # + # @param kind is a string naming the right. For example "control" + + def __init__(self, kind, delegate=False): + self.kind = kind + self.delegate = delegate - def __init__(self, kind): - self.kind = kind + ## + # Test to see if this right object is allowed to perform an operation. + # Returns True if the operation is allowed, False otherwise. + # + # @param op_name is a string naming the operation. For example "listslices". - ## - # Test to see if this right object is allowed to perform an operation. - # Returns True if the operation is allowed, False otherwise. - # - # @param op_name is a string naming the operation. For example "listslices". + def can_perform(self, op_name): + allowed_ops = privilege_table.get(self.kind.lower(), None) + if not allowed_ops: + return False - def can_perform(self, op_name): - allowed_ops = privilege_table.get(self.kind.lower(), None) - if not allowed_ops: - return False + # if "*" is specified, then all ops are permitted + if "*" in allowed_ops: + return True - # if "*" is specified, then all ops are permitted - if "*" in allowed_ops: - return True + return (op_name.lower() in allowed_ops) - return (op_name.lower() in allowed_ops) + ## + # Test to see if this right is a superset of a child right. A right is a + # superset if every operating that is allowed by the child is also allowed + # by this object. + # + # @param child is a Right object describing the child right - ## - # Test to see if this right is a superset of a child right. A right is a - # superset if every operating that is allowed by the child is also allowed - # by this object. - # - # @param child is a Right object describing the child right + def is_superset(self, child): + my_allowed_ops = privilege_table.get(self.kind.lower(), None) + child_allowed_ops = privilege_table.get(child.kind.lower(), None) - def is_superset(self, child): - my_allowed_ops = privilege_table.get(self.kind.lower(), None) - child_allowed_ops = privilege_table.get(child.kind.lower(), None) + if not self.delegate: + return False - if "*" in my_allowed_ops: - return True + if "*" in my_allowed_ops: + return True - for right in child_allowed_ops: - if not right in my_allowed_ops: - return False + for right in child_allowed_ops: + if not right in my_allowed_ops: + return False - return True + return True ## # A RightList object represents a list of privileges. @@ -139,9 +153,9 @@ class RightList: # # @param right is either a Right object or a string describing the right - def add(self, right): + def add(self, right, delegate=False): if isinstance(right, str): - right = Right(kind = right) + right = Right(right, delegate) self.rights.append(right) ## @@ -156,7 +170,14 @@ class RightList: parts = string.split(",") for part in parts: - self.rights.append(Right(part)) + if ':' in part: + spl = part.split(':') + kind = spl[0].strip() + delegate = bool(int(spl[1])) + else: + kind = part.strip() + delegate = 0 + self.rights.append(Right(kind, bool(delegate))) ## # Save the rightlist object to a string. It is saved in the format of a @@ -165,7 +186,7 @@ class RightList: def save_to_string(self): right_names = [] for right in self.rights: - right_names.append(right.kind) + right_names.append('%s:%d' % (right.kind.strip(), right.delegate)) return ",".join(right_names) @@ -202,7 +223,29 @@ class RightList: ## - # Determine tje rights that an object should have. The rights are entirely + # set the delegate bit to 'delegate' on + # all privileges + # + # @param delegate boolean (True or False) + + def delegate_all_privileges(self, delegate): + for right in self.rights: + right.delegate = delegate + + ## + # true if all privileges have delegate bit set true + # false otherwise + + def get_all_delegate(self): + for right in self.rights: + if not right.delegate: + return False + return True + + + + ## + # Determine the rights that an object should have. The rights are entirely # dependent on the type of the object. For example, users automatically # get "refresh", "resolve", and "info". # @@ -224,11 +267,15 @@ class RightList: rl.add("resolve") rl.add("info") elif type == "sa": - rl.add("authority,sa") + rl.add("authority") + rl.add("sa") elif type == "ma": - rl.add("authority,ma") + rl.add("authority") + rl.add("ma") elif type == "authority": - rl.add("authority,sa,ma") + rl.add("authority") + rl.add("sa") + rl.add("ma") elif type == "slice": rl.add("refresh") rl.add("embed") diff --git a/sfa/trust/trustedroot.py b/sfa/trust/trustedroot.py index 90dfe462..2d4b89ef 100644 --- a/sfa/trust/trustedroot.py +++ b/sfa/trust/trustedroot.py @@ -34,3 +34,13 @@ class TrustedRootList: return gid_list + def get_file_list(self): + gid_file_list = [] + + file_list = os.listdir(self.basedir) + for gid_file in file_list: + fn = os.path.join(self.basedir, gid_file) + if os.path.isfile(fn): + gid_file_list.append(fn) + + return gid_file_list diff --git a/sfa/util/api.py b/sfa/util/api.py index 0c2b78a0..a7a07b61 100644 --- a/sfa/util/api.py +++ b/sfa/util/api.py @@ -142,6 +142,7 @@ class BaseAPI: Return a new instance of the specified method. """ # Look up method + print self.methods if method not in self.methods: raise SfaInvalidAPIMethod, method @@ -164,7 +165,8 @@ class BaseAPI: self.source = source return function(*args) - def handle(self, source, data): + + def handle(self, source, data, method_map): """ Handle an XML-RPC or SOAP request from the specified source. """ @@ -172,7 +174,10 @@ class BaseAPI: try: interface = xmlrpclib (args, method) = xmlrpclib.loads(data) + if method_map.has_key(method): + method = method_map[method] methodresponse = True + except Exception, e: if SOAPpy is not None: interface = SOAPpy diff --git a/sfa/util/faults.py b/sfa/util/faults.py index 8e5ece1c..5880fd90 100644 --- a/sfa/util/faults.py +++ b/sfa/util/faults.py @@ -95,7 +95,8 @@ class ExistingRecord(SfaFault): SfaFault.__init__(self, 111, faultString, extra) def __str__(self): return repr(self.value) - + + class NonexistingCredType(SfaFault): def __init__(self, value, extra = None): self.value = value @@ -105,7 +106,7 @@ class NonexistingCredType(SfaFault): return repr(self.value) class NonexistingFile(SfaFault): - def __init__(self, value): + def __init__(self, value, extra = None): self.value = value faultString = "Non existing file: %(value)s, " % locals() SfaFault.__init__(self, 111, faultString, extra) @@ -113,7 +114,7 @@ class NonexistingFile(SfaFault): return repr(self.value) class InvalidRPCParams(SfaFault): - def __init__(self, value): + def __init__(self, value, extra = None): self.value = value faultString = "Invalid RPC Params: %(value)s, " % locals() SfaFault.__init__(self, 102, faultString, extra) @@ -217,7 +218,15 @@ class CertNotSignedByParent(SfaFault): SfaFault.__init__(self, 103, faultString, extra) def __str__(self): return repr(self.value) - + +class GidParentHrn(SfaFault): + def __init__(self, value, extra = None): + self.value = value + faultString = "Cert URN is not an extension of its parent: %(value)s" % locals() + SfaFault.__init__(self, 103, faultString, extra) + def __str__(self): + return repr(self.value) + class GidInvalidParentHrn(SfaFault): def __init__(self, value, extra = None): self.value = value @@ -235,7 +244,7 @@ class SliverDoesNotExist(SfaFault): return repr(self.value) class BadRequestHash(xmlrpclib.Fault): - def __init__(self, hash = None): + def __init__(self, hash = None, extra = None): faultString = "bad request hash: " + str(hash) xmlrpclib.Fault.__init__(self, 902, faultString) @@ -270,3 +279,10 @@ class AccountNotEnabled(SfaFault): def __str__(self): return repr(self.value) +class CredentialNotVerifiable(SfaFault): + def __init__(self, value, extra = None): + self.value = value + faultString = "Unable to verify credential: %(value)s, " %locals() + SfaFault.__init__(self, 115, faultString, extra) + def __str__(self): + return repr(self.value) diff --git a/sfa/util/namespace.py b/sfa/util/namespace.py index e3a7536f..45ce06a7 100644 --- a/sfa/util/namespace.py +++ b/sfa/util/namespace.py @@ -9,7 +9,11 @@ def get_leaf(hrn): parts = hrn.split(".") return ".".join(parts[-1:]) -def get_authority(hrn): +def get_authority(xrn): + hrn, type = urn_to_hrn(xrn) + if type and type == 'authority': + return hrn + parts = hrn.split(".") return ".".join(parts[:-1]) @@ -85,7 +89,14 @@ def hrn_to_urn(hrn, type=None): authority = get_authority(hrn) name = get_leaf(hrn) - urn = "+".join([unicode(part).replace('.', ':') \ - for part in ['',authority,type,name]]) - + + if authority.startswith("plc"): + if type == None: + urn = "+".join(['',authority.replace('.',':'),name]) + else: + urn = "+".join(['',authority.replace('.',':'),type,name]) + + else: + urn = "+".join(['',authority,type,name]) + return URN_PREFIX + urn diff --git a/sfa/util/server.py b/sfa/util/server.py index 2ee43785..e6d3f3be 100644 --- a/sfa/util/server.py +++ b/sfa/util/server.py @@ -105,8 +105,8 @@ class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): # get arguments request = self.rfile.read(int(self.headers["content-length"])) remote_addr = (remote_ip, remote_port) = self.connection.getpeername() - self.api.remote_addr = remote_addr - response = self.api.handle(remote_addr, request) + self.api.remote_addr = remote_addr + response = self.api.handle(remote_addr, request, self.server.method_map) except Exception, fault: @@ -139,6 +139,7 @@ class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLR self.interface = None self.key_file = key_file self.cert_file = cert_file + self.method_map = {} # add cache to the request handler HandlerClass.cache = Cache() #for compatibility with python 2.4 (centos53) @@ -255,7 +256,7 @@ class SfaServer(threading.Thread): ## # Register functions that will be served by the XMLRPC server. This - # function should be overrided by each descendant class. + # function should be overridden by each descendant class. def register_functions(self): self.server.register_function(self.noop) diff --git a/sfa/util/sfalogging.py b/sfa/util/sfalogging.py index ff08c082..0e17ccba 100644 --- a/sfa/util/sfalogging.py +++ b/sfa/util/sfalogging.py @@ -1,17 +1,20 @@ import logging +import os #SFA access log initialization - +TMPDIR = os.getenv("TMPDIR", "/tmp") +SFA_HTTPD_ACCESS_LOGFILE = TMPDIR + "/" + 'sfa_httpd_access.log' SFA_ACCESS_LOGFILE='/var/log/sfa_access.log' -SFA_HTTPD_ACCESS_LOGFILE='/tmp/sfa_httpd_access.log' logger=logging.getLogger() logger.setLevel(logging.INFO) + try: - logfile=logging.FileHandler(SFA_ACCESS_LOGFILE) + logfile=logging.FileHandler(SFA_ACCESS_LOGFILE) except IOError: - # This is usually a permissions error becaue the file is - # owned by root, but httpd is trying to access it. - logfile=logging.FileHandler(SFA_HTTPD_ACCESS_LOGFILE) + # This is usually a permissions error becaue the file is + # owned by root, but httpd is trying to access it. + logfile=logging.FileHandler(SFA_HTTPD_ACCESS_LOGFILE) + formatter = logging.Formatter("%(asctime)s - %(message)s") logfile.setFormatter(formatter) logger.addHandler(logfile) diff --git a/sfa/util/table.py b/sfa/util/table.py index 83646273..6c68776d 100644 --- a/sfa/util/table.py +++ b/sfa/util/table.py @@ -28,7 +28,7 @@ class SfaTable(list): if record_filter: records = self.find(record_filter) - for record in reocrds: + for record in records: self.append(record) def exists(self):