d452cb1aeffda1c2e155594abf95ad3b835fbfce
[sfa.git] / geni / util / geniserver.py
1 ##
2 # This module implements a general-purpose server layer for geni.
3 # The same basic server should be usable on the registry, component, or
4 # other interfaces.
5 #
6 # TODO: investigate ways to combine this with existing PLC server?
7 ##
8
9 import SimpleXMLRPCServer
10
11 import sys
12 import traceback
13 import threading
14 import SocketServer
15 import BaseHTTPServer
16 import SimpleHTTPServer
17 import SimpleXMLRPCServer
18 import xmlrpclib
19 import string
20 from geni.util.api import GeniAPI 
21
22 ##
23 # Verification callback for pyOpenSSL. We do our own checking of keys because
24 # we have our own authentication spec. Thus we disable several of the normal
25 # prohibitions that OpenSSL places on certificates
26
27 def verify_callback(conn, x509, err, depth, preverify):
28     # if the cert has been preverified, then it is ok
29     if preverify:
30        #print "  preverified"
31        return 1
32
33     # we're only passing single certificates, not chains
34     if depth > 0:
35        #print "  depth > 0 in verify_callback"
36        return 0
37
38     # create a Certificate object and load it from the client's x509
39     ctx = conn.get_context()
40     server = ctx.get_app_data()
41     server.peer_cert = Certificate()
42     server.peer_cert.load_from_pyopenssl_x509(x509)
43
44     # the certificate verification done by openssl checks a number of things
45     # that we aren't interested in, so we look out for those error messages
46     # and ignore them
47
48     # XXX SMBAKER: I don't know what this error is, but it's being returned
49     # by newer pl nodes.
50     if err == 9:
51        #print "  X509_V_ERR_CERT_NOT_YET_VALID"
52        return 1
53
54     # allow self-signed certificates
55     if err == 18:
56        #print "  X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"
57        return 1
58
59     # allow certs that don't have an issuer
60     if err == 20:
61        #print "  X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"
62        return 1
63
64     # allow certs that are untrusted
65     if err == 21:
66        #print "  X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"
67        return 1
68
69     # allow certs that are untrusted
70     if err == 27:
71        #print "  X509_V_ERR_CERT_UNTRUSTED"
72        return 1
73
74     print "  error", err, "in verify_callback"
75
76     return 0\r
77
78 ##
79 # Taken from the web (XXX find reference). Implements an HTTPS xmlrpc server
80
81 class SecureXMLRPCServer(BaseHTTPServer.HTTPServer,SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
82     def __init__(self, server_address, HandlerClass, key_file, cert_file, logRequests=True):
83         """Secure XML-RPC server.
84
85         It it very similar to SimpleXMLRPCServer but it uses HTTPS for transporting XML data.
86         """
87         self.logRequests = logRequests
88
89         SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self, True, None)
90         SocketServer.BaseServer.__init__(self, server_address, HandlerClass)
91         ctx = SSL.Context(SSL.SSLv23_METHOD)
92         ctx.use_privatekey_file(key_file)
93         ctx.use_certificate_file(cert_file)
94         ctx.set_verify(SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_callback)
95         ctx.set_app_data(self)
96         self.socket = SSL.Connection(ctx, socket.socket(self.address_family,
97                                                         self.socket_type))
98         self.server_bind()
99         self.server_activate()
100
101     # _dispatch
102     #
103     # Convert an exception on the server to a full stack trace and send it to
104     # the client.
105
106     def _dispatch(self, method, params):
107         try:
108             return SimpleXMLRPCServer.SimpleXMLRPCDispatcher._dispatch(self, method, params)
109         except:
110             # can't use format_exc() as it is not available in jython yet
111             # (evein in trunk).
112             type, value, tb = sys.exc_info()
113             raise xmlrpclib.Fault(1,''.join(traceback.format_exception(type, value, tb)))
114
115 ##
116 # taken from the web (XXX find reference). Implents HTTPS xmlrpc request handler
117
118 class SecureXMLRpcRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
119     """Secure XML-RPC request handler class.
120
121     It it very similar to SimpleXMLRPCRequestHandler but it uses HTTPS for transporting XML data.
122     """
123     def setup(self):
124         #self.api = GeniAPI()
125         self.connection = self.request
126         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
127         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
128
129     def do_POST(self):
130         """Handles the HTTPS POST request.
131
132         It was copied out from SimpleXMLRPCServer.py and modified to shutdown the socket cleanly.
133         """
134
135         try:
136             # get arguments
137             request = self.rfile.read(int(self.headers["content-length"]))
138
139             # In previous versions of SimpleXMLRPCServer, _dispatch
140             # could be overridden in this class, instead of in
141             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
142             # check to see if a subclass implements _dispatch and dispatch
143             # using that method if present.
144             response = self.server._marshaled_dispatch(request, getattr(self, '_dispatch', None))
145             #response = self.api.handle(None, request)
146
147         except: # This should only happen if the module is buggy
148             # internal error, report as HTTP server error
149             self.send_response(500)
150
151             self.end_headers()
152         else:
153             # got a valid XML RPC response
154             self.send_response(200)
155             self.send_header("Content-type", "text/xml")
156             self.send_header("Content-length", str(len(response)))
157             self.end_headers()
158             self.wfile.write(response)
159
160             # shut down the connection
161             self.wfile.flush()
162             self.connection.shutdown() # Modified here!
163
164 ##
165 # Implements an HTTPS XML-RPC server. Generally it is expected that GENI
166 # functions will take a credential string, which is passed to
167 # decode_authentication. Decode_authentication() will verify the validity of
168 # the credential, and verify that the user is using the key that matches the
169 # GID supplied in the credential.
170
171 class GeniServer(threading.Thread):
172
173     ##
174     # Create a new GeniServer object.
175     #
176     # @param ip the ip address to listen on
177     # @param port the port to listen on
178     # @param key_file private key filename of registry
179     # @param cert_file certificate filename containing public key 
180     #   (could be a GID file)
181
182     def __init__(self, ip, port, key_file, cert_file):
183         threading.Thread.__init__(self)
184         self.key = Keypair(filename = key_file)
185         self.cert = Certificate(filename = cert_file)
186         self.server = SecureXMLRPCServer((ip, port), SecureXMLRpcRequestHandler, key_file, cert_file)
187         self.trusted_cert_list = None
188         self.register_functions()
189
190     ##
191     # Decode the credential string that was submitted by the caller. Several
192     # checks are performed to ensure that the credential is valid, and that the
193     # callerGID included in the credential matches the caller that is
194     # connected to the HTTPS connection.
195
196     def decode_authentication(self, cred_string, operation):
197         self.client_cred = Credential(string = cred_string)
198         self.client_gid = self.client_cred.get_gid_caller()
199         self.object_gid = self.client_cred.get_gid_object()
200
201         # make sure the client_gid is not blank
202         if not self.client_gid:
203             raise MissingCallerGID(self.client_cred.get_subject())
204
205         # make sure the client_gid matches client's certificate
206         peer_cert = self.server.peer_cert
207         if not peer_cert.is_pubkey(self.client_gid.get_pubkey()):
208             raise ConnectionKeyGIDMismatch(self.client_gid.get_subject())
209
210         # make sure the client is allowed to perform the operation
211         if operation:
212             if not self.client_cred.can_perform(operation):
213                 raise InsufficientRights(operation)
214
215         if self.trusted_cert_list:
216             self.client_cred.verify_chain(self.trusted_cert_list)
217             if self.client_gid:
218                 self.client_gid.verify_chain(self.trusted_cert_list)
219             if self.object_gid:
220                 self.object_gid.verify_chain(self.trusted_cert_list)
221
222     ##
223     # Register functions that will be served by the XMLRPC server. This
224     # function should be overrided by each descendant class.
225
226     def register_functions(self):
227         self.server.register_function(self.noop)
228
229     ##
230     # Sample no-op server function. The no-op function decodes the credential
231     # that was passed to it.
232
233     def noop(self, cred, anything):
234         self.decode_authentication(cred)
235
236         return anything
237
238     ##
239     # Execute the server, serving requests forever. 
240
241     def run(self):
242         self.server.serve_forever()
243
244