Skip to content

Commit 6f84341

Browse files
committed
jupyter bind loopback, use token
1 parent 36071fd commit 6f84341

3 files changed

Lines changed: 87 additions & 6 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ exploration. Instead of running an entire script, one can run one code cell at
472472

473473
You can read more about Jupyter Notebooks here: https://jupyter.org/
474474

475-
1. Within the root project of your package folder, run `./jupyterlab.sh start`
475+
1. Within the root project of your package folder, run `./jupyterlab.sh start`. This prints an access token and opens an already-authenticated JupyterLab session in your browser. If the browser doesn't open automatically, copy the printed `http://localhost:8888/?token=...` URL into your browser.
476476
1. Double-click on "account.ipynb" file, which provides a starting point for a notebook
477477
1. Use shift+enter to execute each cell within the notebook. Add/edit/delete cells of code as needed for your data exploration.
478478
1. Don't forget to run `./jupyterlab.sh stop` to stop the docker container
@@ -563,4 +563,4 @@ If you're using OAuth Tokens authentication, the initial configure will retrieve
563563
## Other docs
564564

565565
- [Troubleshooting](./docs/troubleshooting.md)
566-
- [For Contributors](./FOR_CONTRIBUTORS.md)
566+
- [Contributing](./CONTRIBUTING.md)

‎src/datacustomcode/templates/script/jupyterlab.sh‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,26 +45,39 @@ check_docker() {
4545
echo "Docker daemon is running"
4646
}
4747

48+
# Function to check if openssl is installed
49+
check_openssl() {
50+
if ! command -v openssl &> /dev/null; then
51+
echo "openssl is not installed. It is required to generate a secure JupyterLab access token."
52+
exit 1
53+
fi
54+
}
55+
4856
# Function to start Jupyter server
4957
start_jupyter() {
5058
echo "Building the docker image"
5159
docker build -t datacloud-customcode .
5260

61+
local TOKEN
62+
TOKEN=$(openssl rand -hex 32)
63+
5364
echo "Running the docker container"
54-
docker run -d --rm -p 8888:8888 \
65+
docker run -d --rm -p 127.0.0.1:8888:8888 \
5566
-v $(pwd):/workspace \
5667
--name jupyter-server \
5768
datacloud-customcode jupyter lab \
5869
--ip=0.0.0.0 \
5970
--port=8888 \
6071
--no-browser \
6172
--allow-root \
62-
--NotebookApp.token='' \
63-
--NotebookApp.password='' \
73+
--NotebookApp.token="$TOKEN" \
6474
--notebook-dir=/workspace
6575

6676
sleep 3 # Wait for server to start
67-
open_browser "http://localhost:8888"
77+
local URL
78+
URL="http://localhost:8888/?token=$TOKEN"
79+
echo "Opening $URL"
80+
open_browser $URL
6881
}
6982

7083
# Function to stop Jupyter server
@@ -82,6 +95,7 @@ stop_jupyter() {
8295
case "$1" in
8396
"start")
8497
check_docker
98+
check_openssl
8599
start_jupyter
86100
;;
87101
"stop")

‎tests/test_jupyterlab_script.py‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from __future__ import annotations
2+
3+
import os
4+
import subprocess
5+
6+
from datacustomcode.template import script_template_dir
7+
8+
JUPYTERLAB_SH = os.path.join(script_template_dir, "jupyterlab.sh")
9+
10+
# These tests don't actually run the jupyter script. They simply verify
11+
# certain specific configurations of the script for things like syntax
12+
# and security correctness.
13+
#
14+
# These were added when fixing a bug that could have allowed for RCE
15+
# over the local network on the user's device due to previous insufficient
16+
# network config. While not perfect, they do offer a bit of assurance that
17+
# the script is configured correctly.
18+
19+
20+
class TestJupyterlabScript:
21+
def _read(self) -> str:
22+
with open(JUPYTERLAB_SH) as f:
23+
return f.read()
24+
25+
def test_jupyterlab_sh_syntax_is_valid(self):
26+
"""`bash -n` should accept the script without syntax errors."""
27+
result = subprocess.run(
28+
["bash", "-n", JUPYTERLAB_SH],
29+
capture_output=True,
30+
text=True,
31+
check=False,
32+
)
33+
assert result.returncode == 0, result.stderr
34+
35+
def test_start_jupyter_binds_loopback_host_port(self):
36+
content = self._read()
37+
assert "-p 127.0.0.1:8888:8888" in content
38+
assert "-p 8888:8888" not in content
39+
40+
def test_start_jupyter_binds_container_to_all_interfaces(self):
41+
content = self._read()
42+
assert "--ip=0.0.0.0" in content
43+
assert "--ip=127.0.0.1" not in content
44+
45+
def test_start_jupyter_generates_token_not_empty_auth(self):
46+
content = self._read()
47+
assert "--NotebookApp.token=''" not in content
48+
assert "--NotebookApp.password=''" not in content
49+
assert "openssl rand -hex 32" in content
50+
51+
def test_start_jupyter_uses_dynamic_token_variable(self):
52+
content = self._read()
53+
assert "local TOKEN" in content
54+
assert "TOKEN=$(openssl rand -hex 32)" in content
55+
assert '--NotebookApp.token="$TOKEN"' in content
56+
57+
def test_open_browser_url_includes_token_param(self):
58+
content = self._read()
59+
assert 'URL="http://localhost:8888/?token=$TOKEN"' in content
60+
assert "open_browser $URL" in content
61+
62+
def test_token_never_written_to_file(self):
63+
content = self._read()
64+
assert "credentials.ini" not in content
65+
for line in content.splitlines():
66+
if "TOKEN" in line:
67+
assert ">" not in line, f"Line writes TOKEN to a file: {line!r}"

0 commit comments

Comments
 (0)