--- /dev/null
+##
+# Geniwrapper uses two crypto libraries: pyOpenSSL and M2Crypto to implement
+# the necessary crypto functionality. Ideally just one of these libraries
+# would be used, but unfortunately each of these libraries is independently
+# lacking. The pyOpenSSL library is missing many necessary functions, and
+# the M2Crypto library has crashed inside of some of the functions. The
+# design decision is to use pyOpenSSL whenever possible as it seems more
+# stable, and only use M2Crypto for those functions that are not possible
+# in pyOpenSSL.
+#
+# This module exports two classes: Keypair and Certificate.
+##
+
+import os
+import tempfile
+from OpenSSL import crypto
+import M2Crypto
+from M2Crypto import X509
+from M2Crypto import EVP
+
+from excep import *
+
+##
+# Public-private key pairs are implemented by the Keypair class.
+# A Keypair object may represent both a public and private key pair, or it
+# 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):\r
+ 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 OpenSSL pkey object
+
+ def get_m2_pkey(self):
+ if not self.m2key:
+ self.m2key = M2Crypto.EVP.load_key_string(self.as_pem())
+ return self.m2key
+
+ ##
+ # Given another Keypair object, return TRUE if the two keys are the same.
+
+ def get_openssl_pkey(self):
+ return self.key
+
+ def is_same(self, pkey):
+ return self.as_pem() == pkey.as_pem()
+
+##
+# The certificate class implements a general purpose X509 certificate, making
+# use of the appropriate pyOpenSSL or M2Crypto abstractions. It also adds
+# several addition features, such as the ability to maintain a chain of
+# parent certificates, and storage of application-specific data.
+#
+# Certificates include the ability to maintain a chain of parents. Each\r
+# certificate includes a pointer to it's parent certificate. When loaded\r
+# from a file or a string, the parent chain will be automatically loaded.\r
+# When saving a certificate to a file or a string, the caller can choose\r
+# whether to save the parent certificates as well.\r
+
+class Certificate:
+ digest = "md5"
+
+ data = None
+ cert = None
+ issuerKey = None
+ issuerSubject = None
+ parent = None
+
+ ##
+ # Create a certificate object.
+ #\r
+ # @param create If create==True, then also create a blank X509 certificate.\r
+ # @param subject If subject!=None, then create a blank certificate and set\r
+ # it's subject name.\r
+ # @param string If string!=None, load the certficate from the string.\r
+ # @param filename If filename!=None, load the certficiate from the file.\r
+
+ 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("-----parent-----", 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 + "-----parent-----" + 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_issuer()
+ 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)
+
+ ##
+ # 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.
+\r
+ 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.
+
+ 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: <pre>
+ # if this_certificate was signed by trusted_certs:\r
+ # return\r
+ # else\r
+ # return verify_chain(parent, trusted_certs)\r
+ # </pre>\r
+ #\r
+ # 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.\r
+ #
+
+ 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.
+
+ # TODO: verify expiration time
+
+ # if this cert is signed by a trusted_cert, then we are set
+ for trusted_cert in trusted_certs:
+ # TODO: verify expiration of trusted_cert ?
+ if self.is_signed_by_cert(trusted_cert):
+ #print self.get_subject(), "is signed by a root"
+ return
+
+ # if there is no parent, then no way to verify the chain
+ if not self.parent:
+ #print self.get_subject(), "has no parent"
+ raise CertMissingParent(self.get_subject())
+
+ # if it wasn't signed by the parent...
+ if not self.is_signed_by_cert(self.parent):
+ #print self.get_subject(), "is not signed by parent"
+ return CertNotSignedByParent(self.get_subject())
+
+ # if the parent isn't verified...
+ self.parent.verify_chain(trusted_certs)
+
+ return
--- /dev/null
+##
+# Geniwrapper Configuration Info
+#
+# This module holds configuration parameters for geniwrapper. There are two
+# main pieces of information that are used: the database connection and
+# the PLCAPI connection
+##
+
+##
+# Geniwrapper uses a MYSQL database to store records. This database may be
+# co-located with the PLC database, or it may be a separate database. The
+# following parameters define the connection to the database.
+#
+# Note that Geniwrapper does not access any of the PLC databases directly via
+# a mysql connection; All PLC databases are accessed via PLCAPI.
+
+import os
+import sys
+
+# If we have been checked out into a directory at the same
+# level as myplc, where plc_config.py lives. If we are in a
+# MyPLC environment, plc_config.py has already been installed
+# in site-packages.
+myplc = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + \
+ os.sep + "myplc"
+
+class Config:
+ """
+ Parse the bash/Python/PHP version of the configuration file. Very
+ fast but no type conversions.
+ """
+
+ def __init__(self, file = "/usr/share/geniwrapper/util/geni_config"):
+ # Load plc_config
+
+ loaded = False
+ path = os.path.dirname(os.path.abspath(__file__))
+ filename = file.split(os.sep)[-1]
+ alt_file = path + os.sep + filename
+ myplc_file = myplc + os.sep + filename
+ files = [file, alt_file, myplc_file]
+
+ for file in files:
+ try:
+ execfile(file, self.__dict__)
+ loaded = True
+ except:
+ pass
+
+ if not loaded:
+ raise Exception, "Could not find config in " + ", ".join(files)
+
+
+ def load(self, file):
+ try:
+ execfile(file, self.__dict__)
+ except:
+ raise Exception, "Could not find config in " + file
+
+plcConfig = Config("/etc/planetlab/plc_config")
+
+def get_default_dbinfo():
+ dbinfo={ 'dbname' : plcConfig.PLC_DB_NAME,
+ 'address' : plcConfig.PLC_DB_HOST,
+ 'port' : plcConfig.PLC_DB_PORT,
+ 'user' : plcConfig.PLC_DB_USER,
+ 'password' : plcConfig.PLC_DB_PASSWORD
+ }
+
+ return dbinfo
+
+##
+# Geniwrapper uses a PLCAPI connection to perform operations on the registry,
+# such as creating and deleting slices. This connection requires an account
+# on the PLC server with full administrator access.
+#
+# The Url parameter controls whether the connection uses PLCAPI directly (i.e.
+# Geniwrapper is located on the same machine as PLC), or uses a XMLRPC connection
+# to the PLC machine. If you wish to use the API directly, then remove the Url
+# field from the dictionary.
+
+def get_pl_auth():
+ pl_auth = {'Username': plcConfig.PLC_API_MAINTENANCE_USER,
+ 'AuthMethod': 'capability',
+ 'AuthString': plcConfig.PLC_MAINTENANCE_PASSWORD,
+ "Url": 'https://%s:%s%s' %(plcConfig.PLC_API_HOST, plcConfig.PLC_API_PORT, plcConfig.PLC_API_PATH)
+ }
+
+ return pl_auth
--- /dev/null
+##
+# Implements Geni Credentials
+#
+# Credentials are layered on top of certificates, and are essentially a
+# certificate that stores a tuple of parameters.
+##
+
+from cert import *
+from rights import *
+from gid import *
+import xmlrpclib
+
+##
+# 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 Credential(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
+
+ ##
+ # 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(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()
+ if data:
+ dict = xmlrpclib.loads(self.get_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())
+
+ 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)
+
--- /dev/null
+
+class MalformedHrnException(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class TreeException(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class NonexistingRecord(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class ExistingRecord(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class NonexistingCredType(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class NonexistingFile(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class InvalidRPCParams(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+# SMBAKER exceptions follow
+
+class ConnectionKeyGIDMismatch(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class MissingCallerGID(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class RecordNotFound(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class UnknownGeniType(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class MissingAuthority(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class PlanetLabRecordDoesNotExist(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class PermissionError(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class InsufficientRights(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class MissingDelegateBit(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class ChildRightsNotSubsetOfParent(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class CertMissingParent(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class CertNotSignedByParent(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class GidInvalidParentHrn(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
+class SliverDoesNotExist(Exception):
+ def __init__(self, value):
+ self.value = value
+ def __str__(self):
+ return repr(self.value)
+
--- /dev/null
+# Directory where Geni intefaces are installed
+GENI_BASE_DIR="/usr/share/geniwrapper/"
+
+# HRN
+# Human readable name for the interfaces
+GENI_INTERFACE_HRN="planetlab.us"
+
+
+## Aggregate Configuration
+#
+# Enable aggregate inteface
+# Enable the aggregate inteface.
+GENI_AGGREGATE_ENABLED=1
+
+# Hostname
+# The fully qualified hostname of the aggregate server
+GENI_AGGREGATE_HOSTNAME="localhost"
+
+# IP
+# Geni aggregate ip address
+GENI_AGGREGATE_IP="127.0.0.1"
+
+# Port
+# Geni aggregate server port
+GENI_AGGREGATE_PORT=12346
+
+
+# Registry Configuration
+#
+# Enabled
+# Enable the registry interface
+GENI_REGISTRY_ENABLE=1
+
+# Hostname
+# The fully qualified hostname of the registry server
+GENI_REGISTRY_HOSTNAME="localhost"
+
+# IP
+# Geni registry ip address
+GENI_REGISTRY_IP="127.0.0.1"
+
+# Port
+# Geni registry port
+GENI_REGISTRY_PORT=12345
+
+# Slice Manager Configuration
+#
+# Enabled
+# Enable the slice manager
+GENI_SM_ENABLED=1
+
+
+# PLC Interace Access Configuration
+#
+# PLC user
+# Valid plc account geni interfaces will use for plc requests
+
+GENI_PLC_USER='root@localhost.localdomain'
+
+# Passowrd
+# Password for user account
+GENI_PLC_PASSWORD='root'
+
+# Hostname
+# Fully qualified hostname of PLC interface
+GENI_PLC_HOST='localhost'
+
+# Port
+# Port of PLC interface
+GENI_PLC_PORT=443
+
+# API Path
+# Path of PLC api interface
+GENI_PLC_API_PATH='PLCAPI'
+
+# PLC Shell PATH
+# Path of PLC shell. This only applies if Geni interfaces and PLC interface are running on the same
+# machine
+GENI_PLC_SHELL_PATH='/usr/share/plc_api'
+
--- /dev/null
+##
+# This module implements the client-side of the Geni API. Stubs are provided
+# that convert the supplied parameters to the necessary format and send them
+# via XMLRPC to a Geni Server.
+#
+# TODO: Investigate ways to combine this with existing PLC API?
+##
+
+import xmlrpclib
+
+from gid import *
+from credential import *
+from record import *
+from geniticket import *
+
+##
+# ServerException, ExceptionUnmarshaller
+#
+# Used to convert server exception strings back to an exception.
+# from usenet, Raghuram Devarakonda
+
+class ServerException(Exception):
+ pass
+
+class ExceptionUnmarshaller(xmlrpclib.Unmarshaller):
+ def close(self):\r
+ try:\r
+ return xmlrpclib.Unmarshaller.close(self)\r
+ except xmlrpclib.Fault, e:\r
+ raise ServerException(e.faultString)
+
+##
+# GeniTransport
+#
+# A transport for XMLRPC that works on top of HTTPS
+
+class GeniTransport(xmlrpclib.Transport):
+ key_file = None
+ cert_file = None
+ def make_connection(self, host):\r
+ # create a HTTPS connection object from a host descriptor\r
+ # host may be a string, or a (host, x509-dict) tuple\r
+ import httplib\r
+ host, extra_headers, x509 = self.get_host_info(host)\r
+ try:\r
+ HTTPS = httplib.HTTPS()\r
+ except AttributeError:\r
+ raise NotImplementedError(\r
+ "your version of httplib doesn't support HTTPS"\r
+ )\r
+ else:\r
+ return httplib.HTTPS(host, None, key_file=self.key_file, cert_file=self.cert_file) #**(x509 or {}))\r
+\r
+ def getparser(self):\r
+ unmarshaller = ExceptionUnmarshaller()\r
+ parser = xmlrpclib.ExpatParser(unmarshaller)\r
+ return parser, unmarshaller\r
+\r
+##\r
+# The GeniClient class provides stubs for executing Geni operations. A given\r
+# client object connects to one server. To connect to multiple servers, create\r
+# multiple GeniClient objects.\r
+#\r
+# The Geni protocol uses an HTTPS connection, and the client's side of the\r
+# connection uses his private key. Generally, this private key must match the\r
+# public key that is containing in the GID that the client is providing for\r
+# those functions that take a GID.\r
+\r
+class GeniClient():
+ ##
+ # Create a new GeniClient object.
+ #
+ # @param url is the url of the server
+ # @param key_file = private key file of client
+ # @param cert_file = x.509 cert containing the client's public key. This
+ # could be a GID certificate, or any x.509 cert.
+
+ def __init__(self, url, key_file, cert_file):
+ self.url = url
+ self.key_file = key_file
+ self.cert_file = cert_file
+ self.transport = GeniTransport()
+ self.transport.key_file = self.key_file
+ self.transport.cert_file = self.cert_file
+ self.server = xmlrpclib.ServerProxy(self.url, self.transport, allow_none=True)
+
+ # -------------------------------------------------------------------------
+ # Registry Interface
+ # -------------------------------------------------------------------------
+
+ ##
+ # Create a new GID. For MAs and SAs that are physically located on the
+ # registry, this allows a owner/operator/PI to create a new GID and have it
+ # signed by his respective authority.
+ #
+ # @param cred credential of caller
+ # @param name hrn for new GID
+ # @param uuid unique identifier for new GID
+ # @param pkey_string public-key string (TODO: why is this a string and not a keypair object?)
+ #
+ # @return a GID object
+
+ def create_gid(self, cred, name, uuid, pkey_string):
+ gid_str = self.server.create_gid(cred.save_to_string(save_parents=True), name, uuid, pkey_string)
+ return GID(string=gid_str)
+
+ ##
+ # Retrieve the GID for an object. This function looks up a record in the
+ # registry and returns the GID of the record if it exists.
+ # TODO: Is this function needed? It's a shortcut for Resolve()
+ #
+ # @param name hrn to look up
+ #
+ # @return a GID object
+
+ def get_gid(self, name):
+ gid_str_list = self.server.get_gid(name)
+ gid_list = []
+ for str in gid_str_list:
+ gid_list.append(GID(string=str))
+ return gid_list
+
+ ##
+ # Get_self_credential a degenerate version of get_credential used by a
+ # client to get his initial credential when he doesn't have one. This is
+ # the same as get_credential(..., cred=None,...).
+ #
+ # The registry ensures that the client is the principal that is named by
+ # (type, name) by comparing the public key in the record's GID to the
+ # private key used to encrypt the client-side of the HTTPS connection. Thus
+ # it is impossible for one principal to retrieve another principal's
+ # credential without having the appropriate private key.
+ #
+ # @param type type of object (user | slice | sa | ma | node
+ # @param name human readable name of object
+ #
+ # @return a credential object
+
+ def get_self_credential(self, type, name):
+ cred_str = self.server.get_self_credential(type, name)
+ return Credential(string = cred_str)
+
+ ##
+ # Retrieve a credential for an object.
+ #
+ # If cred==None, then the behavior reverts to get_self_credential()
+ #
+ # @param cred credential object specifying rights of the caller
+ # @param type type of object (user | slice | sa | ma | node)
+ # @param name human readable name of object
+ #
+ # @return a credental object
+
+ def get_credential(self, cred, type, name):
+ if cred == None:
+ return self.get_self_credential(type, name)
+ cred_str = self.server.get_credential(cred.save_to_string(save_parents=True), type, name)
+ return Credential(string = cred_str)
+
+ ##
+ # List the records in an authority. The objectGID in the supplied credential
+ # should name the authority that will be listed.
+ #
+ # @param cred credential object specifying rights of the caller
+ #
+ # @return list of record objects
+
+ def list(self, cred, auth_hrn):
+ result_dict_list = self.server.list(cred.save_to_string(save_parents=True), auth_hrn)
+ result_rec_list = []
+ for dict in result_dict_list:
+ result_rec_list.append(GeniRecord(dict=dict))
+ return result_rec_list
+
+ ##
+ # Register an object with the registry. In addition to being stored in the
+ # Geni database, the appropriate records will also be created in the
+ # PLC databases.
+ #
+ # The geni_info and/or pl_info fields must in the record must be filled
+ # out correctly depending on the type of record that is being registered.
+ #
+ # TODO: The geni_info member of the record should be parsed and the pl_info
+ # adjusted as necessary (add/remove users from a slice, etc)
+ #
+ # @param cred credential object specifying rights of the caller
+ # @return record to register
+ #
+ # @return GID object for the newly-registered record
+
+ def register(self, cred, record):
+ gid_str = self.server.register(cred.save_to_string(save_parents=True), record.as_dict())
+ return GID(string = gid_str)
+
+ ##
+ # Remove an object from the registry. If the object represents a PLC object,
+ # then the PLC records will also be removed.
+ #
+ # @param cred credential object specifying rights of the caller
+ # @param type
+ # @param hrn
+
+ def remove(self, cred, type, hrn):
+ result = self.server.remove(cred.save_to_string(save_parents=True), type, hrn)
+ return result
+
+ ##
+ # Resolve an object in the registry. A given HRN may have multiple records
+ # associated with it, and therefore multiple records may be returned. The
+ # caller should check the type fields of the records to find the one that
+ # he is interested in.
+ #
+ # @param cred credential object specifying rights of the caller
+ # @param name human readable name of object
+
+ def resolve(self, cred, name):
+ result_dict_list = self.server.resolve(cred.save_to_string(save_parents=True), name)
+ result_rec_list = []
+ for dict in result_dict_list:
+ result_rec_list.append(GeniRecord(dict=dict))
+ return result_rec_list
+
+ ##
+ # Update an object in the registry. Currently, this only updates the
+ # PLC information associated with the record. The Geni fields (name, type,
+ # GID) are fixed.
+ #
+ # The record is expected to have the pl_info field filled in with the data
+ # that should be updated.
+ #
+ # TODO: The geni_info member of the record should be parsed and the pl_info
+ # adjusted as necessary (add/remove users from a slice, etc)
+ #
+ # @param cred credential object specifying rights of the caller
+ # @param record a record object to be updated
+
+ def update(self, cred, record):
+ result = self.server.update(cred.save_to_string(save_parents=True), record.as_dict())
+ return result
+
+ # ------------------------------------------------------------------------
+ # Slice Interface
+ # ------------------------------------------------------------------------
+
+ ##
+ # Start a slice.
+ #
+ # @param cred a credential identifying the caller (callerGID) and the slice
+ # (objectGID)
+
+ def start_slice(self, cred):
+ result = self.server.start_slice(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # Stop a slice.
+ #
+ # @param cred a credential identifying the caller (callerGID) and the slice
+ # (objectGID)
+
+ def stop_slice(self, cred):
+ result = self.server.stop_slice(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # Reset a slice.
+ #
+ # @param cred a credential identifying the caller (callerGID) and the slice
+ # (objectGID)
+
+ def reset_slice(self, cred):
+ result = self.server.reset_slice(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # Delete a slice.
+ #
+ # @param cred a credential identifying the caller (callerGID) and the slice
+ # (objectGID)
+
+ def delete_slice(self, cred):
+ result = self.server.delete_slice(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # List the slices on a component.
+ #
+ # @param cred credential object that authorizes the caller
+ #
+ # @return a list of slice names
+
+ def list_slices(self, cred):
+ result = self.server.list_slices(cred.save_to_string(save_parents=True))
+ return result
+
+ ##
+ # Retrieve a ticket. This operation is currently implemented on the
+ # registry (see SFA, engineering decisions), and is not implemented on
+ # components.
+ #
+ # The ticket is filled in with information from the PLC database. This
+ # information includes resources, and attributes such as user keys and
+ # initscripts.
+ #
+ # @param cred credential object
+ # @param name name of the slice to retrieve a ticket for
+ # @param rspec resource specification dictionary
+ #
+ # @return a ticket object
+
+ def get_ticket(self, cred, name, rspec):
+ ticket_str = self.server.get_ticket(cred.save_to_string(save_parents=True), name, rspec)
+ ticket = Ticket(string=ticket_str)
+ return ticket
+
+ ##
+ # Redeem a ticket. This operation is currently implemented on the
+ # component.
+ #
+ # The ticket is submitted to the node manager, and the slice is instantiated
+ # or updated as appropriate.
+ #
+ # TODO: This operation should return a sliver credential and indicate
+ # whether or not the component will accept only sliver credentials, or
+ # will accept both sliver and slice credentials.
+ #
+ # @param ticket a ticket object containing the ticket
+
+ def redeem_ticket(self, ticket):
+ result = self.server.redeem_ticket(ticket.save_to_string(save_parents=True))
+ return result
+
+
--- /dev/null
+##
+# This module implements a general-purpose server layer for geni.
+# The same basic server should be usable on the registry, component, or
+# other interfaces.
+#
+# TODO: investigate ways to combine this with existing PLC server?
+##
+
+import SimpleXMLRPCServer
+
+import sys
+import traceback
+import threading
+import SocketServer
+import BaseHTTPServer
+import SimpleHTTPServer
+import SimpleXMLRPCServer
+
+from excep import *
+from cert import *
+from credential import *
+
+import socket, os
+from OpenSSL import SSL
+
+##
+# Verification callback for pyOpenSSL. We do our own checking of keys because
+# we have our own authentication spec. Thus we disable several of the normal
+# prohibitions that OpenSSL places on certificates
+
+def verify_callback(conn, x509, err, depth, preverify):
+ # if the cert has been preverified, then it is ok
+ if preverify:
+ #print " preverified"
+ return 1
+
+ # we're only passing single certificates, not chains
+ if depth > 0:
+ #print " depth > 0 in verify_callback"
+ return 0
+
+ # create a Certificate object and load it from the client's x509
+ ctx = conn.get_context()
+ server = ctx.get_app_data()
+ server.peer_cert = Certificate()
+ server.peer_cert.load_from_pyopenssl_x509(x509)
+
+ # the certificate verification done by openssl checks a number of things
+ # that we aren't interested in, so we look out for those error messages
+ # and ignore them
+
+ # XXX SMBAKER: I don't know what this error is, but it's being returned
+ # by newer pl nodes.
+ if err == 9:
+ #print " X509_V_ERR_CERT_NOT_YET_VALID"
+ return 1
+
+ # allow self-signed certificates
+ if err == 18:
+ #print " X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
+ return 1
+
+ # allow certs that don't have an issuer
+ if err == 20:
+ #print " X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
+ return 1
+
+ # allow certs that are untrusted
+ if err == 21:
+ #print " X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
+ return 1
+
+ # allow certs that are untrusted
+ if err == 27:
+ #print " X509_V_ERR_CERT_UNTRUSTED"
+ return 1
+
+ print " error", err, "in verify_callback"
+
+ return 0\r
+
+##
+# Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
+
+class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
+ def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
+ """Secure XML-RPC server.
+
+ It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
+ """
+ self.logRequests = logRequests
+
+ SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
+ SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
+ ctx = SSL.Context(SSL.SSLv23_METHOD)
+ ctx.use_privatekey_file(key_file)
+ ctx.use_certificate_file(cert_file)
+ ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
+ ctx.set_app_data(self)
+ self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
+ self.socket_type))
+ self.server_bind()
+ self.server_activate()
+
+ # _dispatch
+ #
+ # Convert an exception on the server to a full stack trace and send it to
+ # the client.
+
+ def _dispatch(self, method, params):
+ try:
+ return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
+ except:
+ # can't use format_exc() as it is not available in jython yet
+ # (evein in trunk).
+ type, value, tb = sys.exc_info()
+ raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
+
+##
+# taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
+
+class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
+ """Secure XML-RPC request handler class.
+
+ It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
+ """
+ def setup(self):
+ self.connection = self.request
+ self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
+ self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
+
+ def do_POST(self):
+ """Handles the HTTPS POST request.
+
+ It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
+ """
+
+ try:
+ # get arguments
+ data = self.rfile.read(int(self.headers["content-length"]))
+ # In previous versions of SimpleXMLRPCServer, _dispatch
+ # could be overridden in this class, instead of in
+ # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
+ # check to see if a subclass implements _dispatch and dispatch
+ # using that method if present.
+ response = self.server._marshaled_dispatch(
+ data, getattr(self, '_dispatch', None)
+ )
+ except: # This should only happen if the module is buggy
+ # internal error, report as HTTP server error
+ self.send_response(500)
+
+ self.end_headers()
+ else:
+ # got a valid XML RPC response
+ self.send_response(200)
+ self.send_header("Content-type", "text/xml")
+ self.send_header("Content-length", str(len(response)))
+ self.end_headers()
+ self.wfile.write(response)
+
+ # shut down the connection
+ self.wfile.flush()
+ self.connection.shutdown() # Modified here!
+
+##
+# Implements an HTTPS XML-RPC server. Generally it is expected that GENI
+# functions will take a credential string, which is passed to
+# decode_authentication. Decode_authentication() will verify the validity of
+# the credential, and verify that the user is using the key that matches the
+# GID supplied in the credential.
+
+class GeniServer(threading.Thread):
+
+ ##
+ # Create a new GeniServer object.
+ #
+ # @param ip the ip address to listen on
+ # @param port the port to listen on
+ # @param key_file private key filename of registry
+ # @param cert_file certificate filename containing public key
+ # (could be a GID file)
+
+ def __init__(self, ip, port, key_file, cert_file):
+ threading.Thread.__init__(self)
+ self.key = Keypair(filename = key_file)
+ self.cert = Certificate(filename = cert_file)
+ self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
+ self.trusted_cert_list = None
+ self.register_functions()
+
+ ##
+ # Decode the credential string that was submitted by the caller. Several
+ # checks are performed to ensure that the credential is valid, and that the
+ # callerGID included in the credential matches the caller that is
+ # connected to the HTTPS connection.
+
+ def decode_authentication(self, cred_string, operation):
+ self.client_cred = Credential(string = cred_string)
+ 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())
+
+ # make sure the client_gid matches client's certificate
+ peer_cert = self.server.peer_cert
+ if not peer_cert.is_pubkey(self.client_gid.get_pubkey()):
+ raise ConnectionKeyGIDMismatch(self.client_gid.get_subject())
+
+ # 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_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)
+
+ ##
+ # Register functions that will be served by the XMLRPC server. This
+ # function should be overrided by each descendant class.
+
+ def register_functions(self):
+ self.server.register_function(self.noop)
+
+ ##
+ # Sample no-op server function. The no-op function decodes the credential
+ # that was passed to it.
+
+ def noop(self, cred, anything):
+ self.decode_authentication(cred)
+
+ return anything
+
+ ##
+ # Execute the server, serving requests forever.
+
+ def run(self):
+ self.server.serve_forever()
+
+
--- /dev/null
+# genitable.py
+#
+# implements support for geni records stored in db tables
+#
+# TODO: Use existing PLC database methods? or keep this separate?
+
+import report
+
+from pg import DB
+from gid import *
+from record import *
+
+GENI_TABLE_PREFIX = "geni$"
+
+class GeniTable():
+ def __init__(self, create=False, hrn="unspecified.default.registry", cninfo=None):
+ global GENI_TABLE_PREFIX
+
+ self.hrn = hrn
+
+ # pgsql doesn't like table names with "." in them, to replace it with "$"
+ self.tablename = GENI_TABLE_PREFIX + self.hrn.replace(".", "$")
+
+ # establish a connection to the pgsql server
+ self.cnx = DB(cninfo['dbname'], cninfo['address'], port=cninfo['port'], user=cninfo['user'], passwd=cninfo['password'])
+
+ # if asked to create the table, then create it
+ if create:
+ self.create()
+
+ def exists(self):
+ tableList = self.cnx.get_tables()
+ if 'public.' + self.tablename in tableList:
+ return True
+ if 'public."' + self.tablename + '"' in tableList:
+ return True
+ return False
+
+ def create(self):
+ querystr = "CREATE TABLE " + self.tablename + " ( \
+ key text, \
+ name text, \
+ gid text, \
+ type text, \
+ pointer integer);"
+
+ self.cnx.query('DROP TABLE IF EXISTS ' + self.tablename)
+ self.cnx.query(querystr)
+
+ def remove(self, record):
+ query_str = "DELETE FROM " + self.tablename + " WHERE key = '" + record.get_key() + "'"
+ self.cnx.query(query_str)
+
+ def insert(self, record):
+ fieldnames = ["key"] + record.get_field_names()
+ fieldvals = record.get_field_value_strings(fieldnames)
+ query_str = "INSERT INTO " + self.tablename + \
+ "(" + ",".join(fieldnames) + ") " + \
+ "VALUES(" + ",".join(fieldvals) + ")"
+ #print query_str
+ self.cnx.query(query_str)
+
+ def update(self, record):
+ names = record.get_field_names()
+ pairs = []
+ for name in names:
+ val = record.get_field_value_string(name)
+ pairs.append(name + " = " + val)
+ update = ", ".join(pairs)
+
+ query_str = "UPDATE " + self.tablename+ " SET " + update + " WHERE key = '" + record.get_key() + "'"
+ #print query_str
+ self.cnx.query(query_str)
+
+ def find_dict(self, type, value, searchfield):
+ query_str = "SELECT * FROM " + self.tablename + " WHERE " + searchfield + " = '" + str(value) + "'"
+ dict_list = self.cnx.query(query_str).dictresult()
+ result_dict_list = []
+ for dict in dict_list:
+ if (type=="*") or (dict['type'] == type):
+ result_dict_list.append(dict)
+ return result_dict_list
+
+ def find(self, type, value, searchfield):
+ result_dict_list = self.find_dict(type, value, searchfield)
+ result_rec_list = []
+ for dict in result_dict_list:
+ result_rec_list.append(GeniRecord(dict=dict))
+ return result_rec_list
+
+ def resolve_dict(self, type, hrn):
+ return self.find_dict(type, hrn, "name")
+
+ def resolve(self, type, hrn):
+ return self.find(type, hrn, "name")
+
+ def list_dict(self):
+ query_str = "SELECT * FROM " + self.tablename
+ result_dict_list = self.cnx.query(query_str).dictresult()
+ return result_dict_list
+
+ def list(self):
+ result_dict_list = self.list_dict()
+ result_rec_list = []
+ for dict in result_dict_list:
+ result_rec_list.append(GeniRecord(dict=dict))
+ return result_rec_list
+
+def set_geni_table_prefix(x):
+ global GENI_TABLE_PREFIX
+
+ GENI_TABLE_PREFIX = x
+
+def geni_records_purge(cninfo):
+ global GENI_TABLE_PREFIX
+
+ cnx = DB(cninfo['dbname'], cninfo['address'], port=cninfo['port'], user=cninfo['user'], passwd=cninfo['password'])
+ tableList = cnx.get_tables()
+ for table in tableList:
+ if table.startswith(GENI_TABLE_PREFIX) or \
+ table.startswith('public.' + GENI_TABLE_PREFIX) or \
+ table.startswith('public."' + GENI_TABLE_PREFIX):
+ report.trace("dropping table " + table)
+ cnx.query("DROP TABLE " + table)
--- /dev/null
+# tickets.py
+#
+# implements GENI tickets
+#
+
+from cert import *
+from rights import *
+from gid import *
+import xmlrpclib
+
+# Ticket is tuple:
+# (gidCaller, gidObject, attributes, rspec, delegate)
+#
+# gidCaller = GID of the caller performing the operation
+# gidObject = GID of the slice
+# attributes = slice attributes (keys, vref, instantiation, etc)
+# rspec = resources
+
+class Ticket(Certificate):
+ gidCaller = None
+ gidObject = None
+ attributes = {}
+ rspec = {}
+ delegate = False
+
+ def __init__(self, create=False, subject=None, string=None, filename=None):
+ Certificate.__init__(self, create, subject, string, filename)
+
+ def set_gid_caller(self, gid):
+ self.gidCaller = gid
+
+ def get_gid_caller(self):
+ if not self.gidCaller:
+ self.decode()
+ return self.gidCaller
+
+ def set_gid_object(self, gid):
+ self.gidObject = gid
+
+ def get_gid_object(self):
+ if not self.gidObject:
+ self.decode()
+ return self.gidObject
+
+ def set_attributes(self, gid):
+ self.attributes = gid
+
+ def get_attributes(self):
+ if not self.attributes:
+ self.decode()
+ return self.attributes
+
+ def set_rspec(self, gid):
+ self.rspec = gid
+
+ def get_rspec(self):
+ if not self.rspec:
+ self.decode()
+ return self.rspec
+
+ def set_delegate(self, delegate):
+ self.delegate = delegate
+
+ def get_delegate(self):
+ if not self.delegate:
+ self.decode()
+ return self.delegate
+
+ def encode(self):
+ dict = {"gidCaller": None,
+ "gidObject": None,
+ "attributes": self.attributes,
+ "rspec": self.rspec,
+ "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)
+ str = xmlrpclib.dumps((dict,), allow_none=True)
+ self.set_data(str)
+
+ def decode(self):
+ data = self.get_data()
+ if data:
+ dict = xmlrpclib.loads(self.get_data())[0][0]
+ else:
+ dict = {}
+
+ self.attributes = dict.get("attributes", {})
+ self.rspec = dict.get("rspec", {})
+ self.delegate = dict.get("delegate", False)
+
+ 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
+
+ def dump(self, dump_parents=False):
+ print "TICKET", self.get_subject()
+
+ 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 " attributes:"
+ for attrname in self.get_attributes().keys():
+ print " ", attrname, self.get_attributes()[attrname]
+
+ print " rspec:"
+ for attrname in self.get_rspec().keys():
+ print " ", attrname, self.get_rspec()[attrname]
+
+ if self.parent and dump_parents:
+ print "PARENT",
+ self.parent.dump(dump_parents)
--- /dev/null
+##
+# Implements GENI GID. GIDs are based on certificates, and the GID class is a
+# descendant of the certificate class.
+##
+
+from cert import *
+import uuid
+import xmlrpclib
+
+##
+# Create a new uuid. Returns the UUID as a string.
+
+def create_uuid():
+ return str(uuid.uuid4().int)
+
+##
+# GID is a tuplie:
+# (uuid, hrn, public_key)
+#
+# UUID is a unique identifier and is created by the python uuid module
+# (or the utility function create_uuid() in gid.py).
+#
+# HRN is a human readable name. It is a dotted form similar to a backward domain\r
+# name. For example, planetlab.us.arizona.bakers.\r
+#\r
+# PUBLIC_KEY is the public key of the principal identified by the UUID/HRN.\r
+# It is a Keypair object as defined in the cert.py module.\r
+#\r
+# It is expected that there is a one-to-one pairing between UUIDs and HRN,
+# but it is uncertain how this would be inforced or if it needs to be enforced.
+#
+# 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 GID(Certificate):
+ uuid = None
+ hrn = None
+
+ ##
+ # Create a new GID object
+ #
+ # @param create If true, create the X509 certificate
+ # @param subject If subject!=None, create the X509 cert and set the subject name
+ # @param string If string!=None, load the GID from a string
+ # @param filename If filename!=None, load the GID from a file
+
+ def __init__(self, create=False, subject=None, string=None, filename=None, uuid=None, hrn=None):
+ Certificate.__init__(self, create, subject, string, filename)
+ if uuid:
+ self.uuid = uuid
+ if hrn:
+ self.hrn = hrn
+
+ def set_uuid(self, uuid):
+ self.uuid = uuid
+
+ def get_uuid(self):
+ if not self.uuid:
+ self.decode()
+ return self.uuid
+
+ def set_hrn(self, hrn):
+ self.hrn = hrn
+
+ def get_hrn(self):
+ if not self.hrn:
+ self.decode()
+ return self.hrn
+
+ ##
+ # 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
+ # certificate. It may only be called once per certificate.
+
+ def encode(self):
+ dict = {"uuid": self.uuid,
+ "hrn": self.hrn}
+ str = xmlrpclib.dumps((dict,))
+ self.set_data(str)
+
+ ##
+ # Decode the subject-alt-name field of the X509 certificate into the
+ # fields of the GID. This is automatically called by the various get_*()
+ # functions in this class.
+
+ def decode(self):
+ data = self.get_data()
+ if data:
+ dict = xmlrpclib.loads(self.get_data())[0][0]
+ else:
+ dict = {}
+
+ self.uuid = dict.get("uuid", None)
+ self.hrn = dict.get("hrn", None)
+
+ ##
+ # Dump the credential to stdout.
+ #
+ # @param indent specifies a number of spaces to indent the output
+ # @param dump_parents If true, also dump the parents of the GID
+
+ def dump(self, indent=0, dump_parents=False):
+ print " "*indent, " hrn:", self.get_hrn()
+ print " "*indent, "uuid:", self.get_uuid()
+
+ if self.parent and dump_parents:
+ print " "*indent, "parent:"
+ self.parent.dump(indent+4)
+
+ ##
+ # Verify the chain of authenticity of the GID. First perform the checks
+ # of the certificate class (verifying that each parent signs the child,
+ # etc). In addition, GIDs also confirm that the parent's HRN is a prefix
+ # of the child's HRN.
+ #
+ # Verifying these prefixes prevents a rogue authority from signing a GID
+ # for a principal that is not a member of that authority. For example,
+ # planetlab.us.arizona cannot sign a GID for planetlab.us.princeton.foo.
+
+ 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'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())
+
+ return
+
+
+
+
+
--- /dev/null
+##
+# This module implements a hierarchy of authorities and performs a similar
+# function as the "tree" module of the original geniwrapper prototype. An HRN
+# is assumed to be a string of authorities separated by dots. For example,
+# "planetlab.us.arizona.bakers". Each component of the HRN is a different
+# authority, with the last component being a leaf in the tree.
+#
+# Each authority is stored in a subdirectory on the registry. Inside this
+# subdirectory are several files:
+# *.GID - GID file
+# *.PKEY - private key file
+# *.DBINFO - database info
+##
+
+import os
+import report
+from cert import *
+from credential import *
+from gid import *
+from misc import *
+from config import *
+from geniticket import *
+
+##
+# The AuthInfo class contains the information for an authority. This information
+# includes the GID, private key, and database connection information.
+
+class AuthInfo():
+ hrn = None
+ gid_object = None
+ gid_filename = None
+ privkey_filename = None
+ dbinfo_filename = None
+
+ ##
+ # Initialize and authority object.
+ #
+ # @param hrn the human readable name of the authority
+ # @param gid_filename the filename containing the GID
+ # @param privkey_filename the filename containing the private key
+ # @param dbinfo_filename the filename containing the database info
+
+ def __init__(self, hrn, gid_filename, privkey_filename, dbinfo_filename):
+ self.hrn = hrn
+ self.set_gid_filename(gid_filename)
+ self.privkey_filename = privkey_filename
+ self.dbinfo_filename = dbinfo_filename
+
+ ##
+ # Set the filename of the GID
+ #
+ # @param fn filename of file containing GID
+
+ def set_gid_filename(self, fn):
+ self.gid_filename = fn
+ self.gid_object = None
+
+ ##
+ # Get the GID in the form of a GID object
+
+ def get_gid_object(self):
+ if not self.gid_object:
+ self.gid_object = GID(filename = self.gid_filename)
+ return self.gid_object
+
+ ##
+ # Get the private key in the form of a Keypair object
+
+ def get_pkey_object(self):
+ return Keypair(filename = self.privkey_filename)
+
+ ##
+ # Get the dbinfo in the form of a dictionary
+
+ def get_dbinfo(self):
+ f = file(self.dbinfo_filename)
+ dict = eval(f.read())\r
+ f.close()\r
+ return dict\r
+\r
+ ##\r
+ # Replace the GID with a new one. The file specified by gid_filename is\r
+ # overwritten with the new GID object\r
+ #\r
+ # @param gid object containing new GID\r
+\r
+ def update_gid_object(self, gid):\r
+ gid.save_to_file(self.gid_filename)\r
+ self.gid_object = gid\r
+\r
+##\r
+# The Hierarchy class is responsible for managing the tree of authorities.\r
+# Each authority is a node in the tree and exists as an AuthInfo object.\r
+#\r
+# The tree is stored on disk in a hierarchical manner than reflects the\r
+# structure of the tree. Each authority is a subdirectory, and each subdirectory\r
+# contains the GID, pkey, and dbinfo files for that authority (as well as\r
+# subdirectories for each sub-authority)\r
+
+class Hierarchy():
+ ##
+ # Create the hierarchy object.
+ #
+ # @param basedir the base directory to store the hierarchy in
+
+ def __init__(self, basedir="."):
+ self.basedir = os.path.join(basedir, "authorities")
+
+ ##
+ # Given a hrn, return the filenames of the GID, private key, and dbinfo
+ # files.
+ #
+ # @param hrn the human readable name of the authority
+
+ def get_auth_filenames(self, hrn):
+ leaf = get_leaf(hrn)
+ parent_hrn = get_authority(hrn)
+ directory = os.path.join(self.basedir, hrn.replace(".", "/"))
+
+ gid_filename = os.path.join(directory, leaf+".gid")
+ privkey_filename = os.path.join(directory, leaf+".pkey")
+ dbinfo_filename = os.path.join(directory, leaf+".dbinfo")
+
+ return (directory, gid_filename, privkey_filename, dbinfo_filename)
+
+ ##
+ # Check to see if an authority exists. An authority exists if it's disk
+ # files exist.
+ #
+ # @param the human readable name of the authority to check
+
+ def auth_exists(self, hrn):
+ (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+ self.get_auth_filenames(hrn)
+
+ return os.path.exists(gid_filename) and \
+ os.path.exists(privkey_filename) and \
+ os.path.exists(dbinfo_filename)
+
+ ##
+ # Create an authority. A private key for the authority and the associated
+ # GID are created and signed by the parent authority.
+ #
+ # @param hrn the human readable name of the authority to create
+ # @param create_parents if true, also create the parents if they do not exist
+
+ def create_auth(self, hrn, create_parents=False):
+ report.trace("Hierarchy: creating authority: " + hrn)
+
+ # 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)
+
+ (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+ self.get_auth_filenames(hrn)
+
+ # create the directory to hold the files
+ try:
+ os.makedirs(directory)\r
+ # if the path already exists then pass\r
+ except OSError, (errno, strerr):\r
+ if errno == 17:\r
+ pass
+
+ pkey = Keypair(create = True)
+ pkey.save_to_file(privkey_filename)
+
+ gid = self.create_gid(hrn, create_uuid(), pkey)
+ gid.save_to_file(gid_filename, save_parents=True)
+
+ # XXX TODO: think up a better way for the dbinfo to work
+
+ dbinfo = get_default_dbinfo()
+ dbinfo_file = file(dbinfo_filename, "w")
+ dbinfo_file.write(str(dbinfo))\r
+ dbinfo_file.close()
+
+ ##
+ # Return the AuthInfo object for the specified authority. If the authority
+ # does not exist, then an exception is thrown. As a side effect, disk files
+ # and a subdirectory may be created to store the authority.
+ #
+ # @param hrn the human readable name of the authority to create.
+
+ def get_auth_info(self, hrn):
+ #report.trace("Hierarchy: getting authority: " + hrn)
+
+ if not self.auth_exists(hrn):
+ raise MissingAuthority(hrn)
+
+ (directory, gid_filename, privkey_filename, dbinfo_filename) = \
+ self.get_auth_filenames(hrn)
+
+ auth_info = AuthInfo(hrn, gid_filename, privkey_filename, dbinfo_filename)
+
+ # check the GID and see if it needs to be refreshed
+ gid = auth_info.get_gid_object()
+ gid_refreshed = self.refresh_gid(gid)
+ if gid != gid_refreshed:
+ auth_info.update_gid_object(gid_refreshed)
+
+ return auth_info
+
+ ##
+ # Create a new GID. The GID will be signed by the authority that is it's
+ # immediate parent in the hierarchy (and recursively, the parents' GID
+ # will be signed by its parent)
+ #
+ # @param hrn the human readable name to store in the GID
+ # @param uuid the unique identifier to store in the GID
+ # @param pkey the public key to store in the GID
+
+ def create_gid(self, hrn, uuid, pkey):
+ gid = GID(subject=hrn, uuid=uuid, hrn=hrn)
+
+ parent_hrn = get_authority(hrn)
+ if not parent_hrn:
+ # if there is no parent hrn, then it must be self-signed. this
+ # is where we terminate the recursion
+ gid.set_issuer(pkey, hrn)
+ else:
+ # we need the parent's private key in order to sign this GID
+ parent_auth_info = self.get_auth_info(parent_hrn)
+ gid.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn)
+ gid.set_parent(parent_auth_info.get_gid_object())
+
+ gid.set_pubkey(pkey)
+ gid.encode()
+ gid.sign()
+
+ return gid
+
+ ##
+ # Refresh a GID. The primary use of this function is to refresh the
+ # the expiration time of the GID. It may also be used to change the HRN,
+ # UUID, or Public key of the GID.
+ #
+ # @param gid the GID to refresh
+ # @param hrn if !=None, change the hrn
+ # @param uuid if !=None, change the uuid
+ # @param pubkey if !=None, change the public key
+
+ def refresh_gid(self, gid, hrn=None, uuid=None, pubkey=None):
+ # TODO: compute expiration time of GID, refresh it if necessary
+ gid_is_expired = False
+
+ # update the gid if we need to
+ if gid_is_expired or hrn or uuid or pubkey:
+ if not hrn:
+ hrn = gid.get_hrn()
+ if not uuid:
+ uuid = gid.get_uuid()
+ if not pubkey:
+ pubkey = gid.get_pubkey()
+
+ gid = self.create_gid(hrn, uuid, pubkey)
+
+ return gid
+
+ ##
+ # Retrieve an authority credential for an authority. The authority
+ # credential will contain the authority privilege and will be signed by
+ # the authority's parent.
+ #
+ # @param hrn the human readable name of the authority
+
+ def get_auth_cred(self, hrn):
+ auth_info = self.get_auth_info(hrn)
+ gid = auth_info.get_gid_object()
+
+ cred = Credential(subject=hrn)
+ cred.set_gid_caller(gid)
+ cred.set_gid_object(gid)
+ cred.set_privileges("authority")
+ cred.set_delegate(True)
+ cred.set_pubkey(auth_info.get_gid_object().get_pubkey())
+
+ parent_hrn = get_authority(hrn)
+ if not parent_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)
+ 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_parent(self.get_auth_cred(parent_hrn))
+
+ cred.encode()
+ cred.sign()
+
+ return cred
+ ##
+ # Retrieve an authority ticket. An authority ticket is not actually a
+ # redeemable ticket, but only serves the purpose of being included as the
+ # parent of another ticket, in order to provide a chain of authentication
+ # for a ticket.
+ #
+ # This looks almost the same as get_auth_cred, but works for tickets
+ # XXX does similarity imply there should be more code re-use?
+ #
+ # @param hrn the human readable name of the authority
+
+ def get_auth_ticket(self, hrn):
+ auth_info = self.get_auth_info(hrn)
+ gid = auth_info.get_gid_object()
+
+ ticket = Ticket(subject=hrn)
+ ticket.set_gid_caller(gid)
+ ticket.set_gid_object(gid)
+ ticket.set_delegate(True)
+ ticket.set_pubkey(auth_info.get_gid_object().get_pubkey())
+
+ parent_hrn = get_authority(hrn)
+ if not parent_hrn:
+ # if there is no parent hrn, then it must be self-signed. this
+ # is where we terminate the recursion
+ ticket.set_issuer(auth_info.get_pkey_object(), hrn)
+ else:
+ # we need the parent's private key in order to sign this GID
+ parent_auth_info = self.get_auth_info(parent_hrn)
+ ticket.set_issuer(parent_auth_info.get_pkey_object(), parent_auth_info.hrn)
+ ticket.set_parent(self.get_auth_cred(parent_hrn))
+
+ ticket.encode()
+ ticket.sign()
+
+ return ticket
+
--- /dev/null
+from excep import *
+
+def get_leaf(hrn):
+ parts = hrn.split(".")
+ return ".".join(parts[-1:])
+
+def get_authority(hrn):
+ parts = hrn.split(".")
+ return ".".join(parts[:-1])
+
+def get_auth_type(type):
+ if (type=="slice") or (type=="user") or (type=="sa"):
+ return "sa"
+ elif (type=="component") or (type=="ma"):
+ return "ma"
+ else:
+ raise UnknownGeniType(type)
+
+def hrn_to_pl_slicename(hrn):
+ parts = hrn.split(".")
+ return parts[-2] + "_" + parts[-1]
+
+# assuming hrn is the hrn of an authority, return the plc authority name
+def hrn_to_pl_authname(hrn):
+ parts = hrn.split(".")
+ return parts[-1]
+
+# assuming hrn is the hrn of an authority, return the plc login_base
+def hrn_to_pl_login_base(hrn):
+ return hrn_to_pl_authname(hrn)
+
--- /dev/null
+##
+# Implements support for geni records
+#
+# TODO: Use existing PLC database methods? or keep this separate?
+##
+
+import report
+from gid import *
+
+##
+# The GeniRecord class implements a Geni Record. A GeniRecord is a tuple
+# (Name, GID, Type, Info).
+#
+# Name specifies the HRN of the object
+# GID is the GID of the object
+# Type is user | sa | ma | slice | component
+#
+# Info is comprised of the following sub-fields
+# pointer = a pointer to the record in the PL database
+# pl_info = planetlab-specific info (when talking to client)
+# geni_info = geni-specific info (when talking to client)
+#
+# The pointer is interpreted depending on the type of the record. For example,
+# if the type=="user", then pointer is assumed to be a person_id that indexes
+# into the persons table.
+#
+# A given HRN may have more than one record, provided that the records are
+# of different types. For example, planetlab.us.arizona may have both an SA
+# and a MA record, but cannot have two SA records.
+
+class GeniRecord():
+
+ ##
+ # Create a Geni Record
+ #
+ # @param name if !=None, assign the name of the record
+ # @param gid if !=None, assign the gid of the record
+ # @param type one of user | sa | ma | slice | component
+ # @param pointer is a pointer to a PLC record
+ # @param dict if !=None, then fill in this record from the dictionary
+
+ def __init__(self, name=None, gid=None, type=None, pointer=None, dict=None, string=None):
+ self.dirty = True
+ self.pl_info = None
+ self.geni_info = None
+ self.name = None
+ self.gid = None
+ self.type = None
+ self.pointer = None
+ if name:
+ self.set_name(name)
+ if gid:
+ self.set_gid(gid)
+ if type:
+ self.set_type(type)
+ if pointer:
+ self.set_pointer(pointer)
+ if dict:
+ self.load_from_dict(dict)
+ if string:
+ self.load_from_string(string)
+
+ ##
+ # Set the name of the record
+ #
+ # @param name is a string containing the HRN
+
+ def set_name(self, name):
+ self.name = name
+ self.dirty = True
+
+ ##
+ # Set the GID of the record
+ #
+ # @param gid is a GID object or the string representation of a GID object
+
+ def set_gid(self, gid):
+ if isinstance(gid, str):
+ self.gid = gid
+ else:
+ self.gid = gid.save_to_string(save_parents=True)
+ self.dirty = True
+
+ ##
+ # Set the type of the record
+ #
+ # @param type is a string: user | sa | ma | slice | component
+
+ def set_type(self, type):
+ self.type = type
+ self.dirty = True
+
+ ##
+ # Set the pointer of the record
+ #
+ # @param pointer is an integer containing the ID of a PLC record
+
+ def set_pointer(self, pointer):
+ self.pointer = pointer
+ self.dirty = True
+
+ ##
+ # Set the PLC info of the record
+ #
+ # @param pl_info is a dictionary containing planetlab info
+
+ def set_pl_info(self, pl_info):
+ self.pl_info = pl_info
+ self.dirty = True
+
+ ##
+ # Set the geni info the record
+ #
+ # @param geni_info is a dictionary containing geni info
+
+ def set_geni_info(self, geni_info):
+ self.geni_info = geni_info
+ self.dirty = True
+
+ ##
+ # Return the pl_info of the record, or an empty dictionary if none exists
+
+ def get_pl_info(self):
+ if self.pl_info:
+ return self.pl_info
+ else:
+ return {}
+
+ ##
+ # Return the geni_info of the record, or an empty dictionary if none exists
+
+ def get_geni_info(self):
+ if self.geni_info:
+ return self.geni_info
+ else:
+ return {}
+
+ ##
+ # Return the name (HRN) of the record
+
+ def get_name(self):
+ return self.name
+
+ ##
+ # Return the type of the record
+
+ def get_type(self):
+ return self.type
+
+ ##
+ # Return the pointer of the record. The pointer is an integer that may be
+ # used to look up the record in the PLC database. The evaluation of pointer
+ # depends on the type of the record
+
+ def get_pointer(self):
+ return self.pointer
+
+ ##
+ # Return the GID of the record, in the form of a GID object
+ # TODO: not the best name for the function, because we have things called
+ # gidObjects in the Cred
+
+ def get_gid_object(self):
+ return GID(string=self.gid)
+
+ ##
+ # Return a key that uniquely identifies this record among all records in
+ # Geni. This key is used to uniquely identify the record in the Geni
+ # database.
+
+ def get_key(self):
+ return self.name + "#" + self.type
+
+ ##
+ # Returns a list of field names in this record. pl_info, geni_info are not
+ # included because they are not part of the record that is stored in the
+ # database, but are rather computed values from other entities
+
+ def get_field_names(self):
+ return ["name", "gid", "type", "pointer"]
+
+ ##
+ # Given a field name ("name", "gid", ...) return the value of that field.
+ #
+ # @param name is the name of field to be returned
+
+ def get_field_value_string(self, fieldname):
+ if fieldname == "key":
+ val = self.get_key()
+ else:
+ val = getattr(self, fieldname)
+ if isinstance(val, str):
+ return "'" + str(val) + "'"
+ else:
+ return str(val)
+
+ ##
+ # Given a list of field names, return a list of values for those fields.
+ #
+ # @param fieldnames is a list of field names
+
+ def get_field_value_strings(self, fieldnames):
+ strs = []
+ for fieldname in fieldnames:
+ strs.append(self.get_field_value_string(fieldname))
+ return strs
+
+ ##
+ # Return the record in the form of a dictionary
+
+ def as_dict(self):
+ dict = {}
+ names = self.get_field_names()
+ for name in names:
+ dict[name] = getattr(self, name)
+
+ if self.pl_info:
+ dict['pl_info'] = self.pl_info
+
+ if self.geni_info:
+ dict['geni_info'] = self.geni_info
+
+ return dict
+
+ ##
+ # Load the record from a dictionary
+ #
+ # @param dict dictionary to load record fields from
+
+ def load_from_dict(self, dict):
+ self.set_name(dict['name'])
+ self.set_gid(dict['gid'])
+ self.set_type(dict['type'])
+ self.set_pointer(dict['pointer'])
+ if "pl_info" in dict:
+ self.set_pl_info(dict["pl_info"])
+ if "geni_info" in dict:
+ self.set_geni_info(dict["geni_info"])
+
+ ##
+ # Save the record to a string. The string contains an XML representation of
+ # the record.
+
+ def save_to_string(self):
+ dict = self.as_dict()
+ str = xmlrpclib.dumps((dict,), allow_none=True)
+ return str
+
+ ##
+ # Load the record from a string. The string is assumed to contain an XML
+ # representation of the record.
+
+ def load_from_string(self, str):
+ dict = xmlrpclib.loads(str)[0][0]
+ self.load_from_dict(dict)
+
+ ##
+ # Dump the record to stdout
+ #
+ # @param dump_parents if true, then the parents of the GID will be dumped
+
+ def dump(self, dump_parents=False):
+ print "RECORD", self.name
+ print " hrn:", self.name
+ print " type:", self.type
+ print " gid:"
+ if (not self.gid):
+ print " None"
+ else:
+ self.get_gid_object().dump(8, dump_parents)
+ print " pointer:", self.pointer
+
+ print " geni_info:"
+ geni_info = getattr(self, "geni_info", {})
+ if geni_info:
+ for key in geni_info.keys():
+ print " ", key, ":", geni_info[key]
+
+ print " pl_info:"
+ pl_info = getattr(self, "pl_info", {})
+ if pl_info:
+ for key in pl_info.keys():
+ print " ", key, ":", pl_info[key]
+
+
--- /dev/null
+# remoteshell.py
+#
+# interface to the PLC api via xmlrpc
+#
+# RemoteShell() exports an API that looks identical to that exported by
+# PLC.Shell.Shell(). It's meant to be a drop in replacement for running
+# geniwrapper on a different machine than PLC.
+
+import xmlrpclib
+
+class RemoteShell:
+ def __init__(self):
+ self.servers = {}
+
+ def call(self, name, pl_auth, *args):
+
+ key = pl_auth["Url"] + "#" + pl_auth["Username"]
+
+ if not (key in self.servers):
+ server = xmlrpclib.Server(pl_auth["Url"], verbose = 0, allow_none=True)
+ #server.AdmAuthCheck(pl_auth)
+ server.AuthCheck(pl_auth)
+ self.servers[key] = server
+
+ server = self.servers[key]
+
+ arglist = ["pl_auth"]
+ for arg in args:
+ arglist.append(repr(arg))
+
+ str = "server." + name + "(" + ",".join(arglist) + ")"
+ result = eval(str)
+
+ return result
+
+ # TODO: there's probably an automatic way to import all these stubs
+
+ def AddInitScript(self, pl_auth, *args):
+ return self.call("AddInitScript", pl_auth, *args)
+
+ def AddNode(self, pl_auth, *args):
+ return self.call("AddNode", pl_auth, *args)
+
+ def AddPerson(self, pl_auth, *args):
+ return self.call("AddPerson", pl_auth, *args)
+
+ def AddSite(self, pl_auth, *args):
+ return self.call("AddSite", pl_auth, *args)
+
+ def AddSlice(self, pl_auth, *args):
+ return self.call("AddSlice", pl_auth, *args)
+
+ def DeleteNode(self, pl_auth, *args):
+ return self.call("DeleteNode", pl_auth, *args)
+
+ def DeletePerson(self, pl_auth, *args):
+ return self.call("DeletePerson", pl_auth, *args)
+
+ def DeleteSite(self, pl_auth, *args):
+ return self.call("DeleteSite", pl_auth, *args)
+
+ def DeleteSlice(self, pl_auth, *args):
+ return self.call("DeleteSlice", pl_auth, *args)
+
+ def GetInitScripts(self, pl_auth, *args):
+ return self.call("GetInitScripts", pl_auth, *args)
+
+ def GetKeys(self, pl_auth, *args):
+ return self.call("GetKeys", pl_auth, *args)
+
+ def GetNodes(self, pl_auth, *args):
+ return self.call("GetNodes", pl_auth, *args)
+
+ def GetPersons(self, pl_auth, *args):
+ return self.call("GetPersons", pl_auth, *args)
+
+ def GetSites(self, pl_auth, *args):
+ return self.call("GetSites", pl_auth, *args)
+
+ def GetSliceAttributes(self, pl_auth, *args):
+ return self.call("GetSliceAttributes", pl_auth, *args)
+
+ def GetSlices(self, pl_auth, *args):
+ return self.call("GetSlices", pl_auth, *args)
+
+ def UpdateNode(self, pl_auth, *args):
+ return self.call("UpdateNode", pl_auth, *args)
+
+ def UpdatePerson(self, pl_auth, *args):
+ return self.call("UpdatePerson", pl_auth, *args)
+
+ def UpdateSite(self, pl_auth, *args):
+ return self.call("UpdateSite", pl_auth, *args)
+
+ def UpdateSlice(self, pl_auth, *args):
+ return self.call("UpdateSlice", pl_auth, *args)
+
+
--- /dev/null
+def trace(x):
+ print x
+
+def error(x):
+ print x
--- /dev/null
+##
+# This Module implements rights and lists of rights for the Geni wrapper. Rights
+# are implemented by two classes:
+#
+# Right - represents a single right
+#
+# RightList - represents a list of rights
+#
+# A right may allow several different operations. For example, the "info" right
+# allows "listslices", "listcomponentresources", etc.
+##
+
+##
+# privilege_table is a list of priviliges and what operations are allowed
+# per privilege.
+
+privilege_table = {"authority": ["*"],
+ "refresh": ["remove", "update"],
+ "resolve": ["resolve", "list", "getcredential"],
+ "sa": ["*"],
+ "embed": ["getticket", "redeemslice", "createslice", "deleteslice", "updateslice", "getsliceresources"],
+ "bind": ["getticket", "loanresources"],
+ "control": ["updateslice", "createslice", "stopslice", "startslice", "deleteslice", "resetslice", "getsliceresources"],
+ "info": ["listslices", "listnodes"],
+ "ma": ["*"]}
+
+##
+# The Right class represents a single privilege.
+
+class Right:
+ ##
+ # Create a new right.
+ #
+ # @param kind is a string naming the right. For example "control"
+
+ 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".
+
+ 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
+
+ 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
+
+ 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 "*" in my_allowed_ops:
+ return True
+
+ for right in child_allowed_ops:
+ if not right in my_allowed_ops:
+ return False
+
+ return True
+
+##
+# A RightList object represents a list of privileges.
+
+class RightList:
+ ##
+ # Create a new rightlist object, containing no rights.
+ #
+ # @param string if string!=None, load the rightlist from the string
+
+ def __init__(self, string=None):
+ self.rights = []
+ if string:
+ self.load_from_string(string)
+
+ ##
+ # Add a right to this list
+ #
+ # @param right is either a Right object or a string describing the right
+
+ def add(self, right):
+ if isinstance(right, str):
+ right = Right(kind = right)
+ self.rights.append(right)
+
+ ##
+ # Load the rightlist object from a string
+
+ def load_from_string(self, string):
+ self.rights = []
+
+ # none == no rights, so leave the list empty
+ if not string:
+ return
+
+ parts = string.split(",")
+ for part in parts:
+ self.rights.append(Right(part))
+
+ ##
+ # Save the rightlist object to a string. It is saved in the format of a
+ # comma-separated list.
+
+ def save_to_string(self):
+ right_names = []
+ for right in self.rights:
+ right_names.append(right.kind)
+
+ return ",".join(right_names)
+
+ ##
+ # Check to see if some right in this list allows an operation. This is
+ # done by evaluating the can_perform function of each operation in the
+ # list.
+ #
+ # @param op_name is an operation to check, for example "listslices"
+
+ def can_perform(self, op_name):
+ for right in self.rights:
+ if right.can_perform(op_name):
+ return True
+ return False
+
+ ##
+ # Check to see if all of the rights in this rightlist are a superset
+ # of all the rights in a child rightlist. A rightlist is a superset
+ # if there is no operation in the child rightlist that cannot be
+ # performed in the parent rightlist.
+ #
+ # @param child is a rightlist object describing the child
+
+ def is_superset(self, child):
+ for child_right in child.rights:
+ allowed = False
+ for my_right in self.rights:
+ if my_right.is_superset(child_right):
+ allowed = True
+ if not allowed:
+ return False
+ return True
+
--- /dev/null
+import sys
+import pprint
+import os
+from xml.dom import minidom
+from types import StringTypes
+
+class Rspec():
+
+ def __init__(self, xml = None, xsd = None):
+ self.xsd = xsd # schema
+ self.rootNode = None # root of the dom
+ self.dict = {} # dict of the rspec.
+ if xml:
+ if type(xml) == file:
+ self.parseFile(xml)
+ if type(xml) == str:
+ self.parseString(xml)
+ self.dict = self.toDict()
+
+ def _getText(self, nodelist):
+ rc = ""
+ for node in nodelist:
+ if node.nodeType == node.TEXT_NODE:
+ rc = rc + node.data
+ return rc
+
+ # The rspec is comprised of 2 parts, and 1 reference:
+ # attributes/elements describe individual resources
+ # complexTypes are used to describe a set of attributes/elements
+ # complexTypes can include a reference to other complexTypes.
+
+
+ def _getName(self, node):
+ '''Gets name of node. If tag has no name, then return tag's localName'''
+ name = None
+ if not node.nodeName.startswith("#"):
+ if node.localName:
+ name = node.localName
+ elif node.attributes.has_key("name"):
+ name = node.attributes.get("name").value
+ return name
+
+
+ # Attribute. {name : nameofattribute, {items: values})
+ def _attributeDict(self, attributeDom):
+ '''Traverse single attribute node. Create a dict {attributename : {name: value,}]}'''
+ node = {} # parsed dict
+ for attr in attributeDom.attributes.keys():
+ node[attr] = attributeDom.attributes.get(attr).value
+ return node
+
+
+ def toDict(self, nodeDom = None):
+ """
+ convert this rspec to a dict and return it.
+ """
+ node = {}
+ if not nodeDom:
+ nodeDom = self.rootNode
+
+ elementName = nodeDom.nodeName
+ if elementName and not elementName.startswith("#"):
+ # attributes have tags and values. get {tag: value}, else {type: value}
+ node[elementName] = self._attributeDict(nodeDom)
+ #node.update(self._attributeDict(nodeDom))
+ # resolve the child nodes.
+ if nodeDom.hasChildNodes():
+ for child in nodeDom.childNodes:
+ childName = self._getName(child)
+ if not childName:
+ continue
+ if not node[elementName].has_key(childName):
+ node[elementName][childName] = []
+ #node[childName] = []
+ childdict = self.toDict(child)
+ for value in childdict.values():
+ node[elementName][childName].append(value)
+ #node[childName].append(self.toDict(child))
+ return node
+
+
+ def toxml(self):
+ """
+ convert this rspec to an xml string and return it.
+ """
+ return self.rootNode.toxml()
+
+
+ def toprettyxml(self):
+ """
+ print this rspec in xml in a pretty format.
+ """
+ return self.rootNode.toprettyxml()
+
+
+ def parseFile(self, filename):
+ """
+ read a local xml file and store it as a dom object.
+ """
+ dom = minidom.parse(filename)
+ self.rootNode = dom.childNodes[0]
+
+
+ def parseString(self, xml):
+ """
+ read an xml string and store it as a dom object.
+ """
+ xml = xml.replace('\n', '').replace('\t', '').strip()
+ dom = minidom.parseString(xml)
+ self.rootNode = dom.childNodes[0]
+
+
+ def dict2dom(self, rdict, include_doc = False):
+ """
+ convert a dict object into a dom object.
+ """
+
+ def elementNode(tagname, rd):
+ element = minidom.Element(tagname)
+ for key in rd.keys():
+ if isinstance(rd[key], StringTypes):
+ element.setAttribute(key, rd[key])
+ elif isinstance(rd[key], dict):
+ child = elementNode(key, rd[key])
+ element.appendChild(child)
+ elif isinstance(rd[key], list):
+ for item in rd[key]:
+ child = elementNode(key, item)
+ element.appendChild(child)
+ return element
+
+ node = elementNode(rdict.keys()[0], rdict.values()[0])
+ if include_doc:
+ rootNode = minidom.Document()
+ rootNode.appendChild(node)
+ else:
+ rootNode = node
+ return rootNode
+
+
+ def parseDict(self, rdict, include_doc = True):
+ """
+ Convert a dictionary into a dom object and store it.
+ """
+ self.rootNode = self.dict2dom(rdict, include_doc)
+
+
+ def getDictsByTagName(self, tagname, dom = None):
+ """
+ Search the dom for all elements with the specified tagname
+ and return them as a list of dicts
+ """
+ if not dom:
+ dom = self.rootNode
+ dicts = []
+ doms = dom.getElementsByTagName(tagname)
+ dictlist = [self.toDict(d) for d in doms]
+ for item in dictlist:
+ for value in item.values():
+ dicts.append(value)
+ return dicts
+
+# vim:ts=4:expandtab
--- /dev/null
+import os
+
+from gid import *
+
+class TrustedRootList():
+ def __init__(self, dir="./trusted_roots"):
+ self.basedir = dir
+
+ # create the directory to hold the files
+ try:
+ os.makedirs(self.basedir)\r
+ # if the path already exists then pass\r
+ except OSError, (errno, strerr):\r
+ if errno == 17:\r
+ pass
+
+ def add_gid(self, gid):
+ fn = os.path.join(self.basedir, gid.get_hrn() + ".gid")
+
+ gid.save_to_file(fn)
+
+ def get_list(self):
+ gid_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 = GID(filename = fn)
+ gid_list.append(gid)
+
+ return gid_list
+
--- /dev/null
+from excep import *
+SR_SUFFIX = '_srr'
+CR_SUFFIX = '_crr'
+
+global_sr_tree = None
+global_cr_tree = None
+
+def set_tree_globals(tree1, tree2):
+ global global_sr_tree
+ global global_cr_tree
+ global_sr_tree = tree1
+ global_cr_tree = tree2
+
+def get_tree_globals():
+ return (global_sr_tree, global_cr_tree)
+
+#function converts a hierarchical name from geni format to array of strings
+def geni_to_arr(name):
+ arrayName = []
+ try:
+ parts = name.split(".")
+ for i in range(len(parts)):
+ arrayName.append(parts[i])
+ return arrayName
+ except:
+ raise MalformedHrnException(name)
+
+#used to parse the function name and the parameters specified in "operation_request"
+def msg_to_params(str):
+ try:
+ return eval(str)
+ except:
+ raise InvalidRPCParams(str)
+
+#returns the authority hrn of a given 'hrn'
+def obtain_authority(hrn):
+ parts = hrn.split(".")
+ auth_str = ''
+ if len(parts) > 1:
+ auth_str = parts[0]+''
+ for i in range(1, len(parts)-1):
+ auth_str = auth_str + '.' + parts[i]
+ return auth_str
+
+#returns the last element of an hrn
+def get_leaf(hrn):
+ parts = hrn.split(".")
+ return parts[len(parts)-1]
+
+#checks whether the 'hrn_auth' is an authority of 'hrn'
+def check_authority(hrn, hrn_auth):
+ arr = geni_to_arr(hrn)
+ arr_auth = geni_to_arr(hrn_auth)
+ try:
+ for i in range(len(arr_auth)):
+ if arr[i] != arr_auth[i]:
+ return False
+ except:
+ return False
+ return True
+
+def hrn_to_tablename(hrn,type):
+ hrn = hrn.replace(".","$")
+ if type == 'slc':
+ hrn = hrn + SR_SUFFIX
+ else:
+ hrn = hrn + CR_SUFFIX
+ return hrn
+