| Both sides previous revision Previous revision Next revision | Previous revision |
| jailingguiapplications [2017/08/04 13:06] – VladimirKrstulja | jailingguiapplications [2021/04/29 11:37] (current) – Fix a typo DanielEbdrup |
|---|
| |
| This is a short tutorial on how to run GUI applications jailed. This is done primarily for Firefox but the same principle can be applied for any other application. | This is a short tutorial on how to run GUI applications jailed. This is done primarily for Firefox but the same principle can be applied for any other application. |
| | |
| | <callout type="info"> |
| | There are tutorials on the net that involve connecting to the jail using ssh with X forwarding, for example [[https://forums.freebsd.org/threads/53362/|this fine FreeBSD Forum post]]. That works too, but is not strictly necessary on the local system, and it is actually much, much slower than using the unix socket directly, even if %%OpenSSH%% from ports is used with the None cipher enabled. |
| | |
| | Alternatively to ssh, and if unix sockets are not wanted, X can be configured to listen on tcp. The config for that is depending on your DE/WM, but in case of ''%%i3wm%%'' launched with ''%%startx%%'', one needs ''%%startx -- -listen tcp%%'', and then the ''%%DISPLAY%%'' env. var (see below) must be changed to either the host IP (mind the firewall rules) or the local jail IP since ''%%X%%'' will listen on all interfaces, eg. ''%%DISPLAY=10.0.0.2:0.0%%''. |
| | </callout> |
| |
| We start by creating a basic jail that depends on your favorite jail abstraction tool. But to unhide all the abstracted details that those tools often do, we're going to do it all manually, and based on ZFS, so feel free to adapt the tasks for whatever jail management tool you're using. | We start by creating a basic jail that depends on your favorite jail abstraction tool. But to unhide all the abstracted details that those tools often do, we're going to do it all manually, and based on ZFS, so feel free to adapt the tasks for whatever jail management tool you're using. |
| To begin with, we'll dedicate ''%%zroot/jails%%'' as the base dataset for all jail deployments, and create a basejail in ''%%zroot/jails/basejail%%''. The basejail method allows us to simply ''%% zfs clone %%'' it into target jail dataset, which is the most efficient way to share the base jail among other jails. Updating them all then requires updating the basejail and re-creating jail roots. Here's how. | To begin with, we'll dedicate ''%%zroot/jails%%'' as the base dataset for all jail deployments, and create a basejail in ''%%zroot/jails/basejail%%''. The basejail method allows us to simply ''%% zfs clone %%'' it into target jail dataset, which is the most efficient way to share the base jail among other jails. Updating them all then requires updating the basejail and re-creating jail roots. Here's how. |
| |
| <code> | The commands given are all executed as root, on the host, unless explicitly stated differently. |
| # zfs create -o compress=lz4 -o atime=off zroot/jails | |
| # zfs create zroot/jails/basejail | <code bash> |
| # bsdinstall jail /zroot/jails/basejail | zfs create -o compress=lz4 -o atime=off zroot/jails |
| | zfs create zroot/jails/basejail |
| | bsdinstall jail /zroot/jails/basejail |
| </code> | </code> |
| |
| So we snapshot it and create our Firefox jail filesystem: | So we snapshot it and create our Firefox jail filesystem: |
| |
| <code> | <code bash> |
| # zfs snapshot zroot/jails/basejail@latest | zfs snapshot zroot/jails/basejail@latest |
| # zfs create zroot/jails/firefox | zfs create zroot/jails/firefox |
| # zfs clone zroot/jails/basejail@latest zroot/jails/firefox/root | zfs clone zroot/jails/basejail@latest zroot/jails/firefox/root |
| # zfs create zroot/jails/firefox/var | zfs create zroot/jails/firefox/var |
| # zfs create zroot/jails/firefox/tmp | zfs create zroot/jails/firefox/tmp |
| # zfs create zroot/jails/firefox/home | zfs create zroot/jails/firefox/home |
| # rsync -a /zroot/jails/firefox/root/var/ /zroot/jails/firefox/var/ | rsync -a /zroot/jails/firefox/root/var/ /zroot/jails/firefox/var/ |
| # zfs set mountpoint=/zroot/jails/firefox/root/var zroot/jails/firefox/var | zfs set mountpoint=/zroot/jails/firefox/root/var zroot/jails/firefox/var |
| # zfs set mountpoint=/zroot/jails/firefox/root/tmp zroot/jails/firefox/tmp | zfs set mountpoint=/zroot/jails/firefox/root/tmp zroot/jails/firefox/tmp |
| # zfs set mountpoint=/zroot/jails/firefox/root/usr/home zroot/jails/firefox/home | zfs set mountpoint=/zroot/jails/firefox/root/usr/home zroot/jails/firefox/home |
| </code> | </code> |
| |
| For extra security we want our jail to run with minimum require privilege, so we set some properties on these datasets, which should make obvious why we separated them like this. Of course, these rules are not applicable to every application, as some, unfortunately would like to write or execute to/from paths they shouldn't. For firefox, these suffice, tho'. | For extra security we want our jail to run with minimum require privilege, so we set some properties on these datasets, which should make obvious why we separated them like this. Of course, these rules are not applicable to every application, as some, unfortunately would like to write or execute to/from paths they shouldn't. For firefox, these suffice, tho'. |
| |
| <code> | <code bash> |
| # zfs set setuid=off exec=off zroot/jails/firefox/var | zfs set setuid=off exec=off zroot/jails/firefox/var |
| # zfs set setuid=off exec=off zroot/jails/firefox/tmp | zfs set setuid=off exec=off zroot/jails/firefox/tmp |
| # zfs set setuid=off exec=off zroot/jails/firefox/home | zfs set setuid=off exec=off zroot/jails/firefox/home |
| </code> | </code> |
| |
| Next, with the filesystem in place, we install the packages. ''%%xauth%%'' and ''%%firefox%%'' are the base minimum, while ''%%liberation-fonts-ttf%%'' is recommended addition for some nice fonts in Firefox. | Next, with the filesystem in place, we install the packages. ''%%xauth%%'' and ''%%firefox%%'' are the base minimum, while ''%%liberation-fonts-ttf%%'' is recommended addition for some nice fonts in Firefox. |
| |
| <code> | <code bash> |
| pkg -c /zroot/jails/firefox/root install firefox xauth liberation-fonts-ttf | pkg -c /zroot/jails/firefox/root install firefox xauth liberation-fonts-ttf |
| </code> | </code> |
| At this point, we comment out the ''%%exec.%%'' directives, and uncomment the ''%%persist%%'' directive because we want to get inside the jail with no processes running, to bootstrap it. But before we do that, there are two more undefined items here, the devfs ruleset and jail's ip address. So, let's handle those first. | At this point, we comment out the ''%%exec.%%'' directives, and uncomment the ''%%persist%%'' directive because we want to get inside the jail with no processes running, to bootstrap it. But before we do that, there are two more undefined items here, the devfs ruleset and jail's ip address. So, let's handle those first. |
| |
| The devfs ruleset is required to allow audio devices in the jail, so the applications can play audio. We copy the default ruleset for jails (ruleset number 4) from ''%%/etc/defaults/devfs.rules%%'' and add audio devices, into ''%%/etc/devfs.rules%%'': | The recommended way to set up the jails' networking is to clone ''%%lo0%%'' and give it a dedicated range, then use pf to NAT the traffic out. In short these three things: |
| | |
| | <code bash> |
| | # /etc/rc.conf |
| | |
| | # |
| | # Among other thigns you set up in rc.conf, the following is minimum required for jail networking. |
| | # |
| | # We use the 10.0.0.0/29 range just as an example for up to 6 jails |
| | # |
| | cloned_interfaces=lo1 |
| | ifconfig_lo1_aliases="10.0.0.1-6/29" |
| | |
| | # And this to enable pf rules for NAT |
| | pf_enable="YES" |
| | pf_rules="/etc/pf.conf" |
| | </code> |
| | |
| | We enable the network by running ''%% service netif cloneup %%'', and the defined ''%%lo1%%'' interface will be cloned with that IP range. |
| | |
| | Then the ''%%pf%%'' rules. These are the minimum required to get NAT going, and it doesn't do anything else, so adjust as needed, or if you already have pf in place, the ''%%nat%%'' line is all that's required. |
| | |
| | <code bash> |
| | # /etc/pf.conf |
| | |
| | # This is for re0 interface, so replace with whatever you have, like em0, igb0, ... |
| | extif = "re0" |
| | intif = "lo1" |
| | |
| | set skip on lo |
| | set state-policy if-bound |
| | |
| | nat on $extif inet from ($intif) to ! ($intif) -> ($extif) |
| | </code> |
| | |
| | And after that, ''%% service pf start %%'' should start and enable the firewall. If you're doing this over SSH, this article assumes you know what you're doing, missing the appropriate rules. |
| | |
| | Next, the devfs ruleset is required to allow audio devices in the jail, so the applications can play audio. We copy the default ruleset for jails (ruleset number 4) from ''%%/etc/defaults/devfs.rules%%'' and add audio devices, into ''%%/etc/devfs.rules%%'': |
| |
| <code> | <code> |
| | # /etc/devfs.rules |
| | |
| [devfsrules_desktop_jail=5] | [devfsrules_desktop_jail=5] |
| add include $devfsrules_hide_all | add include $devfsrules_hide_all |
| </code> | </code> |
| |
| Basically we just added ''%%mixer*%%'' and ''%%dsp*%%'' devices in addition to the default jails ruleset. | Basically we just added ''%%mixer*%%'' and ''%%dsp*%%'' devices in addition to the default jails ruleset. With that file in place, we just have to ''%% service devfs restart %%'' and the ruleset is in effect. |
| | |
| | So now we're ready to start the jail and jexec into it for final setup. |
| | |
| | <callout type="info"> |
| | **A note on DISPLAY env variable** |
| | |
| | The below approach uses a custom shell script that's started by the jail's ''%%exec.start%%'', in which the DISPLAY environment variable is set. This is not strictly needed or the best approach. It suffices to put the in-jail users into a login class, and define an ENV for that class in ''%%/etc/login.conf%%'': |
| | |
| | ''%%:setenv=DISPLAY=\c0:\%%'' |
| | |
| | Don't forget to ''%%cap_mkdb /etc/login.conf%%'' in the jail. \c is escape sequence for colon. |
| | |
| | </callout> |
| | |
| | <code bash> |
| | |
| | # Start the jail |
| | jail -c firefox |
| | |
| | # jexec into it (the commands listed here after this are done inside the jail) |
| | jexec -l firefox |
| | |
| | # First, create a user for firefox (note the exec.jail_user = "firefox" in the jail.conf, so that's the user) |
| | pw useradd firefox -w random -m |
| | |
| | # Write out the "init" script (note the exec.start path in jail.conf, so that's the init script) |
| | cat << EOF > /home/firefox/run-firefox |
| | #!/bin/sh |
| | |
| | export DISPLAY=:0.0 |
| | /usr/local/bin/firefox > /dev/null & |
| | EOF |
| | |
| | # We did all this as root, so: |
| | chown firefox:firefox /home/firefox/run-firefox |
| | chmod u+x /home/firefox/run-firefox |
| | |
| | # Prepare the mountpoint for host's X unix socket |
| | mkdir /tmp/.X11-unix |
| | chmod 777 /tmp/.X11-unix |
| | |
| | # Done! |
| | exit |
| | </code> |
| | |
| | And that's it. We stop the jail with ''%% jail -r firefox %%'', uncomment the ''%%exec.%%'' bits from ''%%jail.conf%%'', comment the ''%%persist%%'' bit, and the jail is almost ready to run. Finally: |
| | |
| | <code bash> |
| | # Allow jails to talk to xorg |
| | xhost + |
| | |
| | # Mount the host's X unix socket into the jail |
| | mount_nullfs /tmp/.X11-unix /zroot/jails/firefox/root/tmp/.X11-unix |
| | |
| | # And finally make the jail's root readonly: |
| | zfs set readonly=on zroot/jails/firefox/root |
| | </code> |
| | |
| | Done. We start firefox by starting the jail itself: |
| | |
| | <code bash> |
| | jail -c firefox |
| | </code> |
| | |
| | With the ''%%persist%%'' item commented out, the jail will shut down automatically when firefox is exited |
| | |
| | ===== A few gotchas for maintenance ===== |
| | |
| | - The root is mounted readonly, so any ''%%pkg -j firefox upgrade%%'' operations (and similar) will require remounting it with ''%%readonly=off%%'' first |
| | |
| | - To update/upgrade the base, do all that's required on the ''%%zroot/jails/basejail%%'', make a snapshot, ''%% zfs destroy zroot/jails/${name}/root %%'' (first umount ''%%/var%%'', ''%%/tmp%%'' and ''%%/home%%'' from it), and re-clone the base into a new ''%%root%%'' dataset, remount ''%%/var%%'', ''%%/tmp%%'' and ''%%/home%%'' with zfs from the host. |
| | |
| | * To update basejail: ''%% freebsd-update -b /zroot/jails/basejail fetch install %%'' |
| | * To upgrade basejail: ''%% freebsd-update -b /zroot/jails/basejail -r 11.2-RELEASE --currently-running 11.1-RELEASE upgrade %%'', though perhaps just untar the ''%%base.txz%%'' for each upgrade, thatis create a new basejail. |
| | * To unmount datasets under jail's ''%%root%%'': ''%% mount | grep ' on /zroot/jails/firefox/ | awk '{ print $3 }' | sort -r | xargs umount %%'' (replace ''%%firefox%%'' with whatever jail it is) |
| | * To re-mount datasets under jail's ''%%root%%'': ''%% zfs list -o name | grep -E "^zroot/jails/firefox/" | xargs -n 1 zfs mount %%'' (replace ''%%firefox%%'' with whatever jail it is) |
| | |
| | - The X unit socket will have to be re-mounted after reboot, ZFS datasets are mounted automatically. An ''%%exec.prestart%%'' could be added to the jail's config (''%%jail.conf%%''): |
| | |
| | <code> |
| | exec.prestart = "mount | grep ' on /zroot/jails/${name}/root/tmp/.X11-unix` || mount_nullfs /tmp/.X11-unix /zroot/jails/${name}/root/tmp/.X11-unix" |
| | </code> |
| | |
| | ===== Integrating host-side browser launchers with jailed Firefox ===== |
| | |
| | Various launchers will want to launch local ''%%/usr/local/bin/firefox%%'' when an URL is clicked or otherwise selected to be open in the default browser. To integrate this with jailed Firefox, one needs a wrapper script that will convert local Firefox calls to ''%%jexec%%'' calls. Three things are needed for this: |
| | |
| | - Local "firefox" binary that will be used as the wrapper |
| | - A sudo-enabled NOPASSWD script that's called by the local "firefox" wrapper, and that executes ''%%jexec%%'' in the jail |
| | - A ''%%sudoer%%'' rule allowing the script in #2 to be started with NOPASSWD |
| | |
| | As a simple example, assuming local "firefox" will be called with first param being the URL, eg. ''%%/usr/local/bin/firefox http://google.com%%'', we then set up (all on the host): |
| | |
| | <code bash> |
| | #!/bin/sh |
| | # This is /usr/local/bin/firefox |
| | |
| | if [ -z "$1" ]; then |
| | exit 1 |
| | fi |
| | sudo /home/user/bin/jail-firefox-exec $1 |
| | </code> |
| | |
| | <code bash> |
| | #!/bin/sh |
| | # This is /home/user/bin/jail-firefox-exec |
| | |
| | if [ -z "$1" ]; then |
| | exit 1 |
| | fi |
| | jexec -U firefox firefox firefox $1 |
| | </code> |
| | |
| | <code> |
| | # In sudoers (called by sudo visudo), for example: |
| | |
| | %wheel ALL=(ALL) NOPASSWD: /home/user/bin/jail-firefox-exec |
| | </code> |
| |