TL;DR
I've tinkered with a solution based on systemd user-services: https://codeberg.org/s1m/termenv. It works as expected but I wish there was a stable solution for this.
For long time, isolations on operating systems has been about isolations between users only. It was accepted that applications of a same user were able to access the same files, and interact with each other. They all have the same rights.
Mobile OS (Android, iOS) has introduced isolation of applications. Two applications can't be trusted the same way, and they don't need to access the same data. Applications are still allowed to interact with each other, only through dedicated interfaces. They have their own storage for their data and their config. If they need to access other directories, the user have to agree to. The applications may use the device settings but, except dedicated applications, they aren't allowed to edit the settings.
As for mobile devices, we need a way to isolate applications on desktop. On Linux, a well known solution is flatpak. Flatpaks are build with a manifest that defines the permissions of the application. They can mount some user directories if they need to read them, access devices, sockets, etc. Portals exist to grant permissions to some resources during runtime. Most graphical applications available on Linux are also distributed with flatpak.
CLI applications and services are usually isolated using containers, and systemd services.
Isolation for terminal environments
In addition to graphical applications, I wish to have different environments on my terminals, isolated from each other. On terminals, per environment isolation is relevant, as we usually use many different applications in a single session.
I need an environment as restricted as possible responsible to update my configs and my user packages. The config and the user packages must be read-only for other environments. And I need to be able to restrict access to directories of other environment if I want to.
It avoids the easy bashrc backdoor, having tens of new hidden directories created with different dev tools, messing with virtual environments and user packages, and so on.
To define my needs:
- In the environments, the host system must not be modifiable
- A single environment dedicated to update user packages must be allowed to do it, and everything else must be inaccessible
- A single environment dedicated to update my config, must be allowed to do it, and everything else must be inaccessible
- My configs must be readable to all other environments
- The different environments must be able to override some config/directories, for themselves - this should not include sensitive directories, like the exec directories, the auto-executed scripts, or where the environment is defined.
- In the environments it must not be possible to trivially escape the sandbox.
Many solutions to get different working environments exist:
- toolbx, based on podman, allows to run different system with access to the user directories. It doesn't support isolations of the user home and runtime directories out of the box.
- distribox is similar to toolbx, but I haven't looked into it
- systemd-homed areas, introduced in v258 but it only provides the ability to change the home directory to get different configs (yet?).
Nothing fit my needs out of the box, so I had to tinker with something. I was previously using something based on toolbx: I used to generate containers to change the home directory and mount read-only some directories.
My current solution
With my recent move to ParticleOS, an experimental image-based distribution with a deep integration of systemd, I've decided to edit my terminal environment scripts to use user managed systemd services.
There are 2 executable scripts: termlaunchmenu
that starts a menu to select an environment to run the 2nd scripts, te
that applies the configuration of the said environment, and starts my configured command in a transient service.
termlaunchmenu
is not very configurable at this moment, and may need to be edited. It's supposed to be started with a system keyboard shortcut, and it depends on wofi.
te
is much more configurable. The main config file is located at $HOME/.config/te/conf
and it defines the command executed by the transient unit, the different group of directories, and the default inaccessible, read-only and read-writable directories. To avoid the sandbox being trivially escaped, $XDG_RUNTIME_DIR/bus
is inaccessible to all the environments.
Then the different environment config are defined in $HOME/.config/te/sessions/env_name
. For example, the environment dedicated to update cargo packages is defined as follow:
$HOME/.config/te/sessions/cargo:
NO_PATHS+=( ${DEV_PATHS[@]} ${DOC_PATHS[@]} )
RW_PATHS+=( ${CARGO[@]} )
The source is available on Codeberg: https://codeberg.org/s1m/termenv.
Results
So far, the solution works well. A few things can be improved (like denying access to runtime directories by default, and write an allow-list).
I honestly would prefer to have a stable solution for this. It would be fantastic if systemd-homed areas could be improved to allow to:
- Restrict access to the system
- Restrict access to the root home (
/home/myuser/
) and to the other areas - Restrict access to the user and areas runtime directories
- Bind the content of the root home (inaccessible, RO, or RW)
- Define group of directories and configure how areas mount them (RO/RW) or not