trustedci.org
Trusted CI:
The NSF Cybersecurity Center of Excellence
Jupyter Security
Rick Wagner
rick@globus.org
Matthias Bussonnier� bussonniermatthias@gmail.com
Ishan Abhinit
iabhinit@iu.edu
Mark Krenz
mkrenz@iu.edu
Introduction: Agenda, etc.
Trusted CI:
The NSF Cybersecurity Center of Excellence
Our mission: to provide the NSF community a coherent understanding of cybersecurity’s role in producing trustworthy science and the information and know-how required to achieve and maintain effective cybersecurity programs.
What is Cyberinfrastructure (CI)?
“The comprehensive infrastructure needed to capitalize on dramatic advances in information technology has been termed cyberinfrastructure (CI). Cyberinfrastructure integrates hardware for computing, data and networks, digitally-enabled sensors, observatories and experimental facilities, and an interoperable suite of software and middleware services and tools. “
-NSF Cyberinfrastructure Vision for 21st Century Discovery
Image credit: NSF
Today’s Agenda
30 minutes:
30 minutes:
90 minutes, with a 30 minute break at 4:00 p.m:
60 minutes:
Taking questions
Introduction to Jupyter
&
Jupyter Security
Where to ask for help & contribute
Overview of Jupyter architecture & nomenclature
Jupyter Notebook
Running a Notebook Server
Jupyter Architecture
Browser
1 Server / User
Python Kernel
R Kernel
C++ Kernel
CHP/HUB
~ login shell
~ sshd
~ HPC job
workers ?
ZMQ, Signed – Not Encrypted
REST
& websocket
encrypted
Server+pyplugins, serves webapp + js-plugins
Server: high level API (File handling, shell)
Kernels: Computation (and local to them File IO)
Name / Nomenclature
Note: Many of these are defaults there are a number of alternatives implementations.
Threat Model vs Functionality
Avoid: Arbitrary Code Execution
Allow: Arbitrary Code Execution
Venn Diagram
Threat Model
Threat Model Example of Attacks
Browser
1 Server / User
Python Kernel
R Kernel
C++ Kernel
.ipynb
.js
.py
Jupyter core security model
From https://jupyter-notebook.readthedocs.io/en/latest/security.html
Our security model
Trusted JS/HTML
HTML which is or has been explicitly requested by the user
Example:
The rest of the security is pluggable
Example:
Securing an Installation
STEP -1
Securing an Installation
STEP 0
“Secure Config Jupyter == Secure Config Bash”
“Secure Config JupyterHub == Secure Config sshd”
Securing an Installation
STEP 1
Securing an Installation
STEP 2
Cmnd_Alias JUPYTER_CMD = /home/jupyterhub/miniconda3/bin/sudospawner
jupyterhub ALL=(%jupyter) NOPASSWD:JUPYTER_CMD
Securing an Installation
STEP 2
Securing an Installation
STEP 3
Securing an Installation
STEP 3
Securing an Installation
STEP 3
Securing an Installation
STEP 3
Not worse than SSHD at that point
At that point we are about as secure as we can be than SSH.
Admin users still sensitive targets, but Oauth/LDAP passwd != System password (hopefully).
Admin could still impersonate and access some services other can’t.
Securing an Installation
STEP 4
STEP N+
At that point Hub is more restrictive than user install + ssh tunnel
Risk awareness
Hands-On Exercises
Jupyter Security Boundaries
Notebook Server
Local
Remote
Local
Remote
Local
Remote
Jupyter Security Boundaries
JupyterHub
Local
Remote
Hands on Exercises
Where to run�$ commands
Hosts:
User accounts:
Local
Remote
Driving Security Motivation: Calling APIs
https://jupyter.demo.globus.org/
Single User Notebook Server
trusted-ci<N>.globus-training.net
Browser
1 Server / User
SSH:22
HTTP:8889
GET /tree
tcpflow
Access Remote Notebook Server
Start a single user Jupyter notebook server
local:~$ ssh campusadmin@trusted-ci<N>.globus-training.net
SSH to the server
campusadmin@trusted-ci<N>:~$ jupyter notebook --no-browser --port=8889
Run tcpflow
“tcpflow is a program that captures data transmitted as part of TCP connections (flows), and stores the data in a way that is convenient for protocol analysis and debugging.”
It’s like tcpdump with readable text output. --Rick
Basic command
campusadmin@trusted-ci<N>:~$ sudo tcpflow -p -c -i lo port <port>
I don’t recommend leaving this running
campusadmin@trusted-ci<N>:~$ sudo tcpflow -p -c -i lo port 8889 | grep ‘password=’
Let’s run this on the HTTP port
https://github.com/simsong/tcpflow
Access Remote Notebook Server
local:~$ ssh -N -f -L localhost:8888:localhost:8889 \
campusadmin@trusted-ci<n>.globus-training.net
Port forward from your laptop the server
Change 8889 to 8888 & put that line in a browser
Look for this line in your Jupyter server output:
http://localhost:8889/?token=a0fac14de491e7eb80ba8ab0e6e1ee16ad6ef4532d1316ca
tcpflow HTTP Capture
campusadmin@trusted-ci1:~$ sudo tcpflow -p -c -i lo port 8889
tcpflow: listening on lo
127.000.000.001.43882-127.000.000.001.08889: GET /?token=1297a2b4dc6e3d7ec8a95f6443acff930f1f1920f3172ac8 HTTP/1.1
Host: localhost:8888
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Upgrade-Insecure-Requests: 1
Cookie: username-localhost-8888="2|1:0|10:1571099165|23:username-localhost-8888|44:MzlkZTk1NjNhYjBmNDU2MWEyYmVmOWE5MjhlNzlmOTE=|8a9b04bf9af42ef7d84da55c9f3cc06ac1b91a330608ca2378a1743d7ed617cb"; _xsrf=2|25101594|37f123c20fef78ecf3cb7bb75bb7b5f1|1569445998
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.2 Safari/605.1.15
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive
127.000.000.001.08889-127.000.000.001.43882: HTTP/1.1 302 Found
Server: TornadoServer/6.0.3
Content-Type: text/html; charset=UTF-8
Date: Tue, 15 Oct 2019 05:04:56 GMT
Location: /tree?token=1297a2b4dc6e3d7ec8a95f6443acff930f1f1920f3172ac8
Content-Length: 0
Notebook Server <-> Kernel Traffic
trusted-ci<N>.globus-training.net
Browser
1 Server / User
SSH:22
ZMQ:<port>
tcpflow
Python Kernel
HTTP:8889
{“code”:
Start a Kernel & Notebook�Find its Ports
Connection information is in the notebook
Start a Kernel & Notebook�Find its Ports
On host, connect info also in file.
Keep file ACLs limited to user
If “you” get connect info and
key
“you” can connect and execute
code as user
campusadmin@trusted-ci1:~$ cd .local/share/jupyter/runtime/
campusadmin@trusted-ci1:~$ ls -1tr
notebook_cookie_secret
nbserver-8485.json
nbserver-8485-open.html
nbserver-9738.json
nbserver-9738-open.html
kernel-e6757c17-0699-4e15-8ac7-4596c7178844.json
campusadmin@trusted-ci1:~$ cat kernel-*
{
"shell_port": 33617,
"iopub_port": 56051,
"stdin_port": 52759,
"control_port": 55021,
"hb_port": 50873,
"ip": "127.0.0.1",
"key": "1a917676-c05a83835fc282317c41975d",
"transport": "tcp",
"signature_scheme": "hmac-sha256",
"kernel_name": ""
}
campusadmin@trusted-ci1:~$
Connection information is also in the user home directory
Listen to Kernel Input and Output
$ sudo tcpflow -p -c -i lo port 56051
tcpflow: listening on lo
127.000.000.001.56051-127.000.000.001.43522:�... .snip. ...�{"code":"import os\nprint('because no one ever stores secrets in the environment')\nfor k in ('USER', 'PWD', 'HOME'):\n print('{}: {}'.format(k, os.environ[k]))","execution_count":10}
... .snip. ...
{"name":"stdout","text":"because no one ever stores secrets in the environment\nUSER: campusadmin\nPWD: /home/campusadmin\nHOME: /home/campusadmin\n"}
JupyterHub Starting Point
trusted-ci<N>.globus-training.net
JupyterHub proxy
Browser
HTTPS:443
Apache
HTTP:8000
JupyterHub�hub
Notebook�Server
HTTP:<>
ZMQ:<>
Python�Kernel
<> = “random” ports
JupyterHub HPC/Cluster Model
10.0.0.0/16
c-N-M.local
jupyter.example.edu
JupyterHub proxy
Browser
Apache
JupyterHub�hub
Notebook�Server
Python�Kernel
SSH:22
compute�node
private
network
Setting Up JupyterHub
Going to set up JupyterHub available at�� https://trusted-ci<N>.globus-training.net/jhub/
These hosts have Let’s Encrypt certs enabled
Apache config /etc/apache2/sites-enabled/000-default-le-ssl.conf
Apache Rewrites & Proxies
RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /jhub/(.*) ws://127.0.0.1:8000/jhub/$1 [P,L]
RewriteRule /jhub/(.*) http://127.0.0.1:8000/jhub/$1 [P,L]
<Location "/jhub/">
# preserve Host header to avoid cross-origin problems
ProxyPreserveHost on
# proxy to JupyterHub
ProxyPass http://127.0.0.1:8000/jhub/
ProxyPassReverse http://127.0.0.1:8000/jhub/
</Location>
Add to /etc/apache2/sites-enabled/000-default-le-ssl.conf�Add to
Bounce Apache
sudo systemctl restart apache2.service
JupyterHub Config & Start
$ cd /etc/jupyterhub
$ sudo jupyterhub --generate-config -f \� /etc/jupyterhub/jupyterhub_config.py
$ sudo jupyterhub --ip 127.0.0.1 --port 8000 --base-url \
'/jhub/' --config=/etc/jupyterhub/jupyterhub_config.py
local:~$ ssh campusadmin@trusted-ci<N>.globus-training.net
SSH to the server
Create default JupyterHub config and start JupyterHub
Login As Researcher
Login As Researcher
JupyterHub Internal Traffic
trusted-ci<N>.globus-training.net
JupyterHub proxy
Browser
HTTPS:443
Apache
JupyterHub�hub
Notebook�Server
<> = “random” ports
tcpflow
GET /jhub/user
Find the Proxy Port
Listen...
[I 2019-10-15 01:49:24.982 JupyterHub proxy:261] Adding user researcher to proxy /jhub/user/researcher/ => http://127.0.0.1:41009
01:49:24.984 [ConfigProxy] info: Adding route /jhub/user/researcher -> http://127.0.0.1:41009
01:49:24.985 [ConfigProxy] info: Route added /jhub/user/researcher -> http://127.0.0.1:41009
campusadmin@trusted-ci2:~$ sudo tcpflow -p -c -i lo port 41009
tcpflow: listening on lo
127.000.000.001.50114-127.000.000.001.41009: GET /jhub/user/researcher/api/sessions?_=1571122288275 HTTP/1.1
x-forwarded-proto: http
x-forwarded-port: 80
JupyterHub Internal SSL
trusted-ci<N>.globus-training.net
JupyterHub proxy
Browser
HTTPS:443
Apache
JupyterHub�hub
Notebook�Server
<> = “random” ports
tcpflow
+A94cf23e99
Update Config and Create Certs
In /etc/jupyterhub/
$ sudo jupyterhub --ip 127.0.0.1 --port 8000 --base-url '/jhub/' \� --config=/etc/jupyterhub/jupyterhub_config.py --generate-certs
[I 2019-10-15 02:16:07.663 JupyterHub app:1363] Adding CA for hub-internal: IP:127.0.0.1;DNS:localhost;DNS:ec2-54-67-48-138.us-west-1.compute.amazonaws.com;DNS:trusted-c2.globus-training.net;DNS:trusted-ci2
[I 2019-10-15 02:16:07.889 JupyterHub app:1383] Generating signed pair for proxy-api: IP:127.0.0.1;DNS:localhost;DNS:trusted-c2.globus-training.net;DNS:trusted-ci2
[I 2019-10-15 02:16:07.978 JupyterHub app:1383] Generating signed pair for proxy-client: IP:127.0.0.1;DNS:localhost;DNS:trusted-c2.globus-training.net;DNS:trusted-ci2
[I 2019-10-15 02:16:08.143 JupyterHub app:2301] Certificates written to directory `/etc/jupyterhub/internal-ssl`
c.JupyterHub.trusted_alt_names = ['DNS:trusted-c<N>.globus-training.net', 'DNS:trusted-ci<N>']
c.JupyterHub.internal_certs_location = '/etc/jupyterhub/internal-ssl'
c.JupyterHub.internal_ssl = True
Add to /etc/jupyterhub/jupyterhub_config.py
Update Config and Create Certs
$ cd /etc/jupyterhub
$ sudo jupyterhub --ip 127.0.0.1 --port 8000 --base-url \
'/jhub/' --config=/etc/jupyterhub/jupyterhub_config.py
https://trusted-ci<N>.globus-training.net/jhub/
Browse to
Start JupyterHub
Me or JH Bug? User Cert Dir Ownership
$ sudo ls -al /home/researcher/
total 40
drwxr-xr-x 7 researcher researcher 4096 Oct 15 02:24 .
drwxr-xr-x 6 root root 4096 Oct 13 22:45 ..
-rw-r--r-- 1 researcher researcher 220 Oct 13 22:45 .bash_logout
-rw-r--r-- 1 researcher researcher 3771 Oct 13 22:45 .bashrc
drwx------ 2 researcher researcher 4096 Oct 15 01:49 .cache
drwx------ 3 root root 4096 Oct 15 02:27 .jupyterhub
drwx------ 3 researcher researcher 4096 Oct 15 01:49 .local
-rw-r--r-- 1 researcher researcher 807 Oct 13 22:45 .profile
$ sudo chown researcher:researcher /home/researcher/.jupyterhub
'certfile "%s" does not exist' % self.ssl_options["certfile"]
ValueError: certfile "/home/researcher/.jupyterhub/jupyterhub-certs/user-researcher.crt" does not exist
https://github.com/jupyterhub/jupyterhub/pull/2785
FIXED!
Find the Proxy Port
Listen...
[I 2019-10-15 02:31:20.224 JupyterHub proxy:261] Adding user researcher to proxy /jhub/user/researcher/ => https://127.0.0.1:36193
02:31:20.231 [ConfigProxy] info: Adding route /jhub/user/researcher -> https://127.0.0.1:36193
02:31:20.231 [ConfigProxy] info: Route added /jhub/user/researcher -> https://127.0.0.1:36193
$ sudo tcpflow -p -c -i lo port 36193
tcpflow: listening on lo
Success!
$ sudo tcpflow -p -c -i lo port 36193
tcpflow: listening on lo
127.000.000.001.47846-127.000.000.001.36193: ]!='3|DqyUn/+0,'g(k$
jih98762.*&=5#@?>32101-)%</i$"trusted-ci2.globus-training.net
3t
127.000.000.001.36193-127.000.000.001.47846: =9w*=b;g
0H1013073119Z010Uuser-researcher0"0
vWF%i<
EVK-z$FN}
-/qB/)~F5/|~S@|[HbUxRd`zj694cQUf'L|=Z}]#g/C3I|4FRL@<o*fTUj=P]_[O`UsHMgx4}4v\Ky?of%g]Ne?n0l0 U%0++0HUA0?localhosttrusted-ci2.globus-training.net:0%Ied`0V,;?.i^akNLp[?B.$rh$7IWc4Sus~=BOaMIAV74mdb=e>4W|Ai^^=8;q1KFztPiPOk>{r:[<^3;5b5e8M9k%
6@.o:g[wGa-3*#mLkYc#pecT>`qQ#E;x9rhq_Z*!.#l>x.O3),N[Z=BZi])7DFm$;9{`6%W2G3"$T8C-A=`#C!Go:
0H1013072429Z010Uproxy-client0"00.001.36193: #000
B#\ <=LWBX3?lyW=v}jT-C@[&SxUnFl+zk\zw|q[PFlkMk$Xvk=#Th=-s_?d{8w0w^}$k@ck0W?]h-"mT4.SX`+S#wQ9C
JupyterHub Internal SSL
trusted-ci<N>.globus-training.net
JupyterHub proxy
Browser
HTTPS:443
Apache
JupyterHub�hub
Notebook�Server
<> = “random” ports
tcpflow
{“code”:
ZMQ:<>
Python�Kernel
Start a Kernel & Notebook�Find its Ports
$ sudo ls -1tr /home/researcher/.local/share/jupyter/runtime/
nbserver-8485.json
nbserver-8485-open.html
nbserver-9738.json
nbserver-9738-open.html
kernel-e6757c17-0699-4e15-8ac7-4596c7178844.json
$ sudo cat \
/home/researcher/.local/share/jupyter/runtime/kernel-...
{
"shell_port": 45317,
"iopub_port": 54279,
"stdin_port": 47461,
"control_port": 58485,
"hb_port": 46187,
"ip": "127.0.0.1",
"key": "92b993fc-759de490904af0c952884cbd",
"transport": "tcp",
"signature_scheme": "hmac-sha256",
"kernel_name": ""
}
{“code”:
Change Kernel Communication to IPC
c.Spawner.args = ['--transport="ipc"']
Add to /etc/jupyterhub/jupyterhub_config.py
Start a Kernel & Notebook�Find its Ports
$ sudo ls -1tr /home/researcher/.local/share/jupyter/runtime/
nbserver-11237.json
nbserver-11237-open.html
kernel-0746a014-a85a-4959-8572-637bfcaef64d-ipc-4
kernel-0746a014-a85a-4959-8572-637bfcaef64d-ipc-3
kernel-0746a014-a85a-4959-8572-637bfcaef64d-ipc-2
kernel-0746a014-a85a-4959-8572-637bfcaef64d-ipc-1
kernel-0746a014-a85a-4959-8572-637bfcaef64d.json
kernel-0746a014-a85a-4959-8572-637bfcaef64d-ipc-5
$ sudo cat /home/researcher/.local/share/jupyter/runtime/kernel-...
{
"shell_port": 1,
"iopub_port": 2,
"stdin_port": 3,
"control_port": 4,
"hb_port": 5,
"ip": "/home/researcher/.local/share/jupyter/runtime/kernel-0746a014-a85a-4959-8572-637bfcaef64d-ipc",
"key": "1bc70c05-808f5bec9272a64f66967ac3",
"transport": "ipc",
"signature_scheme": "hmac-sha256",
"kernel_name": ""
}
{“code”:
{“code”:
JupyterHub Internal SSL
trusted-ci<N>.globus-training.net
JupyterHub proxy
Browser
HTTPS:443
Apache
JupyterHub�hub
Notebook�Server
ZMQ:<>
Python�Kernel
HTTPS:443
HTTPS:443
Homework: Apache <-> JupyterHub
trusted-ci<N>.globus-training.net
JupyterHub proxy
Browser
Apache
JupyterHub�hub
Notebook�Server
Python�Kernel
HTTP:8000
Apache config
JupyterHub HPC/Cluster Model
10.0.0.0/16
c-N-M.local
jupyter.example.edu
JupyterHub proxy
Browser
Apache
JupyterHub�hub
Notebook�Server
Python�Kernel
meh
JupyterHub-only server
JupyterHub
Authenticators
Default is PAM (system username & pass)
Outsource authentication to OAuth
JupyterHub Globus OAuthenticator
For this demo:
Create app
Make sure this matches your host
Copy this
Do this & copy
JupyterHub Globus OAuthenticator
sudo JUPYTERHUB_CRYPT_KEY=$(cat /etc/jupyterhub/crypt.key) jupyterhub \
--ip 127.0.0.1 --port 8000 --base-url '/jhub/' \
--config=/etc/jupyterhub/jupyterhub_config.py
New start command to pass key for user secrets
Add to /etc/jupyterhub/jupyterhub_config.py
from oauthenticator.globus import LocalGlobusOAuthenticator
c.JupyterHub.authenticator_class = LocalGlobusOAuthenticator
c.LocalGlobusOAuthenticator.enable_auth_state = True
c.LocalGlobusOAuthenticator.oauth_callback_url = \� 'https://trusted-ci<N>.globus-training.net/jhub/hub/oauth_callback'
c.LocalGlobusOAuthenticator.client_id = '<>'
c.LocalGlobusOAuthenticator.client_secret = '<>'
c.LocalGlobusOAuthenticator.create_system_users = True
We’re going to create a new system account for each user
Can also explicitly map users to system accounts
c.Authenticator.username_map = {
'service-name': 'localname'
}
JupyterHub database will get an encrypted area
https://trusted-ci<N>.globus-training.net/jhub/
Wrap Up:
Answers to Earlier Questions
Defining Jupyter Security Best Practices
What can be done to improve
security in the Jupyter Community?