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