--- /dev/null
+# geniclient.py
+#
+# geni client
+#
+# implements the client-side of the GENI API.
+#
+# TODO: Investigate ways to combine this with existing PLC API?
+
+import xmlrpclib
+
+from gid import *
+from credential import *
+from record import *
+
+# 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
+# GeniClient:\r
+#\r
+# Class for performing GeniClient operations.\r
+\r
+class GeniClient():
+ # url = url of server
+ # key_file = private key file of client
+ # cert_file = x.509 cert of client
+ 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)
+
+ 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. The same as calling
+ # get_credential(..., cred=None,...)
+
+ def get_self_credential(self, type, name):
+ cred_str = self.server.get_self_credential(type, name)
+ return Credential(string = cred_str)
+
+ def get_credential(self, cred, type, name):
+ cred_str = self.server.get_credential(cred.save_to_string(), type, name)
+ return Credential(string = cred_str)
+
+ def resolve(self, cred, name):
+ result_dict_list = self.server.resolve(cred.save_to_string(), name)
+ result_rec_list = []
+ for dict in result_dict_list:
+ result_rec_list.append(GeniRecord(dict=dict))
+ return result_rec_list
+
--- /dev/null
+# geniserver.py
+#
+# geniwrapper server
+#
+# implements a general-purpose server layer for geni. This should be usable on
+# the registry, component, or other interfaces.
+#
+# TODO: investigate ways to combine this with existing PLC server?
+
+import SimpleXMLRPCServer
+
+import SocketServer
+import BaseHTTPServer\r
+import SimpleHTTPServer\r
+import SimpleXMLRPCServer\r
+\r
+from excep import *\r
+from cert import *\r
+from credential import *\r
+\r
+import socket, os\r
+from OpenSSL import SSL\r
+\r
+# verify_callback\r
+#\r
+# verification callback for pyOpenSSL. We do our own checking of keys because\r
+# we have our own authentication spec. Thus we disable several of the normal\r
+# prohibitions that OpenSSL places on certificates\r
+\r
+def verify_callback(conn, x509, err, depth, preverify):\r
+ # if the cert has been preverified, then it is ok\r
+ 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
+
+ # 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
+\r
+# SecureXMLServer\r
+#\r
+# taken from the web (XXX find reference). Implements an HTTPS xmlrpc server\r
+
+class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):\r
+ def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):\r
+ """Secure XML-RPC server.\r
+\r
+ It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.\r
+ """\r
+ self.logRequests = logRequests\r
+\r
+ SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, None, None)\r
+ SocketServer.BaseServer.__init__(self, server_address, HandlerClass)\r
+ ctx = SSL.Context(SSL.SSLv23_METHOD)\r
+ ctx.use_privatekey_file(key_file)\r
+ ctx.use_certificate_file(cert_file)\r
+ ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)\r
+ ctx.set_app_data(self)\r
+ self.socket = SSL.Connection(ctx, socket.socket(self.address_family,\r
+ self.socket_type))\r
+ self.server_bind()\r
+ self.server_activate()\r
+\r
+# SecureXMLRpcRequestHandler\r
+#\r
+# taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler\r
+\r
+class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):\r
+ """Secure XML-RPC request handler class.\r
+\r
+ It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.\r
+ """\r
+ def setup(self):\r
+ self.connection = self.request\r
+ self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)\r
+ self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)\r
+\r
+ def do_POST(self):\r
+ """Handles the HTTPS POST request.\r
+\r
+ It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.\r
+ """\r
+\r
+ try:\r
+ # get arguments\r
+ data = self.rfile.read(int(self.headers["content-length"]))\r
+ # In previous versions of SimpleXMLRPCServer, _dispatch\r
+ # could be overridden in this class, instead of in\r
+ # SimpleXMLRPCDispatcher. To maintain backwards compatibility,\r
+ # check to see if a subclass implements _dispatch and dispatch\r
+ # using that method if present.\r
+ response = self.server._marshaled_dispatch(\r
+ data, getattr(self, '_dispatch', None)\r
+ )\r
+ except: # This should only happen if the module is buggy\r
+ # internal error, report as HTTP server error\r
+ self.send_response(500)\r
+ self.end_headers()\r
+ else:\r
+ # got a valid XML RPC response\r
+ self.send_response(200)\r
+ self.send_header("Content-type", "text/xml")\r
+ self.send_header("Content-length", str(len(response)))\r
+ self.end_headers()\r
+ self.wfile.write(response)\r
+\r
+ # shut down the connection\r
+ self.wfile.flush()\r
+ self.connection.shutdown() # Modified here!\r
+
+# GeniServer
+#
+# Class for a general purpose geni server.
+#
+# 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():
+ def __init__(self, ip, port, key_file, cert_file):
+ self.key = Keypair(filename = key_file)
+ self.cert = Certificate(filename = cert_file)
+ self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
+ self.register_functions()
+
+ def decode_authentication(self, cred_string):
+ self.client_cred = Credential(string = cred_string)
+ self.client_gid = self.client_cred.get_gid_caller()
+
+ # 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 the certificate that the client is using
+ peer_cert = self.server.peer_cert
+ if not peer_cert.is_pubkey(self.client_gid.get_pubkey()):
+ raise ConnectionKeyGIDMismatch(self.client_gid.get_subject())
+
+ # register_functions override this to add more functions
+ def register_functions(self):
+ self.server.register_function(self.noop)
+
+ def noop(self, cred, anything):
+ self.decode_authentication(cred)
+
+ return anything
+
+ def run(self):
+ self.server.serve_forever()
+
+
--- /dev/null
+# record.py
+#
+# implements support for geni records
+#
+# TODO: Use existing PLC database methods? or keep this separate?
+
+from pg import DB
+
+# Record is a tuple (Name, GID, Type, Info)
+# info is implemented as a pointer to a PLC record
+# privkey is stored for convenience on the registry for entities which
+# the registry holds their private keys
+
+class GeniRecord():
+ def __init__(self, name=None, gid=None, type=None, pointer=None, privkey=None, dict=None):
+ self.dirty=True
+ 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 privkey:
+ self.set_privkey(privkey)
+ if dict:
+ self.set_name(dict['name'])
+ self.set_gid(dict['gid'])
+ self.set_type(dict['type'])
+ self.set_pointer(dict['pointer'])
+ self.set_privkey(dict['privkey'])
+
+ def set_name(self, name):
+ self.name = name
+ self.dirty = True
+
+ def set_gid(self, gid):
+ self.gid = gid
+ self.dirty = True
+
+ def set_type(self, type):
+ self.type = type
+ self.dirty = True
+
+ def set_pointer(self, pointer):
+ self.pointer = pointer
+ self.dirty = True
+
+ def set_privkey(self, privkey):
+ self.privkey = privkey
+ self.dirty = True
+
+ def get_key(self):
+ return self.name + "#" + self.type
+
+ def get_field_names(self):
+ return ["name", "gid", "type", "pointer", "privkey"]
+
+ 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)
+
+ def get_field_value_strings(self, fieldnames):
+ strs = []
+ for fieldname in fieldnames:
+ strs.append(self.get_field_value_string(fieldname))
+ return strs
+
+ def as_dict(self):
+ dict = {}
+ names = self.get_field_names()
+ for name in names:
+ dict[name] = self.getattr(name)
+ return dict
+
+# GeniTable
+#
+# Represents a single table on a registry for a single authority.
+
+class GeniTable():
+ def __init__(self, create=False, hrn="unspecified.default.registry", cninfo=None, privkey=None, gid=None):
+ # XXX privkey/gid are necessary so the table can generate GIDs for its
+ # records; might move that out of here as it doesn't seem the right place
+
+ self.hrn = hrn
+
+ # pgsql doesn't like table names with "." in them, to replace it with "$"
+ self.tablename = 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'])[
+
+ # the private key is necessary for creation of GIDs
+ self.privkey = privkey
+
+ self.gid = gid
+
+ # if asked to create the table, then create it
+ if create:
+ self.create()
+
+ def create(self):
+ querystr = "CREATE TABLE " + self.tablename + " ( \
+ key text, \
+ name text, \
+ gid text, \
+ type text, \
+ privkey text, \
+ pointer integer);"
+
+ self.cnx.query('DROP TABLE IF EXISTS ' + self.tablename)
+ self.cnx.query(querystr)
+
+ 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 resolve_raw(self, type, hrn):
+ query_str = "SELECT * FROM " + self.tablename + " WHERE name = '" + hrn + "'"
+ 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 resolve(self, type, hrn):
+ result_dict_list = self.resolve_raw(type, hrn)
+ result_rec_list = []
+ for dict in result_dict_list:
+ result_rec_list.append(GeniRecord(dict=dict))
+ return result_rec_list
+
+ def create_gid(self, hrn, uuid, pubkey):
+ gid = GID(subject=hrn, uuid=uuid, hrn=hrn)
+ gid.set_pubkey(pubkey)
+ gid.set_issuer(key=self.privkey, subject=self.hrn)
+ gid.set_parent(self.gid)
+ gid.encode()
+ gid.sign()
+
+ return gid
+
+ def update_gid(self, record)
+ old_gid = GID(string = record.get_gid())
+ pubkey = old_gid.get_pubkey()
+
+ gid = self.create_gid(old_gid.get_hrn(), old_gid.get_uuid(), old_gid.get_pubkey())
+
+ record.set_gid(gid.save_to_string())
+