From: Josh Karlin Date: Mon, 5 Apr 2010 21:12:32 +0000 (+0000) Subject: Lots of credential updates.. can actually perform list now. Delegation/parents/verif... X-Git-Tag: geni-apiv1-totrunk~81 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;ds=sidebyside;h=f87cfe2ab15ab19ad9e268c6165e1ae84b1f4d74;p=sfa.git Lots of credential updates.. can actually perform list now. Delegation/parents/verify_chain not quite done yet --- diff --git a/Makefile b/Makefile index 3d3fc9a0..65d63e7f 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ DESTDIR="/" ########## all: keyconvert python wsdl -install: keyconvert-install python-install wsdl-install xmlbuilder-install +install: keyconvert-install python-install wsdl-install xmlbuilder-install clean: keyconvert-clean python-clean wsdl-clean diff --git a/sfa/plc/sfa-import-plc.py b/sfa/plc/sfa-import-plc.py index 160ce77c..235877c9 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() if not level1_auth or level1_auth in ['']: level1_auth = None @@ -155,10 +156,11 @@ def main(): slices_dict = {} for slice in slices: slices_dict[slice['slice_id']] = slice - # start importing for site in sites: site_hrn = import_auth + "." + 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 8bcf4205..bf68b6c6 100644 --- a/sfa/plc/sfaImport.py +++ b/sfa/plc/sfaImport.py @@ -206,6 +206,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/trust/credential.py b/sfa/trust/credential.py index 403f7726..3b7dda05 100644 --- a/sfa/trust/credential.py +++ b/sfa/trust/credential.py @@ -11,7 +11,7 @@ import xmlrpclib import random import os - +import datetime import xml.dom.minidom from xml.dom.minidom import Document @@ -20,14 +20,14 @@ 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 * +from sfa.util.sfalogging import logger # TODO: -# . Need to verify credentials -# . Need to add privileges (make PG and PL privs work together and add delegation per privelege instead of global) -# . Need to fix lifetime -# . Need to make sure delegation is fully supported +# . Need to verify credential parents (delegation) +# . implement verify_chain +# . Need to manage ref #s +# . Need to add privileges (make PG and PL privs work together and add delegation per privilege instead of global) # . Need to test signature_template = \ @@ -58,11 +58,9 @@ signature_template = \ - - ## # Credential is a tuple: -# (GIDCaller, GIDObject, LifeTime, Privileges, DelegateBit) +# (GIDCaller, GIDObject, Expiration (in UTC time), Privileges, DelegateBit) # # These fields are encoded in one of two ways. The legacy style places # it in the subjectAltName of an X509 certificate. The new credentials @@ -72,7 +70,7 @@ signature_template = \ class Credential(object): gidCaller = None gidObject = None - lifeTime = None + expiration = None privileges = None delegate = False issuer_privkey = None @@ -80,7 +78,7 @@ class Credential(object): issuer_pubkey = None parent = None xml = None - + max_refid = 0 ## # Create a Credential object # @@ -102,9 +100,8 @@ class Credential(object): self.translate_legacy(str) else: self.xml = str - # Let's not mess around with invalid credentials - self.verify_chain() - + self.decode() + ## # Translate a legacy credential into a new one # @@ -114,6 +111,11 @@ class Credential(object): legacy = CredentialLegacy(False,string=str) self.gidCaller = legacy.get_gid_caller() self.gidObject = legacy.get_gid_object() + lifetime = legacy.get_lifetime() + if not lifetime: + self.set_lifetime(3600) + else: + self.set_lifetime(int(lifetime)) self.lifeTime = legacy.get_lifetime() self.privileges = legacy.get_privileges() self.delegate = legacy.get_delegate() @@ -127,6 +129,12 @@ class Credential(object): self.issuer_privkey = privkey self.issuer_gid = gid + def set_issuer(self, issuer): + issuer = issuer + + def set_subject(self, subject): + subject = subject + def set_pubkey(self, pubkey): self.issuer_pubkey = pubkey @@ -171,17 +179,23 @@ class Credential(object): # 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 minutes + # remaining before expiration def set_lifetime(self, lifeTime): - self.lifeTime = lifeTime + if isinstance(lifeTime, int): + self.expiration = datetime.timedelta(seconds=lifeTime*60) + datetime.datetime.utcnow() + else: + self.expiration = lifeTime ## - # get the lifetime of the credential + # get the lifetime of the credential (in minutes) def get_lifetime(self): if not self.lifeTime: self.decode() - return self.lifeTime + return self.expiration ## # set the delegate bit @@ -226,8 +240,10 @@ class Credential(object): def can_perform(self, op_name): rights = self.get_privileges() + if not rights: return False + return rights.can_perform(op_name) def append_sub(self, doc, parent, element, text): @@ -240,10 +256,13 @@ class Credential(object): # This should be done immediately before signing the credential. def encode(self): + p_sigs = None # Get information from the parent credential if self.parent: - p_doc = xml.dom.minidom.parseString(self.parent) + if not self.parent.xml: + self.parent.encode() + p_doc = xml.dom.minidom.parseString(self.parent.xml) p_signed_cred = p_doc.getElementsByTagName("signed-credential")[0] p_cred = p_signed_cred.getElementsByTagName("credential")[0] p_signatures = p_signed_cred.getElementsByTagName("signatures")[0] @@ -256,7 +275,9 @@ class Credential(object): # Fill in the bit - refid = "ref1" + refid = "ref%d" % (self.max_refid + 1) + self.max_refid += 1 + cred = doc.createElement("credential") cred.setAttribute("xml:id", refid) signed_cred.appendChild(cred) @@ -267,21 +288,26 @@ class Credential(object): self.append_sub(doc, cred, "target_gid", self.gidObject.save_to_string()) self.append_sub(doc, cred, "target_urn", self.gidObject.get_urn()) self.append_sub(doc, cred, "uuid", "") - self.append_sub(doc, cred, "expires", str(self.lifeTime)) - priveleges = doc.createElement("privileges") - cred.appendChild(priveleges) + if not self.expiration: + self.set_lifetime(3600) + self.expiration = self.expiration.replace(microsecond=0) + self.append_sub(doc, cred, "expires", self.expiration.isoformat()) + privileges = doc.createElement("privileges") + cred.appendChild(privileges) if self.privileges: rights = self.privileges.save_to_string().split(",") for right in rights: - priv = doc.createElement("privelege") - priv.append_sub(doc, priv, "name", right.strip()) - priv.append_sub(doc, priv, "can_delegate", str(self.delegate)) - priveleges.appendChild(priv) + priv = doc.createElement("privilege") + self.append_sub(doc, priv, "name", right.strip()) + self.append_sub(doc, priv, "can_delegate", str(self.delegate)) + privileges.appendChild(priv) # Add the parent credential if it exists if self.parent: - cred.appendChild(doc.createElement("parent").appendChild(p_cred)) + parent = doc.createElement("parent") + parent.appendChild(p_cred) + cred.appendChild(parent) signed_cred.appendChild(cred) @@ -295,6 +321,11 @@ class Credential(object): sdoc = xml.dom.minidom.parseString(sz_sig) sig_ele = doc.importNode(sdoc.getElementsByTagName("Signature")[0], True) signatures.appendChild(sig_ele) + + # Add any existing signatures from the parent credential + if p_sigs: + for sig in p_sigs: + signatures.appendChild(sig) # Add any parent signatures @@ -308,9 +339,19 @@ class Credential(object): #print doc.toprettyxml() + def save_to_random_tmp_file(self): + filename = "/tmp/cred_%d" % random.randint(0,999999999) + self.save_to_file(filename) + return filename + + def save_to_file(self, filename, save_parents=True): + if not self.xml: + self.encode() + f = open(filename, "w") + f.write(self.xml) + f.close() - - def save_to_string(self): + def save_to_string(self, save_parents=True): if not self.xml: self.encode() return self.xml @@ -320,14 +361,10 @@ class Credential(object): self.encode() # Call out to xmlsec1 to sign it - XMLSEC = '/usr/bin/xmlsec1' - - filename = "/tmp/cred_%d" % random.randint(0,999999999) - f = open(filename, "w") - f.write(self.xml); - f.close() + ref = 'Sig_ref%d' % self.max_refid + filename = self.save_to_random_tmp_file() signed = os.popen('/usr/bin/xmlsec1 --sign --node-id "%s" --privkey-pem %s,%s %s' \ - % ('ref1', self.issuer_privkey, self.issuer_gid, filename)).read() + % (ref, self.issuer_privkey, self.issuer_gid, filename)).read() os.remove(filename) self.xml = signed @@ -342,21 +379,26 @@ class Credential(object): # this class and should not need to be called explicitly. def decode(self): - p_doc = xml.dom.minidom.parseString(self.xml) - p_signed_cred = p_doc.getElementsByTagName("signed-credential")[0] - p_cred = p_signed_cred.getElementsByTagName("credential")[0] - p_signatures = p_signed_cred.getElementsByTagName("signatures")[0] - p_sigs = p_signatures.getElementsByTagName("Signature") - - self.lifeTime = self.getTextNode(p_cred, "expires") - self.gidCaller = GID(string=self.getTextNode(p_cred, "owner_gid")) - self.gidObject = GID(string=self.getTextNode(p_cred, "target_gid")) - privs = p_cred.getElementsByTagName("priveleges")[0] + doc = xml.dom.minidom.parseString(self.xml) + signed_cred = doc.getElementsByTagName("signed-credential")[0] + cred = signed_cred.getElementsByTagName("credential")[0] + signatures = signed_cred.getElementsByTagName("signatures")[0] + sigs = signatures.getElementsByTagName("Signature") + + + self.max_refid = int(cred.getAttribute("xml:id")[3:]) + sz_expires = self.getTextNode(cred, "expires") + if sz_expires != '': + self.expiration = datetime.datetime.strptime(sz_expires, '%Y-%m-%dT%H:%M:%S') + self.lifeTime = self.getTextNode(cred, "expires") + self.gidCaller = GID(string=self.getTextNode(cred, "owner_gid")) + self.gidObject = GID(string=self.getTextNode(cred, "target_gid")) + privs = cred.getElementsByTagName("privileges")[0] sz_privs = '' delegates = [] - for priv in privs.getElementsByTagName("privelege"): + for priv in privs.getElementsByTagName("privilege"): sz_privs += self.getTextNode(priv, "name") - sz_privs += ", " + sz_privs += "," delegates.append(self.getTextNode(priv, "can_delegate")) # Can we delegate? @@ -366,61 +408,34 @@ class Credential(object): # Make the rights list sz_privs.rstrip(", ") - self.priveleges = RightList(string=sz_privs) + self.privileges = RightList(string=sz_privs) self.delegate - - -## ## -## # 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 for the initial credential: - # 1. That the signature is valid - # 2. That the xml signer's certificate matches the object's certificate - # 3. That the urns match those in the gids + # For a simple credential (no delegation) verify.. + # . That the signature is valid for the credential's xml:id + # . That the signer's pub key matches the pub key of the target (object) + # . That the target/owner urns match those in the gids # - # Verify for the delegated credentials: - # 1. That the signature is valid + # @param trusted_certs: The certificates of trusted CA certificates - # 4. - # 3. That the object's certificate stays the s - # 2. That the GID of the + def verify(self, trusted_certs): + if not self.xml: + self.decode() - #def verify(self, trusted_certs = None): - + # Verify the sigatures + filename = self.save_to_random_tmp_file() + cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs]) + + ref = "Sig_ref%d" % self.max_refid + verified = os.popen('/usr/bin/xmlsec1 --verify --node-id "%s" %s %s 2>&1' \ + % (ref, cert_args, filename)).read() + + if not verified.strip().startswith("OK"): + raise CredentialNotVerifiable("xmlsec1 error: " + verified) + os.remove(filename) ## # Verify that a chain of credentials is valid (see cert.py:verify). In @@ -430,23 +445,26 @@ class Credential(object): # # 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) + def verify_chain(self, trusted_certs): + return - if self.parent: - # make sure the parent delegated rights to the child - if not self.parent.get_delegate(): - raise MissingDelegateBit(self.parent.get_subject()) + ## def verify_chain(self, trusted_certs = None): +## # do the normal certificate verification stuff +## Certificate.verify_chain(self, trusted_certs) - # 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()) +## if self.parent: +## # make sure the parent delegated rights to the child +## if not self.parent.get_delegate(): +## raise MissingDelegateBit(self.parent.get_subject()) - return +## # 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 diff --git a/sfa/trust/gid.py b/sfa/trust/gid.py index d62e43e1..da35d178 100644 --- a/sfa/trust/gid.py +++ b/sfa/trust/gid.py @@ -10,6 +10,8 @@ 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. @@ -57,6 +59,8 @@ 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 = int(uuid) if hrn: @@ -108,6 +112,8 @@ class GID(Certificate): str = szURN + ", " + szUUID self.set_data(str, 'subjectAltName') + + ## # Decode the subject-alt-name field of the X509 certificate into the diff --git a/sfa/trust/hierarchy.py b/sfa/trust/hierarchy.py index 3f0580fe..fad5c543 100644 --- a/sfa/trust/hierarchy.py +++ b/sfa/trust/hierarchy.py @@ -309,11 +309,11 @@ class Hierarchy: 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_keys(auth_info.get_pkey_object(), auth_info.get_gid_object()) + 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_privkey_filename(), parent_auth_info.get_gid_filename()) + 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 922ace71..16ef185d 100644 --- a/sfa/trust/rights.py +++ b/sfa/trust/rights.py @@ -10,6 +10,7 @@ # allows "listslices", "listcomponentresources", etc. ## + ## # privilege_table is a list of priviliges and what operations are allowed # per privilege. diff --git a/sfa/util/faults.py b/sfa/util/faults.py index 8e5ece1c..5e9cecce 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 @@ -270,3 +271,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/method.py b/sfa/util/method.py index 1df12457..5af4129d 100644 --- a/sfa/util/method.py +++ b/sfa/util/method.py @@ -77,6 +77,7 @@ class Method (object): raise SfaInvalidAPIMethod, methodname, self.api.interface # legacy code cannot be type-checked, due to the way Method.args() works + if not hasattr(self,"skip_typecheck"): (min_args, max_args, defaults) = self.args() @@ -87,6 +88,7 @@ class Method (object): for name, value, expected in zip(max_args, args, self.accepts): self.type_check(name, value, expected, args) + result = self.call(*args, **kwds) runtime = time.time() - start diff --git a/sfa/util/table.py b/sfa/util/table.py index bccda20d..d376f639 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): @@ -179,8 +179,9 @@ class SfaTable(list): def drop(self): try: - self.db.do('DROP TABLE IF EXISTS ' + self.tablename) + self.db.do('DROP TABLE IF EXISTS ' + self.tablename) except: + try: self.db.do('DROP TABLE ' + self.tablename) except: