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

  1. Python processes can only read and write data within a project filesystem tree
  2. Python can see the internet only during installation of packages, but during this time it can not see the rest of project
  3. During running the project Python can not communicate with internet, and can not change any information which it sees during stage (2)
  4. 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.