Skip to content

Commit 1fa8751

Browse files
committed
Enable SSL on forwarded requests
1 parent 67f1da1 commit 1fa8751

File tree

3 files changed

+71
-11
lines changed

3 files changed

+71
-11
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: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def __init__(self, *args, **kwargs):
4444
self.proxy_base = ''
4545
self.absolute_url = kwargs.pop('absolute_url', False)
4646
self.host_whitelist = kwargs.pop('host_whitelist', ['localhost', '127.0.0.1'])
47+
self.ssl_options = kwargs.pop('ssl_options', None)
4748
super().__init__(*args, **kwargs)
4849

4950
# Support all the methods that tornado does by default except for GET which
@@ -155,7 +156,8 @@ def _build_proxy_request(self, host, port, proxied_path, body):
155156

156157
headers = self.proxy_request_headers()
157158

158-
client_uri = self.get_client_uri('http', host, port, proxied_path)
159+
protocol = 'http' if self.ssl_options is None else 'https'
160+
client_uri = self.get_client_uri(protocol, host, port, proxied_path)
159161
# Some applications check X-Forwarded-Context and X-ProxyContextPath
160162
# headers to see if and where they are being proxied from.
161163
if not self.absolute_url:
@@ -251,7 +253,8 @@ async def proxy_open(self, host, port, proxied_path=''):
251253
if not proxied_path.startswith('/'):
252254
proxied_path = '/' + proxied_path
253255

254-
client_uri = self.get_client_uri('ws', host, port, proxied_path)
256+
protocol = 'ws' if self.ssl_options is None else 'wss'
257+
client_uri = self.get_client_uri(protocol, host, port, proxied_path)
255258
headers = self.request.headers
256259
current_loop = ioloop.IOLoop.current()
257260
ws_connected = current_loop.asyncio_loop.create_future()
@@ -282,7 +285,8 @@ def ping_cb(data):
282285
async def start_websocket_connection():
283286
self.log.info('Trying to establish websocket connection to {}'.format(client_uri))
284287
self._record_activity()
285-
request = httpclient.HTTPRequest(url=client_uri, headers=headers)
288+
request = httpclient.HTTPRequest(url=client_uri, headers=headers,
289+
ssl_options=self.ssl_options)
286290
self.ws = await pingable_ws_connect(request=request,
287291
on_message_callback=message_cb, on_ping_callback=ping_cb)
288292
ws_connected.set_result(True)
@@ -304,7 +308,7 @@ def proxy_request_headers(self):
304308
def proxy_request_options(self):
305309
'''A dictionary of options to be used when constructing
306310
a tornado.httpclient.HTTPRequest instance for the proxy request.'''
307-
return dict(follow_redirects=False)
311+
return dict(follow_redirects=False, ssl_options=self.ssl_options)
308312

309313
def check_xsrf_cookie(self):
310314
'''
@@ -529,17 +533,21 @@ def options(self, path):
529533
return self.proxy(self.port, path)
530534

531535

532-
def setup_handlers(web_app, host_whitelist):
536+
def setup_handlers(web_app, host_whitelist, ssl_options):
533537
host_pattern = '.*$'
534538
web_app.add_handlers('.*', [
535539
(url_path_join(web_app.settings['base_url'], r'/proxy/(.*):(\d+)(.*)'),
536-
RemoteProxyHandler, {'absolute_url': False, 'host_whitelist': host_whitelist}),
540+
RemoteProxyHandler, {'absolute_url': False, 'host_whitelist': host_whitelist,
541+
'ssl_options': ssl_options}),
537542
(url_path_join(web_app.settings['base_url'], r'/proxy/absolute/(.*):(\d+)(.*)'),
538-
RemoteProxyHandler, {'absolute_url': True, 'host_whitelist': host_whitelist}),
543+
RemoteProxyHandler, {'absolute_url': True, 'host_whitelist': host_whitelist,
544+
'ssl_options': ssl_options}),
539545
(url_path_join(web_app.settings['base_url'], r'/proxy/(\d+)(.*)'),
540-
LocalProxyHandler, {'absolute_url': False}),
546+
LocalProxyHandler, {'absolute_url': False,
547+
'ssl_options': ssl_options}),
541548
(url_path_join(web_app.settings['base_url'], r'/proxy/absolute/(\d+)(.*)'),
542-
LocalProxyHandler, {'absolute_url': True}),
549+
LocalProxyHandler, {'absolute_url': True,
550+
'ssl_options': ssl_options}),
543551
])
544552

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

0 commit comments

Comments
 (0)