X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=sfa%2Ftrust%2Fcredential.py;h=9d0fd2834aebd443f7ca5e5206150510843ec3a8;hb=4a2337e7f70cef81a8de37829aa63fc941c4b96e;hp=9ccf18fae3e7a08bb6fad7e3a277c79f6615beea;hpb=7b231b5c74cd6438336f960bf1eb49bad72c9060;p=sfa.git diff --git a/sfa/trust/credential.py b/sfa/trust/credential.py index 9ccf18fa..9d0fd283 100644 --- a/sfa/trust/credential.py +++ b/sfa/trust/credential.py @@ -44,14 +44,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.trust.credential_legacy import CredentialLegacy +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: @@ -196,14 +195,13 @@ class Signature(object): def encode(self): self.xml = signature_template % (self.get_refid(), self.get_refid()) - ## # 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. +# Credentials are encoded in one of two ways. +# The legacy style (now unsupported) places it in the subjectAltName of an X509 certificate. +# The new credentials are placed in signed XML. # # WARNING: # In general, a signed credential obtained externally should @@ -237,7 +235,7 @@ class Credential(object): # @param string If string!=None, load the credential from the string # @param filename If filename!=None, load the credential from the file # FIXME: create and subject are ignored! - def __init__(self, create=False, subject=None, string=None, filename=None): + def __init__(self, create=False, subject=None, string=None, filename=None, cred=None): self.gidCaller = None self.gidObject = None self.expiration = None @@ -249,18 +247,30 @@ class Credential(object): self.signature = None self.xml = None self.refid = None - self.legacy = None + self.type = None + self.version = None + + if cred: + if isinstance(cred, StringTypes): + string = cred + self.type = 'geni_sfa' + self.version = '1.0' + elif isinstance(cred, dict): + string = cred['geni_value'] + self.type = cred['geni_type'] + self.version = cred['geni_version'] + - # 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) + # if this is a legacy credential, write error and bail out + if isinstance (str, StringTypes) and str.strip().startswith("-----"): + logger.error("Legacy credentials not supported any more - giving up with %s..."%str[:10]) + return else: self.xml = str self.decode() @@ -274,9 +284,12 @@ class Credential(object): break def get_subject(self): + subject = "" if not self.gidObject: self.decode() - return self.gidObject.get_printable_subject() + if self.gidObject: + subject = self.gidObject.get_printable_subject() + return subject # sounds like this should be __repr__ instead ?? def get_summary_tostring(self): @@ -297,24 +310,6 @@ class Credential(object): 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: - self.set_expiration(datetime.datetime.utcnow() + datetime.timedelta(seconds=DEFAULT_CREDENTIAL_LIFETIME)) - else: - self.set_expiration(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 @@ -369,15 +364,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) @@ -388,11 +379,6 @@ class Credential(object): # at this point self.expiration is normalized as a datetime - DON'T call utcparse again return self.expiration - ## - # For legacy sake - def get_lifetime(self): - return self.get_expiration() - ## # set the privileges # @@ -440,19 +426,19 @@ class Credential(object): doc = Document() signed_cred = doc.createElement("signed-credential") -# Declare namespaces -# Note that credential/policy.xsd are really the PG schemas -# in a PL namespace. -# Note that delegation of credentials between the 2 only really works -# cause those schemas are identical. -# Also note these PG schemas talk about PG tickets and CM policies. + # Declare namespaces + # Note that credential/policy.xsd are really the PG schemas + # in a PL namespace. + # Note that delegation of credentials between the 2 only really works + # cause those schemas are identical. + # Also note these PG schemas talk about PG tickets and CM policies. signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.planet-lab.org/resources/sfa/credential.xsd") signed_cred.setAttribute("xsi:schemaLocation", "http://www.planet-lab.org/resources/sfa/ext/policy/1 http://www.planet-lab.org/resources/sfa/ext/policy/1/policy.xsd") -# PG says for those last 2: -# signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd") -# signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd") + # PG says for those last 2: + #signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd") + # signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd") doc.appendChild(signed_cred) @@ -468,9 +454,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) @@ -492,10 +479,10 @@ class Credential(object): # and we need to include those again here or else their signature # no longer matches on the credential. # We expect three of these, but here we copy them all: -# signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") -# and from PG (PL is equivalent, as shown above): -# signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd") -# signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd") + # signed_cred.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") + # and from PG (PL is equivalent, as shown above): + # signed_cred.setAttribute("xsi:noNamespaceSchemaLocation", "http://www.protogeni.net/resources/credential/credential.xsd") + # signed_cred.setAttribute("xsi:schemaLocation", "http://www.protogeni.net/resources/credential/ext/policy/1 http://www.protogeni.net/resources/credential/ext/policy/1/policy.xsd") # HOWEVER! # PL now also declares these, with different URLs, so @@ -525,7 +512,7 @@ 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 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) @@ -664,10 +651,6 @@ class Credential(object): self.xml = signed - # This is no longer a legacy credential - if self.legacy: - self.legacy = None - # Update signatures self.decode() @@ -680,6 +663,12 @@ class Credential(object): def decode(self): if not self.xml: return + + doc = None + try: + doc = parseString(self.xml) + except ExpatError,e: + raise CredentialNotVerifiable("Malformed credential") doc = parseString(self.xml) sigs = [] signed_cred = doc.getElementsByTagName("signed-credential") @@ -778,7 +767,7 @@ class Credential(object): self.decode() # validate against RelaxNG schema - if HAVELXML and not self.legacy: + if HAVELXML: if schema and os.path.exists(schema): tree = etree.parse(StringIO(self.xml)) schema_doc = etree.parse(schema) @@ -804,21 +793,12 @@ class Credential(object): trusted_cert_objects.append(GID(filename=f)) ok_trusted_certs.append(f) except Exception, exc: - logger.error("Failed to load trusted cert from %s: %r", f, exc) + logger.error("Failed to load trusted cert from %s: %r"%( f, exc)) trusted_certs = ok_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_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.get_summary_tostring(), self.expiration.strftime(SFATIME_FORMAT))) # Verify the signatures filename = self.save_to_random_tmp_file() @@ -1026,7 +1006,32 @@ class Credential(object): # only informative def get_filename(self): return getattr(self,'filename',None) - + + def actual_caller_hrn (self): + """a helper method used by some API calls like e.g. Allocate + to try and find out who really is the original caller + + This admittedly is a bit of a hack, please USE IN LAST RESORT + + This code uses a heuristic to identify a delegated credential + + A first known restriction if for traffic that gets through a slice manager + in this case the hrn reported is the one from the last SM in the call graph + which is not at all what is meant here""" + + caller_hrn = self.get_gid_caller().get_hrn() + issuer_hrn = self.get_signature().get_issuer_gid().get_hrn() + subject_hrn = self.get_gid_object().get_hrn() + # if we find that the caller_hrn is an immediate descendant of the issuer, then + # this seems to be a 'regular' credential + if caller_hrn.startswith(issuer_hrn): + actual_caller_hrn=caller_hrn + # 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)) + return actual_caller_hrn + ## # Dump the contents of a credential to stdout in human-readable format # @@ -1034,13 +1039,17 @@ class Credential(object): def dump (self, *args, **kwargs): print self.dump_string(*args, **kwargs) - - def dump_string(self, dump_parents=False): + # show_xml is ignored + def dump_string(self, dump_parents=False, show_xml=None): result="" result += "CREDENTIAL %s\n" % self.get_subject() filename=self.get_filename() if filename: result += "Filename %s\n"%filename - result += " privs: %s\n" % self.get_privileges().save_to_string() + privileges = self.get_privileges() + if privileges: + result += " privs: %s\n" % privileges.save_to_string() + else: + result += " privs: \n" gidCaller = self.get_gid_caller() if gidCaller: result += " gidCaller:\n" @@ -1050,6 +1059,9 @@ class Credential(object): print " gidIssuer:" self.get_signature().get_issuer_gid().dump(8, dump_parents) + if self.expiration: + print " expiration:", self.expiration.strftime(SFATIME_FORMAT) + gidObject = self.get_gid_object() if gidObject: result += " gidObject:\n"