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