Isolating Python and Jupyter using firejail
Motivation for this is already documented in e.g. python security post. Here the aim is to be able run isolated Python processes on a fully ad-hoc basis without need to be thinking about long-lived containers. This approach is based more on filesystem hierarchy and integrates better with traditional unix tools.
Aims
- Python processes can only read and write data within a project filesystem tree
- Python can see the internet only during installation of packages, but during this time it can not see the rest of project
- During running the project Python can not communicate with internet, and can not change any information which it sees during stage (2)
- Can use Jupyter and friends as normal
Implementation
The project directory has to exist before start:
export PROJ=$HOME/j/t3
mkdir -p $PROJ
I am using pyenv to install arbitrary python versions, so give access to it. In first step of creating the virtual env there is no need for network:
firejail --whitelist=$HOME/.pyenv/ --read-only=$HOME/.pyenv/ --whitelist=$PROJ --net=none python3 -m venv $PROJ/.venv
To install packages enable network, but disable access to rest of project, only allow the .venv:
firejail --whitelist=$HOME/.pyenv/ --read-only=$HOME/.pyenv/ --whitelist=$PROJ/.venv $PROJ/.venv/bin/pip3 install --upgrade pybind11 setuptools jupyter
To run Jupyter disable networking but launch Jupyter listening on a Unix socket. Mount the “.venv” read-only so that any subsequent package installs with network can not leak info:
firejail --whitelist=$HOME/.pyenv/ --read-only=$HOME/.pyenv/ --whitelist=$PROJ --read-only=$PROJ/.venv --net=none $PROJ/.venv/bin/jupyter notebook --sock $PROJ/jupyter.sock --no-browser
The Unix socket can be turned into TCP using the ssh
instructions
printed on Jupyter startup, e.g.: ssh -L 8888:$PROJ/jupyter.sock -N
$USER@localhost
will listen locally on 8888 and connect the incoming
TCP connections to the socket.
If ssh server is not running, it should be possible to instead use
socat
, something like socat TCP-LISTEN:8888,reuseaddr,fork
UNIX-CONNECT:$PROJ/jupyter.sock
should work but I have not tried.
Tunnelling to a web server in the jail
If using web-serving app from Python, e.g., the WebAgg backend for matplotlib, easiest is to relay from a filesystem unix socket to the tcp port. E.g. directly from Python can do:
subprocess.Popen("socat UNIX-LISTEN:PROJ/webagg.sock,reuseaddr,fork TCP-CONNECT:localhost:8988".split())
where PROJ should be the project root. Then turn can access back via
TCP port via ssh -L 8888:$PROJ/webagg.sock -N $USER@localhost
.