Notes on using namespaces with unshare

An obvious choice to isolate processes is to use containers or a VM (docker, lxc, qemu). As an alternative, here a few notes on how to use "unshare" to separate most namespaces and run an application in a "unshared chroot". The process user is still root, running as non-root needs more tinkering and learning.

Motivation: I needed to run a NodeJS application "zigbee2mqtt" and wanted to limit its LAN connectivity and limit its access to the filesystem.

Hardware is a RPI3 + ZigbeeDongle attached via USB.

#install packages
opkg install debootstrap unshare kmod-usb-serial-cp210x ip-full kmod-veth mosquitto-client-ssl screen

#create userland from "outside"
debootstrap --arch arm64 bullseye /root/mychroot

#enter the userland
mkdir /var/run/netns
touch /var/run/netns/myChroot
unshare --mount --uts --ipc --pid --kill-child --net=/var/run/netns/myChroot --fork chroot /root/mychroot/ bash

#this is now  "inside" the userland
mount -t proc proc /proc
mount -t sysfs sys /sys

apt install -y curl htop build-essential git vim mosquitto

# /!\ these install tips are from nodejs developers
# /!\ please note that this requires a lot of trust to NPM and NodeJS
# seeing such practices convinced me it is worth isolating the process as much as possible
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt-get install -y nodejs

cd
git clone --depth 1 https://github.com/Koenkk/zigbee2mqtt.git
mv zigbee2mqtt /opt/zigbee2mqtt
cd /opt/zigbee2mqtt
mosquitto -d -p 1883
npm --proxy $http_proxy ci
npm start

To autostart the application put screen -d -S zig -m sh "/root/run.sh" in /etc/rc.local and create two files:

/root/run.sh

#!/bin/sh

function cleanup {
	echo "Outside: cleaning up 🧹"
	ip netns exec myChroot ip link set vethInside down
	ip netns exec myChroot ip link set lo down
	ip netns exec myChroot ip link delete vethInside
	#ip netns exec myChroot ip route add 0.0.0.0/0 via 10.2.2.1 dev vethInside

	ip netns del myChroot

	ip link set vethOutside down
	ip link delete vethOutside
	rm /var/run/netns/myChroot
	rmdir /var/run/netns
	killall mosquitto_pub
	killall mosquitto_sub
	killall socat
}
trap cleanup EXIT SIGINT
cleanup

function MQTTRCV {
	while true; do
		sleep 1
		mosquitto_sub -t "garden/Gemüsebeet/set" -p 8883 --cafile /etc/config/myCA.crt -h server.lan -u "username" -P "passwort" | mosquitto_pub -t 'zigbee2mqtt/Gemüsebeet/set' -l -h 10.2.2.2 -p 1883
	done
}

function MQTTTRX {
	while true; do
		sleep 1
		mosquitto_sub -t 'zigbee2mqtt/Gemüsebeet' -p 1883 -h 10.2.2.2 | mosquitto_pub -l -t "garden/Gemüsebeet" -p 8883 --cafile /etc/config/myCA.crt -h kellerap.lan -u "username" -P "passwort"
	done
}

socat TCP-LISTEN:8080,fork,reuseaddr,range=192.168.1.0/24 TCP:10.2.2.2:8080 &
MQTTRCV &
MQTTTRX &

mkdir /var/run/netns
touch /var/run/netns/myChroot
unshare --mount --uts --ipc --pid --kill-child --net=/var/run/netns/myChroot --fork chroot /root/mychroot/ bash /root/run.sh &
#unshare --mount --uts --ipc --pid --kill-child --net=/var/run/netns/myChroot --fork chroot /root/mychroot/ bash
sleep 5

ip link add vethOutside type veth peer name vethInside netns myChroot
ip addr add 10.2.2.1/30 dev vethOutside
ip link set vethOutside up

ip netns exec myChroot ip addr add 10.2.2.2/30 dev vethInside
ip netns exec myChroot ip link set vethInside up
ip netns exec myChroot ip link set lo up

wait

and the second file is inside the userland /root/mychroot/root/

#!/bin/sh

function cleanup {
	echo "Inside: cleaning up 🧹"
	umount /proc
	umount /sys
}
trap cleanup EXIT SIGINT

mount -t proc proc /proc
mount -t sysfs sys /sys

echo "===================="
mount
echo "===================="
df -h
echo "===================="
ps ax
echo "===================="

mknod /dev/ttyUSB0 c 188 0

mosquitto -d -c /etc/mosquitto/conf.d/mosquitto.conf

cd /opt/zigbee2mqtt
npm start