From: Scott Baker Date: Tue, 5 Aug 2008 17:36:55 +0000 (+0000) Subject: checked in initial versions X-Git-Tag: sfa-0.9-0@14641~870 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=e6b636df901a4e13c5c1079d19b08e897e8222f2;p=sfa.git checked in initial versions --- diff --git a/util/geniclient.py b/util/geniclient.py new file mode 100644 index 00000000..c85b8c11 --- /dev/null +++ b/util/geniclient.py @@ -0,0 +1,80 @@ +# 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): + # create a HTTPS connection object from a host descriptor + # host may be a string, or a (host, x509-dict) tuple + import httplib + host, extra_headers, x509 = self.get_host_info(host) + try: + HTTPS = httplib.HTTPS() + except AttributeError: + raise NotImplementedError( + "your version of httplib doesn't support HTTPS" + ) + else: + return httplib.HTTPS(host, None, key_file=self.key_file, cert_file=self.cert_file) #**(x509 or {})) + +# GeniClient: +# +# Class for performing GeniClient operations. + +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 + diff --git a/util/geniserver.py b/util/geniserver.py new file mode 100644 index 00000000..6a8eb878 --- /dev/null +++ b/util/geniserver.py @@ -0,0 +1,188 @@ +# 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 +import SimpleHTTPServer +import SimpleXMLRPCServer + +from excep import * +from cert import * +from credential import * + +import socket, os +from OpenSSL import SSL + +# verify_callback +# +# 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 + + # 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 + +# SecureXMLServer +# +# 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, None, 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() + +# SecureXMLRpcRequestHandler +# +# 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! + +# 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() + + diff --git a/util/record.py b/util/record.py new file mode 100644 index 00000000..7c01abde --- /dev/null +++ b/util/record.py @@ -0,0 +1,175 @@ +# 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()) +