X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=sfa%2Ftrust%2Fcredential.py;h=66401f896e8db071d3f386bf22645611bb9943ca;hb=cc1a4dcc8ab71ce8cd1a0bed24a9e7256e6e762b;hp=044d9185533ac0e29fece0bb7ba9afbadf716d79;hpb=c99c6ba30b0bed34b3750367996e8f4ea34b4028;p=sfa.git diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py index 044d9185..66401f89 100644 --- a/sfa/trust/credential.py +++ b/sfa/trust/credential.py @@ -26,7 +26,8 @@ # Credentials are signed XML files that assign a subject gid privileges to an object gid ## -import os +import os, os.path +import subprocess from types import StringTypes import datetime from StringIO import StringIO @@ -44,13 +45,13 @@ from xml.parsers.expat import ExpatError from sfa.util.faults import CredentialNotVerifiable, ChildRightsNotSubsetOfParent from sfa.util.sfalogging import logger -from sfa.util.sfatime import utcparse +from sfa.util.sfatime import utcparse, SFATIME_FORMAT from sfa.trust.rights import Right, Rights, determine_rights from sfa.trust.gid import GID from sfa.util.xrn import urn_to_hrn, hrn_authfor_hrn -# 2 weeks, in seconds -DEFAULT_CREDENTIAL_LIFETIME = 86400 * 31 +# 31 days, in seconds +DEFAULT_CREDENTIAL_LIFETIME = 86400 * 28 # TODO: @@ -268,9 +269,9 @@ class Credential(object): str = file(filename).read() # if this is a legacy credential, write error and bail out - if str.strip().startswith("-----"): + if isinstance (str, StringTypes) and str.strip().startswith("-----"): logger.error("Legacy credentials not supported any more - giving up with %s..."%str[:10]) - break + return else: self.xml = str self.decode() @@ -283,23 +284,23 @@ class Credential(object): self.xmlsec_path = path + '/' + 'xmlsec1' break - def get_subject(self): + def pretty_subject(self): subject = "" if not self.gidObject: self.decode() if self.gidObject: - subject = self.gidObject.get_printable_subject() + subject = self.gidObject.pretty_cert() return subject # sounds like this should be __repr__ instead ?? - def get_summary_tostring(self): + def pretty_cred(self): if not self.gidObject: self.decode() - obj = self.gidObject.get_printable_subject() - caller = self.gidCaller.get_printable_subject() + obj = self.gidObject.pretty_cert() + caller = self.gidCaller.pretty_cert() exp = self.get_expiration() # Summarize the rights too? The issuer? - return "[ Grant %s rights on %s until %s ]" % (caller, obj, exp) + return "[Cred. for {caller} rights on {obj} until {exp} ]".format(**locals()) def get_signature(self): if not self.signature: @@ -364,15 +365,11 @@ class Credential(object): # Expiration: an absolute UTC time of expiration (as either an int or string or datetime) # def set_expiration(self, expiration): - if isinstance(expiration, (int, float)): - self.expiration = datetime.datetime.fromtimestamp(expiration) - elif isinstance (expiration, datetime.datetime): - self.expiration = expiration - elif isinstance (expiration, StringTypes): - self.expiration = utcparse (expiration) + expiration_datetime = utcparse (expiration) + if expiration_datetime is not None: + self.expiration = expiration_datetime else: - logger.error ("unexpected input type in Credential.set_expiration") - + logger.error ("unexpected input %s in Credential.set_expiration"%expiration) ## # get the lifetime of the credential (always in datetime format) @@ -458,9 +455,10 @@ class Credential(object): append_sub(doc, cred, "target_urn", self.gidObject.get_urn()) append_sub(doc, cred, "uuid", "") if not self.expiration: + logger.debug("Creating credential valid for %s s"%DEFAULT_CREDENTIAL_LIFETIME) self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME)) self.expiration = self.expiration.replace(microsecond=0) - append_sub(doc, cred, "expires", self.expiration.isoformat()) + append_sub(doc, cred, "expires", self.expiration.strftime(SFATIME_FORMAT)) privileges = doc.createElement("privileges") cred.appendChild(privileges) @@ -515,7 +513,8 @@ class Credential(object): # Below throws InUse exception if we forgot to clone the attribute first oldAttr = signed_cred.setAttributeNode(attr.cloneNode(True)) if oldAttr and oldAttr.value != attr.value: - msg = "Delegating cred from owner %s to %s over %s:\n - Replaced attribute %s value '%s' with '%s'" % (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value) + msg = "Delegating cred from owner %s to %s over %s:\n - Replaced attribute %s value '%s' with '%s'" % \ + (self.parent.gidCaller.get_urn(), self.gidCaller.get_urn(), self.gidObject.get_urn(), oldAttr.name, oldAttr.value, attr.value) logger.warn(msg) #raise CredentialNotVerifiable("Can't encode new valid delegated credential: %s" % msg) @@ -777,7 +776,7 @@ class Credential(object): xmlschema = etree.XMLSchema(schema_doc) if not xmlschema.validate(tree): error = xmlschema.error_log.last_error - message = "%s: %s (line %s)" % (self.get_summary_tostring(), error.message, error.line) + message = "%s: %s (line %s)" % (self.pretty_cred(), error.message, error.line) raise CredentialNotVerifiable(message) if trusted_certs_required and trusted_certs is None: @@ -801,12 +800,12 @@ class Credential(object): # make sure it is not expired if self.get_expiration() < datetime.datetime.utcnow(): - raise CredentialNotVerifiable("Credential %s expired at %s" % (self.get_summary_tostring(), self.expiration.isoformat())) + raise CredentialNotVerifiable("Credential %s expired at %s" % \ + (self.pretty_cred(), + self.expiration.strftime(SFATIME_FORMAT))) # Verify the signatures filename = self.save_to_random_tmp_file() - if trusted_certs is not None: - cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs]) # If caller explicitly passed in None that means skip cert chain validation. # - Strange and not typical @@ -829,11 +828,25 @@ class Credential(object): if trusted_certs is None: break -# print "Doing %s --verify --node-id '%s' %s %s 2>&1" % \ -# (self.xmlsec_path, ref, cert_args, filename) - 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"): + # Thierry - jan 2015 + # up to fedora20 we used os.popen and checked that the output begins with OK + # turns out, with fedora21, there is extra input before this 'OK' thing + # looks like we're better off just using the exit code - that's what it is made for + #cert_args = " ".join(['--trusted-pem %s' % x for x in trusted_certs]) + #command = '{} --verify --node-id "{}" {} {} 2>&1'.\ + # format(self.xmlsec_path, ref, cert_args, filename) + command = [ self.xmlsec_path, '--verify', '--node-id', ref ] + for trusted in trusted_certs: + command += ["--trusted-pem", trusted ] + command += [ filename ] + logger.debug("Running " + " ".join(command)) + try: + verified = subprocess.check_output(command, stderr=subprocess.STDOUT) + logger.debug("xmlsec command returned {}".format(verified)) + if "OK\n" not in verified: + logger.warning("WARNING: xmlsec1 seemed to return fine but without a OK in its output") + except subprocess.CalledProcessError as e: + verified = e.output # xmlsec errors have a msg= which is the interesting bit. mstart = verified.find("msg=") msg = "" @@ -841,7 +854,9 @@ class Credential(object): mstart = mstart + 4 mend = verified.find('\\', mstart) msg = verified[mstart:mend] - raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s %s" % (self.get_summary_tostring(), ref, msg, verified.strip())) + logger.warning("Credential.verify - failed - xmlsec1 returned {}".format(verified.strip())) + raise CredentialNotVerifiable("xmlsec1 error verifying cred %s using Signature ID %s: %s" % \ + (self.pretty_cred(), ref, msg)) os.remove(filename) # Verify the parents (delegation) @@ -918,13 +933,14 @@ class Credential(object): if trusted_gids and len(trusted_gids) > 0: root_cred_signer.verify_chain(trusted_gids) else: - logger.debug("No trusted gids. Cannot verify that cred signer is signed by a trusted authority. Skipping that check.") + logger.debug("Cannot verify that cred signer is signed by a trusted authority. " + "No trusted gids. Skipping that check.") # See if the signer is an authority over the domain of the target. # There are multiple types of authority - accept them all here # Maybe should be (hrn, type) = urn_to_hrn(root_cred_signer.get_urn()) root_cred_signer_type = root_cred_signer.get_type() - if (root_cred_signer_type.find('authority') == 0): + if root_cred_signer_type.find('authority') == 0: #logger.debug('Cred signer is an authority') # signer is an authority, see if target is in authority's domain signerhrn = root_cred_signer.get_hrn() @@ -939,8 +955,11 @@ class Credential(object): # Give up, credential does not pass issuer verification - raise CredentialNotVerifiable("Could not verify credential owned by %s for object %s. Cred signer %s not the trusted authority for Cred target %s" % (self.gidCaller.get_urn(), self.gidObject.get_urn(), root_cred_signer.get_hrn(), root_target_gid.get_hrn())) - + raise CredentialNotVerifiable( + "Could not verify credential owned by {} for object {}. " + "Cred signer {} not the trusted authority for Cred target {}" + .format(self.gidCaller.get_hrn(), self.gidObject.get_hrn(), + root_cred_signer.get_hrn(), root_target_gid.get_hrn())) ## # -- For Delegates (credentials with parents) verify that: @@ -953,23 +972,46 @@ class Credential(object): # 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(("Parent cred ref %s rights " % parent_cred.get_refid()) + - self.parent.get_privileges().save_to_string() + (" not superset of delegated cred %s ref %s rights " % (self.get_summary_tostring(), self.get_refid())) + - self.get_privileges().save_to_string()) + message = ( + "Parent cred {} (ref {}) rights {} " + " not superset of delegated cred {} (ref {}) rights {}" + .format(parent_cred.pretty_cred(),parent_cred.get_refid(), + parent_cred.get_privileges().pretty_rights(), + self.pretty_cred(), self.get_refid(), + self.get_privileges().pretty_rights())) + logger.error(message) + logger.error("parent details {}".format(parent_cred.get_privileges().save_to_string())) + logger.error("self details {}".format(self.get_privileges().save_to_string())) + raise ChildRightsNotSubsetOfParent(message) # 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("Delegated cred %s: Target gid not equal between parent and child. Parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + message = ( + "Delegated cred {}: Target gid not equal between parent and child. Parent {}" + .format(self.pretty_cred(), parent_cred.pretty_cred())) + logger.error(message) + logger.error("parent details {}".format(parent_cred.save_to_string())) + logger.error("self details {}".format(self.save_to_string())) + raise CredentialNotVerifiable(message) # make sure my expiry time is <= my parent's if not parent_cred.get_expiration() >= self.get_expiration(): - raise CredentialNotVerifiable("Delegated credential %s expires after parent %s" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + raise CredentialNotVerifiable( + "Delegated credential {} expires after parent {}" + .format(self.pretty_cred(), parent_cred.pretty_cred())) # 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 %s not signed by parent %s's caller" % (self.get_summary_tostring(), parent_cred.get_summary_tostring())) + message = "Delegated credential {} not signed by parent {}'s caller"\ + .format(self.pretty_cred(), parent_cred.pretty_cred()) + logger.error(message) + logger.error("compare1 parent {}".format(parent_cred.get_gid_caller().pretty_cred())) + logger.error("compare1 parent details {}".format(parent_cred.get_gid_caller().save_to_string())) + logger.error("compare2 self {}".format(self.get_signature().get_issuer_gid().pretty_cred())) + logger.error("compare2 self details {}".format(self.get_signature().get_issuer_gid().save_to_string())) + raise CredentialNotVerifiable(message) # Recurse if parent_cred.parent: @@ -1032,7 +1074,8 @@ class Credential(object): # else this looks like a delegated credential, and the real caller is the issuer else: actual_caller_hrn=issuer_hrn - logger.info("actual_caller_hrn: caller_hrn=%s, issuer_hrn=%s, returning %s"%(caller_hrn,issuer_hrn,actual_caller_hrn)) + logger.info("actual_caller_hrn: caller_hrn=%s, issuer_hrn=%s, returning %s" + %(caller_hrn,issuer_hrn,actual_caller_hrn)) return actual_caller_hrn ## @@ -1045,7 +1088,7 @@ class Credential(object): # show_xml is ignored def dump_string(self, dump_parents=False, show_xml=None): result="" - result += "CREDENTIAL %s\n" % self.get_subject() + result += "CREDENTIAL %s\n" % self.pretty_subject() filename=self.get_filename() if filename: result += "Filename %s\n"%filename privileges = self.get_privileges() @@ -1063,7 +1106,7 @@ class Credential(object): self.get_signature().get_issuer_gid().dump(8, dump_parents) if self.expiration: - print " expiration:", self.expiration.isoformat() + print " expiration:", self.expiration.strftime(SFATIME_FORMAT) gidObject = self.get_gid_object() if gidObject: