-#
-# SfaAPI authentication
-#
-import sys
-
-from sfa.util.faults import InsufficientRights, MissingCallerGID, MissingTrustedRoots, PermissionError, \
- BadRequestHash, ConnectionKeyGIDMismatch, SfaPermissionDenied
-from sfa.util.sfalogging import logger
-from sfa.util.config import Config
-from sfa.util.xrn import get_authority
-
-from sfa.trust.gid import GID
-from sfa.trust.rights import Rights
-from sfa.trust.certificate import Keypair, Certificate
-from sfa.trust.credential import Credential
-from sfa.trust.trustedroots import TrustedRoots
-from sfa.trust.hierarchy import Hierarchy
-from sfa.trust.sfaticket import SfaTicket
-
-
-class Auth:
- """
- Credential based authentication
- """
-
- def __init__(self, peer_cert = None, config = None ):
- self.peer_cert = peer_cert
- self.hierarchy = Hierarchy()
- if not config:
- self.config = Config()
- self.load_trusted_certs()
-
- def load_trusted_certs(self):
- self.trusted_cert_list = TrustedRoots(self.config.get_trustedroots_dir()).get_list()
- self.trusted_cert_file_list = TrustedRoots(self.config.get_trustedroots_dir()).get_file_list()
-
-
-
- def checkCredentials(self, creds, operation, hrn = None):
- valid = []
- if not isinstance(creds, list):
- creds = [creds]
- logger.debug("Auth.checkCredentials with %d creds"%len(creds))
- for cred in creds:
- try:
- self.check(cred, operation, hrn)
- valid.append(cred)
- except:
- cred_obj=Credential(string=cred)
- logger.debug("failed to validate credential - dump=%s"%cred_obj.dump_string(dump_parents=True))
- 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):
- """
- Check the credential against the peer cert (callerGID included
- in the credential matches the caller that is connected to the
- HTTPS connection, check if the credential was signed by a
- trusted cert and check if the credential is allowed to perform
- the specified operation.
- """
- self.client_cred = Credential(string = cred)
- self.client_gid = self.client_cred.get_gid_caller()
- self.object_gid = self.client_cred.get_gid_object()
-
- # make sure the client_gid is not blank
- if not self.client_gid:
- raise MissingCallerGID(self.client_cred.get_subject())
-
- # validate the client cert if it exists
- if self.peer_cert:
- self.verifyPeerCert(self.peer_cert, self.client_gid)
-
- # make sure the client is allowed to perform the operation
- if operation:
- if not self.client_cred.can_perform(operation):
- raise InsufficientRights(operation)
-
- if self.trusted_cert_list:
- self.client_cred.verify(self.trusted_cert_file_list, self.config.SFA_CREDENTIAL_SCHEMA)
- 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]
- if hrn and self.client_gid.get_hrn() not in trusted_peers:
- target_hrn = self.object_gid.get_hrn()
- if not hrn == target_hrn:
- raise PermissionError("Target hrn: %s doesn't match specified hrn: %s " % \
- (target_hrn, hrn) )
- return True
-
- def check_ticket(self, ticket):
- """
- Check if the tickt was signed by a trusted cert
- """
- if self.trusted_cert_list:
- client_ticket = SfaTicket(string=ticket)
- client_ticket.verify_chain(self.trusted_cert_list)
- else:
- raise MissingTrustedRoots(self.config.get_trustedroots_dir())
-
- return True
-
- def verifyPeerCert(self, cert, gid):
- # make sure the client_gid matches client's certificate
- if not cert.is_pubkey(gid.get_pubkey()):
- raise ConnectionKeyGIDMismatch(gid.get_subject()+":"+cert.get_subject())
-
- def verifyGidRequestHash(self, gid, hash, arglist):
- key = gid.get_pubkey()
- if not key.verify_string(str(arglist), hash):
- raise BadRequestHash(hash)
-
- def verifyCredRequestHash(self, cred, hash, arglist):
- gid = cred.get_gid_caller()
- self.verifyGidRequestHash(gid, hash, arglist)
-
- def validateGid(self, gid):
- if self.trusted_cert_list:
- gid.verify_chain(self.trusted_cert_list)
-
- def validateCred(self, cred):
- if self.trusted_cert_list:
- cred.verify(self.trusted_cert_file_list)
-
- def authenticateGid(self, gidStr, argList, requestHash=None):
- gid = GID(string = gidStr)
- self.validateGid(gid)
- # request_hash is optional
- if requestHash:
- self.verifyGidRequestHash(gid, requestHash, argList)
- return gid
-
- def authenticateCred(self, credStr, argList, requestHash=None):
- cred = Credential(string = credStr)
- self.validateCred(cred)
- # request hash is optional
- if requestHash:
- self.verifyCredRequestHash(cred, requestHash, argList)
- return cred
-
- def authenticateCert(self, certStr, requestHash):
- cert = Certificate(string=certStr)
- # xxx should be validateCred ??
- self.validateCert(self, cert)
-
- def gidNoop(self, gidStr, value, requestHash):
- self.authenticateGid(gidStr, [gidStr, value], requestHash)
- return value
-
- def credNoop(self, credStr, value, requestHash):
- self.authenticateCred(credStr, [credStr, value], requestHash)
- return value
-
- def verify_cred_is_me(self, credential):
- is_me = False
- cred = Credential(string=credential)
- caller_gid = cred.get_gid_caller()
- caller_hrn = caller_gid.get_hrn()
- if caller_hrn != self.config.SFA_INTERFACE_HRN:
- raise SfaPermissionDenied(self.config.SFA_INTEFACE_HRN)
-
- return
-
- def get_auth_info(self, auth_hrn):
- """
- Given an authority name, return the information for that authority.
- This is basically a stub that calls the hierarchy module.
-
- @param auth_hrn human readable name of authority
- """
-
- return self.hierarchy.get_auth_info(auth_hrn)
-
-
- def veriry_auth_belongs_to_me(self, name):
- """
- Verify that an authority belongs to our hierarchy.
- This is basically left up to the implementation of the hierarchy
- module. If the specified name does not belong, ane exception is
- thrown indicating the caller should contact someone else.
-
- @param auth_name human readable name of authority
- """
-
- # get auth info will throw an exception if the authority doesnt exist
- self.get_auth_info(name)
-
-
- def verify_object_belongs_to_me(self, name):
- """
- Verify that an object belongs to our hierarchy. By extension,
- this implies that the authority that owns the object belongs
- to our hierarchy. If it does not an exception is thrown.
-
- @param name human readable name of object
- """
- auth_name = self.get_authority(name)
- if not auth_name:
- auth_name = name
- if name == self.config.SFA_INTERFACE_HRN:
- return
- self.verify_auth_belongs_to_me(auth_name)
-
- def verify_auth_belongs_to_me(self, name):
- # get auth info will throw an exception if the authority doesnt exist
- self.get_auth_info(name)
-
-
- def verify_object_permission(self, name):
- """
- Verify that the object gid that was specified in the credential
- allows permission to the object 'name'. This is done by a simple
- prefix test. For example, an object_gid for plc.arizona would
- match the objects plc.arizona.slice1 and plc.arizona.
-
- @param name human readable name to test
- """
- object_hrn = self.object_gid.get_hrn()
- if object_hrn == name:
- return
- if name.startswith(object_hrn + "."):
- return
- #if name.startswith(get_authority(name)):
- #return
-
- raise PermissionError(name)
-
- def determine_user_rights(self, caller_hrn, record):
- """
- Given a user credential and a record, determine what set of rights the
- user should have to that record.
-
- This is intended to replace determine_rights() and
- verify_cancreate_credential()
- """
-
- rl = Rights()
- type = record['type']
-
-
- if type=="slice":
- researchers = record.get("researcher", [])
- pis = record.get("PI", [])
- if (caller_hrn in researchers + pis):
- rl.add("refresh")
- rl.add("embed")
- rl.add("bind")
- rl.add("control")
- rl.add("info")
-
- elif type == "authority":
- pis = record.get("PI", [])
- operators = record.get("operator", [])
- if (caller_hrn == self.config.SFA_INTERFACE_HRN):
- rl.add("authority")
- rl.add("sa")
- rl.add("ma")
- if (caller_hrn in pis):
- rl.add("authority")
- rl.add("sa")
- if (caller_hrn in operators):
- rl.add("authority")
- rl.add("ma")
-
- elif type == "user":
- rl.add("refresh")
- rl.add("resolve")
- rl.add("info")
-
- elif type == "node":
- rl.add("operator")
-
- return rl
-
- def verify_cancreate_credential(self, src_cred, record):
- """
- Verify that a user can retrive a particular type of credential.
- For slices, the user must be on the researcher list. For SA and
- MA the user must be on the pi and operator lists respectively
- """
-
- type = record.get_type()
- cred_object_hrn = src_cred.get_gid_object().get_hrn()
- if cred_object_hrn in [self.config.SFA_REGISTRY_ROOT_AUTH]:
- return
- if type=="slice":
- researchers = record.get("researcher", [])
- if not (cred_object_hrn in researchers):
- raise PermissionError(cred_object_hrn + " is not in researcher list for " + record.get_name())
- elif type == "sa":
- pis = record.get("pi", [])
- if not (cred_object_hrn in pis):
- raise PermissionError(cred_object_hrn + " is not in pi list for " + record.get_name())
- elif type == "ma":
- operators = record.get("operator", [])
- if not (cred_object_hrn in operators):
- raise PermissionError(cred_object_hrn + " is not in operator list for " + record.get_name())
-
- def get_authority(self, hrn):
- return get_authority(hrn)
-
- def filter_creds_by_caller(self, creds, caller_hrn_list):
- """
- Returns a list of creds who's gid caller matches the
- specified caller hrn
- """
- if not isinstance(creds, list):
- creds = [creds]
- creds = []
- if not isinstance(caller_hrn_list, list):
- caller_hrn_list = [caller_hrn_list]
- for cred in creds:
- try:
- tmp_cred = Credential(string=cred)
- if tmp_cred.get_gid_caller().get_hrn() in [caller_hrn_list]:
- creds.append(cred)
- except: pass
- return creds
-
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!--\r
+ \r
+ Copyright (c) 2014 Raytheon BBN Technologies\r
+ \r
+ Permission is hereby granted, free of charge, to any person obtaining\r
+ a copy of this software and/or hardware specification (the "Work") to\r
+ deal in the Work without restriction, including without limitation the\r
+ rights to use, copy, modify, merge, publish, distribute, sublicense,\r
+ and/or sell copies of the Work, and to permit persons to whom the Work\r
+ is furnished to do so, subject to the following conditions:\r
+\r
+ The above copyright notice and this permission notice shall be\r
+ included in all copies or substantial portions of the Work.\r
+ \r
+ THE WORK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS\r
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\r
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\r
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,\r
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+ OUT OF OR IN CONNECTION WITH THE WORK OR THE USE OR OTHER DEALINGS\r
+ IN THE WORK.\r
+\r
+ Portions have this copyright:\r
+\r
+ GENIPUBLIC-COPYRIGHT\r
+ Copyright (c) 2008-2009 University of Utah and the Flux Group.\r
+ All rights reserved.\r
+ \r
+-->\r
+<!--\r
+ GENI credential and privilege specification. The key points:\r
+ \r
+ * A credential is a set of privileges or a Ticket, each with a flag\r
+ to indicate delegation is permitted. Or an ABAC RT0 statement.\r
+ * A credential is signed and the signature included in the body of the\r
+ document.\r
+ * To support delegation, a credential will include its parent, and that\r
+ blob will be signed. So, there will be multiple signatures in the\r
+ document, each with a reference to the credential it signs.\r
+ \r
+ Default namespace = "http://www.geni.net/resources/credential/2"\r
+-->\r
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" xmlns:sig="http://www.w3.org/2000/09/xmldsig#">\r
+ <xs:include schemaLocation="protogeni-rspec-common.xsd"/>\r
+ <xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="sig.xsd"/>\r
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="xml.xsd"/>\r
+ <xs:group name="anyelementbody">\r
+ <xs:sequence>\r
+ <xs:any minOccurs="0" maxOccurs="unbounded" processContents="skip"/>\r
+ </xs:sequence>\r
+ </xs:group>\r
+ <xs:attributeGroup name="anyelementbody">\r
+ <xs:anyAttribute processContents="skip"/>\r
+ </xs:attributeGroup>\r
+ <!-- This is where we get the definition of RSpec from -->\r
+ <xs:element name="privilege">\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element ref="name"/>\r
+ <xs:element name="can_delegate" type="xs:boolean"/>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="name">\r
+ <xs:simpleType>\r
+ <xs:restriction base="xs:string">\r
+ <xs:minLength value="1"/>\r
+ </xs:restriction>\r
+ </xs:simpleType>\r
+ </xs:element>\r
+ <xs:element name="privileges"> <!-- For type 'privilege' only -->\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="privilege"/>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="capability">\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element ref="name"/>\r
+ <xs:element name="can_delegate">\r
+ <xs:simpleType>\r
+ <xs:restriction base="xs:token">\r
+ <xs:enumeration value="0"/>\r
+ <xs:enumeration value="1"/>\r
+ </xs:restriction>\r
+ </xs:simpleType>\r
+ </xs:element>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="capabilities"> <!-- For type 'capability' only -->\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="capability"/>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="ticket"> <!-- For type 'ticket' only -->\r
+ <xs:complexType mixed="true">\r
+ <xs:sequence>\r
+ <xs:element name="can_delegate" type="xs:boolean">\r
+ <xs:annotation>\r
+ <xs:documentation>Can the ticket be delegated?</xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element ref="redeem_before"/>\r
+ <xs:group ref="anyelementbody">\r
+ <xs:annotation>\r
+ <xs:documentation>A desciption of the resources that are being promised</xs:documentation>\r
+ </xs:annotation>\r
+ </xs:group>\r
+ </xs:sequence>\r
+ <xs:attributeGroup ref="anyelementbody"/>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="redeem_before" type="xs:dateTime">\r
+ <xs:annotation>\r
+ <xs:documentation>The ticket must be "cashed in" by this date </xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+\r
+ <!-- Elements used for type 'abac'. See http://groups.geni.net/geni/wiki/TIEDABACCredential -->\r
+ <xs:element name="ABACprincipal">\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element name="keyid" type="xs:string"/> <!-- SHA1 hash of the principal's public key -->\r
+ <xs:element name="mnemonic" type="xs:string" minOccurs="0" maxOccurs="1"/> <!-- EG principal's URN -->\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <!-- A single rt0 element is required for creds of type 'abac'. Must have a single 'head'\r
+ and at least one 'tail'. -->\r
+ <xs:element name="rt0">\r
+ <xs:annotation>\r
+ <xs:documentation>An ABAC RT0 statement, used only for type 'abac'.</xs:documentation>\r
+ </xs:annotation>\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element name="version" type="xs:string" /> <!-- 1.1 for this schema -->\r
+ <xs:element name="head">\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element ref="ABACprincipal"/> <!-- Matching the cred signer -->\r
+ <xs:element name="role" type="xs:string"/>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="tail" minOccurs="1" maxOccurs="unbounded">\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element ref="ABACprincipal"/>\r
+ <xs:element name="role" type="xs:string" minOccurs="0" maxOccurs="1"/>\r
+ <xs:element name="linking_role" type="xs:string" minOccurs="0" \r
+ maxOccurs="1"/>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="abac">\r
+ <xs:annotation>\r
+ <xs:documentation>An ABAC assertion containing a single RT0 statement, used only for type 'abac'.</xs:documentation>\r
+ </xs:annotation>\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element minOccurs="1" maxOccurs="1" ref="rt0"/>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+\r
+ <xs:element name="signatures">\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element maxOccurs="unbounded" ref="sig:Signature"/>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:complexType name="credentials">\r
+ <xs:annotation>\r
+ <xs:documentation>A credential granting privileges or a ticket or making an ABAC assertion.</xs:documentation>\r
+ </xs:annotation>\r
+ <xs:sequence>\r
+ <xs:element ref="credential"/>\r
+ </xs:sequence>\r
+ </xs:complexType>\r
+ <xs:element name="credential">\r
+ <xs:complexType>\r
+ <xs:sequence>\r
+ <xs:element ref="type"/>\r
+ <xs:element ref="serial"/>\r
+ <xs:element ref="owner_gid"/>\r
+ <xs:element minOccurs="0" ref="owner_urn"/>\r
+ <xs:element ref="target_gid"/>\r
+ <xs:element minOccurs="0" ref="target_urn"/>\r
+ <xs:element ref="uuid"/>\r
+ <xs:element ref="expires"/>\r
+ <xs:choice>\r
+ <xs:annotation>\r
+ <xs:documentation>Privileges or a ticket or an ABAC assertion</xs:documentation>\r
+ </xs:annotation>\r
+ <xs:element ref="privileges"/>\r
+ <xs:element ref="ticket"/>\r
+ <xs:element ref="capabilities"/>\r
+ <xs:element ref="abac"/>\r
+ </xs:choice>\r
+ <xs:element minOccurs="0" maxOccurs="unbounded" ref="extensions"/>\r
+ <xs:element minOccurs="0" ref="parent"/>\r
+ </xs:sequence>\r
+ <xs:attribute ref="xml:id" use="required"/>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="type">\r
+ <xs:annotation>\r
+ <xs:documentation>The type of this credential. Currently a Privilege set or a Ticket or ABAC.</xs:documentation>\r
+ </xs:annotation>\r
+ <xs:simpleType>\r
+ <xs:restriction base="xs:token">\r
+ <xs:enumeration value="privilege"/>\r
+ <xs:enumeration value="ticket"/>\r
+ <xs:enumeration value="capability"/>\r
+ <xs:enumeration value="abac"/>\r
+ </xs:restriction>\r
+ </xs:simpleType>\r
+ </xs:element>\r
+ <xs:element name="serial" type="xs:string">\r
+ <xs:annotation>\r
+ <xs:documentation>A serial number.</xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element name="owner_gid" type="xs:string">\r
+ <xs:annotation>\r
+ <xs:documentation>GID of the owner of this credential. </xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element name="owner_urn" type="xs:string">\r
+ <xs:annotation>\r
+ <xs:documentation>URN of the owner. Not everyone can parse DER</xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element name="target_gid" type="xs:string">\r
+ <xs:annotation>\r
+ <xs:documentation>GID of the target of this credential. </xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element name="target_urn" type="xs:string">\r
+ <xs:annotation>\r
+ <xs:documentation>URN of the target.</xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element name="uuid" type="xs:string">\r
+ <xs:annotation>\r
+ <xs:documentation>UUID of this credential</xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element name="expires" type="xs:dateTime">\r
+ <xs:annotation>\r
+ <xs:documentation>Expires on in ISO8601 format but preferably RFC3339</xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element name="extensions">\r
+ <xs:annotation>\r
+ <xs:documentation>Optional Extensions</xs:documentation>\r
+ </xs:annotation>\r
+ <xs:complexType mixed="true">\r
+ <xs:group ref="anyelementbody"/>\r
+ <xs:attributeGroup ref="anyelementbody"/>\r
+ </xs:complexType>\r
+ </xs:element>\r
+ <xs:element name="parent" type="credentials">\r
+ <xs:annotation>\r
+ <xs:documentation>Parent that delegated to us</xs:documentation>\r
+ </xs:annotation>\r
+ </xs:element>\r
+ <xs:element name="signed-credential">\r
+ <xs:complexType>\r
+ <xs:complexContent>\r
+ <xs:extension base="credentials">\r
+ <xs:sequence>\r
+ <xs:element minOccurs="0" ref="signatures"/>\r
+ </xs:sequence>\r
+ </xs:extension>\r
+ </xs:complexContent>\r
+ </xs:complexType>\r
+ </xs:element>\r
+</xs:schema>\r