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.