Skip to content

Commit e356772

Browse files
committed
Enable SSL on forwarded requests
1 parent 4b20947 commit e356772

File tree

3 files changed

+74
-10
lines changed

3 files changed

+74
-10
lines changed

jupyter_server_proxy/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import ssl
2+
13
from .handlers import setup_handlers, SuperviseAndProxyHandler
24
from .config import ServerProxy, make_handlers, get_entrypoint_server_processes, make_server_process
35
from notebook.utils import url_path_join as ujoin
@@ -28,8 +30,16 @@ def load_jupyter_server_extension(nbapp):
2830
server_handlers = make_handlers(base_url, server_proccesses)
2931
nbapp.web_app.add_handlers('.*', server_handlers)
3032

33+
# Configure SSL support
34+
ssl_options = None
35+
if serverproxy.https:
36+
ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=serverproxy.cafile)
37+
ssl_context.load_cert_chain(serverproxy.certfile, serverproxy.keyfile)
38+
ssl_context.check_hostname = False
39+
ssl_options = ssl_context
40+
3141
# Set up default handler
32-
setup_handlers(nbapp.web_app, serverproxy.host_whitelist)
42+
setup_handlers(nbapp.web_app, serverproxy.host_whitelist, ssl_options)
3343

3444
launcher_entries = []
3545
icons = {}

jupyter_server_proxy/config.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Traitlets based configuration for jupyter_server_proxy
33
"""
44
from notebook.utils import url_path_join as ujoin
5-
from traitlets import Dict, List, Union, default
5+
from traitlets import Bool, Dict, List, Unicode, Union, default
66
from traitlets.config import Configurable
77
from .handlers import SuperviseAndProxyHandler, AddSlashHandler
88
import pkg_resources
@@ -203,3 +203,45 @@ def host_whitelist(handler, host):
203203
@default("host_whitelist")
204204
def _host_whitelist_default(self):
205205
return ["localhost", "127.0.0.1"]
206+
207+
keyfile = Unicode(
208+
"",
209+
help="""
210+
Path to optional SSL key.
211+
212+
Use with `https=True` and `certfile`.
213+
""",
214+
config=True
215+
)
216+
217+
certfile = Unicode(
218+
"",
219+
help="""
220+
Path to optional SSL cert.
221+
222+
Use with `https=True` and `keyfile`.
223+
""",
224+
config=True
225+
)
226+
227+
cafile = Unicode(
228+
"",
229+
help="""
230+
Path to optional CA file.
231+
232+
Use with `https=True`.
233+
""",
234+
config=True
235+
)
236+
237+
https = Bool(
238+
False,
239+
help="""
240+
Whether to use SSL for forwarded client requests.
241+
242+
If this is set to `True` then you should provide a path to an SSL key,
243+
cert, and CA. Use this if the proxied service expects to service
244+
requests over SSL.
245+
""",
246+
config=True
247+
)

jupyter_server_proxy/handlers.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def __init__(self, *args, **kwargs):
4545
self.absolute_url = kwargs.pop('absolute_url', False)
4646
self.host_whitelist = kwargs.pop('host_whitelist', ['localhost', '127.0.0.1'])
4747
self.subprotocols = None
48+
self.ssl_options = kwargs.pop('ssl_options', None)
4849
super().__init__(*args, **kwargs)
4950

5051
# Support all the methods that tornado does by default except for GET which
@@ -165,7 +166,8 @@ def _build_proxy_request(self, host, port, proxied_path, body):
165166

166167
headers = self.proxy_request_headers()
167168

168-
client_uri = self.get_client_uri('http', host, port, proxied_path)
169+
protocol = 'http' if self.ssl_options is None else 'https'
170+
client_uri = self.get_client_uri(protocol, host, port, proxied_path)
169171
# Some applications check X-Forwarded-Context and X-ProxyContextPath
170172
# headers to see if and where they are being proxied from.
171173
if not self.absolute_url:
@@ -276,7 +278,8 @@ async def proxy_open(self, host, port, proxied_path=''):
276278
if not proxied_path.startswith('/'):
277279
proxied_path = '/' + proxied_path
278280

279-
client_uri = self.get_client_uri('ws', host, port, proxied_path)
281+
protocol = 'ws' if self.ssl_options is None else 'wss'
282+
client_uri = self.get_client_uri(protocol, host, port, proxied_path)
280283
headers = self.request.headers
281284
current_loop = ioloop.IOLoop.current()
282285
ws_connected = current_loop.asyncio_loop.create_future()
@@ -307,7 +310,8 @@ def ping_cb(data):
307310
async def start_websocket_connection():
308311
self.log.info('Trying to establish websocket connection to {}'.format(client_uri))
309312
self._record_activity()
310-
request = httpclient.HTTPRequest(url=client_uri, headers=headers)
313+
request = httpclient.HTTPRequest(url=client_uri, headers=headers,
314+
ssl_options=self.ssl_options)
311315
self.ws = await pingable_ws_connect(request=request,
312316
on_message_callback=message_cb, on_ping_callback=ping_cb,
313317
subprotocols=self.subprotocols)
@@ -330,7 +334,11 @@ def proxy_request_headers(self):
330334
def proxy_request_options(self):
331335
'''A dictionary of options to be used when constructing
332336
a tornado.httpclient.HTTPRequest instance for the proxy request.'''
337+
<<<<<<< HEAD
333338
return dict(follow_redirects=False, connect_timeout=250.0, request_timeout=300.0)
339+
=======
340+
return dict(follow_redirects=False, ssl_options=self.ssl_options)
341+
>>>>>>> Enable SSL on forwarded requests
334342

335343
def check_xsrf_cookie(self):
336344
'''
@@ -556,17 +564,21 @@ def options(self, path):
556564
return self.proxy(self.port, path)
557565

558566

559-
def setup_handlers(web_app, host_whitelist):
567+
def setup_handlers(web_app, host_whitelist, ssl_options):
560568
host_pattern = '.*$'
561569
web_app.add_handlers('.*', [
562570
(url_path_join(web_app.settings['base_url'], r'/proxy/(.*):(\d+)(.*)'),
563-
RemoteProxyHandler, {'absolute_url': False, 'host_whitelist': host_whitelist}),
571+
RemoteProxyHandler, {'absolute_url': False, 'host_whitelist': host_whitelist,
572+
'ssl_options': ssl_options}),
564573
(url_path_join(web_app.settings['base_url'], r'/proxy/absolute/(.*):(\d+)(.*)'),
565-
RemoteProxyHandler, {'absolute_url': True, 'host_whitelist': host_whitelist}),
574+
RemoteProxyHandler, {'absolute_url': True, 'host_whitelist': host_whitelist,
575+
'ssl_options': ssl_options}),
566576
(url_path_join(web_app.settings['base_url'], r'/proxy/(\d+)(.*)'),
567-
LocalProxyHandler, {'absolute_url': False}),
577+
LocalProxyHandler, {'absolute_url': False,
578+
'ssl_options': ssl_options}),
568579
(url_path_join(web_app.settings['base_url'], r'/proxy/absolute/(\d+)(.*)'),
569-
LocalProxyHandler, {'absolute_url': True}),
580+
LocalProxyHandler, {'absolute_url': True,
581+
'ssl_options': ssl_options}),
570582
])
571583

572584
# vim: set et ts=4 sw=4:

0 commit comments

Comments
 (0)