Work in progress: configuration for running a Home Assistant in containers with systemd and podman on Fedora IoT

I think I will be going down the ESP32 path soon, once I have conceded defeat to running bluetooth directly :slight_smile:

1 Like

Some progress. Temporarily disabling SELinux magically makes everything work (via setenforce 0) in that my sensors can easily be found and update nice and regularly. However, friends don’t let friends disable SELinux so I won’t be keeping it that way. I have found the following denials in my logs on the container host (Fedora 37):

Apr 04 22:59:10 opti.lan audit[831]: USER_AVC pid=831 uid=81 auid=4294967295 ses=4294967295 subj=system_u:system_r:system_dbusd_t:s0-s0:c0.c1023 msg='avc:  denied  { send_msg } for  scontext=system_u:system_r:bluetooth_t:s0 tcontext=unconfined_u:system_r:spc_t:s0 tclass=dbus permissive=0 exe="/usr/bin/dbus-broker" sauid=81 hostname=? addr=? terminal=?'

However I can’t map them to an actual policy violation to understand what the denial actually is. I feel like this might be in your wheelhouse @mattdm as I am hitting the limit of my SELinux troubleshooting knowledge.

… and now more progress!

I was able to get the sensors going with SELinux set to enforcing via a custom SELinux module on the host with the commands below.

Please note that the first command is a calculated guess based on the assumption that the last line in the output of ausearch would match what I previously pasted above.

ausearch --raw | tail -1 | audit2allow -M my-bluetooth-dbus-policy
semodule -X 300 -i my-bluetooth-dbus-policy.pp 

I’ll share more once I understand this a little better!

1 Like

I don’t have a lot of time :frowning: to look, but offhand it seems like SELinux is preventing the DBus access probably exactly to protect against the possible container break-out through DBus escalation. I’m not sure of a better way to do this, though.

Apparently the last line should be

sudo semodule -i  container-usbtty.pp

So .pp instead of .mod

https://twitter.com/Trefex/status/1643321968203055104

Thank you @mattdm and @DezeStijn for taking the time to reply! I will check out the info you have shared and see if I can consolidate it all into a working config for Bluetooth in Home Assistant running in a container using podman on Fedora! Then I can share my learnings here.

1 Like

Ooops — yes, the .pp file is the output of semodule_package. Fixed in the first post above.

2 Likes

Hi @mattdm, thanks for the great post, you saved me a lot of time.
I’m deploying Home Assistant with zigbee coordinator on Fedora IoT linux and I wanted to run all containers via systemd on behalf of non-privileged users, and this post contains the solution for exactly my task.
My setup has some slight differences from yours. My podman version is 4.3.1, my zigbee coordinator is Conbee II. My ISP gives me only IPv6 global address.
I ran the containers, following you step by step, adapting the solution to my coordinator and network. In this process, there were several points on which I had to linger and figure it out. I will summarize these points here.

  1. An advanced.network_key property must be set in /srv/hass/zigbee/configuration.yaml. People simply repeating the solution are at risk of an over-the-air attack on their wireless network with the default symetric key.
    Yet there is your TODO:

but it’s likely that from this comment not everyone will understand that when making the same solution, they should initially set the key before pairing devices.

  1. Instead of hardcoding timezone like

in each container-*.serivce just add another mount to container, and it will inherit tz configuration from host operating system:

--volume=/etc/localtime:/etc/localtime:ro \

The advantage of this approach is that it allows you to use the system-wide timezone configuration in one place, rather than hardcoding in each unit file. Are there any reasonable advantages to specifying a tz in each file separately that I’m missing?
It looks like your ~/.config/systemd/user/container-certbot.service file missing TZ parameter in Environment line and probably it run in utc.

  1. In the middle line of a firewall config you wrote:

but it should be

sudo firewall-cmd --add-forward-port=port=443:proto=tcp:toport=8443

  1. When running homemassistant container configured as described in the first post, a warning appears in the logs:

[homeassistant.components.dhcp] Cannot watch for dhcp packets: [Errno 1] Operation not permitted

The solution on how to avoid dhcp warning is given in post #54.

  1. An IP address 10.0.2.2 was kind of a magic number for me, and everywhere I replaced it with my own IP. But later in the thread I found your explanation about it in post #65, and I started to replace it back :slight_smile:

  2. The original post describing the container configuration process is missing one small but necessary detail: trusted proxy configuration of homeassistant. The solution is given in post #63.
    In my case I had to configure trusted proxy ip as 10.0.2.100, since that’s is the host address inside my nginx container. I researched this question a bit to figure out why I have this particular address, and not the one you have (10.0.2.2). The following command:

podman unshare --rootless-netns ip addr show

gives me this output:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
    link/ether 66:97:64:4c:9a:58 brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
       valid_lft forever preferred_lft forever

It turns out that the address 10.0.2.2 is a gateway. But the source address for nginx–>hass request is 10.0.2.100. What do we have here is intranet communication from nginx and homeassistant perspective. Nginx performs intranet communication from 10.0.2.100' to 10.0.2.2’, and homeassistent in turn gets request from ‘10.0.2.100’. I unfortunately could not find a reason why the source address could change to 10.0.2.2. Maybe this is a consequence of the difference in versions of slirp4netns between my and your setup.

  1. Under certbot section you wrote:

It turnes out here is the command:

/usr/bin/podman run \
  --cgroups=no-conmon \
  --rm \
  --sdnotify=conmon \
  -a stdout -a stderr \
  --replace \
  --name certbot \
  --volume=/etc/localtime:/etc/localtime:ro \
  --volume=/srv/hass/certbot:/etc/letsencrypt:z \
  --volume=/srv/hass/certbot/logs:/var/log/letsencrypt:z \
  --secret=certbot-creds,mode=0400 \
  docker.io/certbot/dns-digitalocean:latest -n certonly --dns-digitalocean --dns-digitalocean-credentials /run/secrets/certbot-creds --dns-digitalocean-propagation-seconds 3660 -d 'home.example.org' --agree-tos --register-unsafely-without-email`

One who wants a whildcard domain will also specify additional option:

-d '*.home.example.org'

Whoever prefers rsa over default ecdsa key can specify the following additional parameters

--key-type rsa --rsa-key-size 4096

  1. A fully qualified container url need to be specified in container-certbot.service, otherwice, when running the container it will be an error:

Error: short name: auto updates require fully-qualified image reference: "certbot/dns-digitalocean"

In order to fix that in the last line of podman command put fully-qualified name: docker.io/certbot/dns-digitalocean:latest instead of certbot/dns-digitalocean

  1. After running certbot command once, /srv/hass/certbot/renewal/home.example.org.conf file will created. It contains all necessary configuration to renew certificate. So unit command can (have to) be simplified. Note, there is no any parameters after -n renew:
    ~/.config/systemd/user/container-certbot.service
....
                          --secret=certbot-creds,mode=0400 \
                          docker.io/certbot/dns-digitalocean:latest -n renew
ExecStop=/usr/bin/podman stop --ignore --cidfile=%t/%n.ctr-id
....
  1. There is a line in certbot service configuration file ~/.config/systemd/user/container-certbot.service that always leads to an error:

leads to:

podman[]: Error: certbot-creds: secret name in use

This can be solved by adding the following line in the post steps actions:

ExecStopPost=-/usr/bin/podman secret rm certbot-creds

  1. Maybe it will come in handy for someone. For my zigbee coordinator - Conbee II, the selinux policy is slightly different. When I started zigbee2mqtt container it failed with EPERM, and in dmesg log appeared an entry:

audit: type=1400 audit(1672258142.933:1748): avc: denied { open } for pid=22375 comm="node" path="/dev/zigbee" dev="devtmpfs" ino=726 scontext=system_u:system_r:container_t:s0:c584,c902 tcontext=system_u:object_r:tty_device_t:s0 tclass=chr_file permissive=0

So I edited the policy to be:

module container-usbtty 1.0;

require {
	type container_t;
	type tty_device_t;
	class chr_file { getattr ioctl lock open read write };
}

#============= container_t ==============
allow container_t tty_device_t:chr_file { getattr ioctl lock open read write };

and it started to work.

  1. To enable port forwarding for IPv6 to use the non-root nginx container, I had to run the following commands as a root:

firewall-cmd --add-rich-rule='rule family=ipv6 forward-port to-port=8080 protocol=tcp port=80'
firewall-cmd --add-rich-rule='rule family=ipv6 forward-port to-port=8443 protocol=tcp port=443'

before calling

firewall-cmd --runtime-to-permanent

Once again, thank you very much Matt. A month ago when I was researching how to combine ostree-based linux, systemd, rootless podman, homemassistant and zigbee2mqtt together I was very excited to discover this thread.
1 Like

Doesn’t Z2M generate a random network_key if this is left empty when you start it for the first time?

The z2m security guide says that by default it uses a known default encryption key. To generate a random key, you need to specify the value GENERATATE in the advanced.network_key parameter on the first run. After the first launch, the value of this parameter in the configuration will be replaced with a random key. Note that changing the network key requires re-pairing of all devices.

Ah yes, forgot about GENERATE even though I did have it in my config

# ...
advanced:
  # Zigbee network - auto-generate new keys
  pan_id: GENERATE
  network_key: GENERATE
  # Zigbee network - set channel to avoid interference with 2.4GHz WiFi
  channel: 24

There’s a better way to do the timezone:

podman run --tz=local <whatever>

2 Likes

I’ve noticed some containers don’t react well to podman auto-updates.
I assume they either don’t get the proper signals or there’s an issue in the order the updates occur.

After my most recent update (last Saturday night) my AppDaemon and HassConf containers didn’t come up properly causing my NSPanel to be off and me losing the ability to change my HA configs from Home Assistant’s web dashboard.

Any ideas what might be going on?

Hmmm… not really. Anything in the logs?

Just a small note for anyone running Podman version >4.4? I believe: the new pasta network mode for rootless containers is a fair amount faster than the older slirp4netns with your reverse proxy. Using it with Traefik in front of my services and is a nice evolution.

1 Like

I was struggling getting my Sonof Zigbee ZBDongle-E working in a the home assistant container in rootless podman. I added the device by serial id and kept the groups from the user for the container, after adding the user to the dialout group, and I could see the device in the container, but would still get generic errors from HA when trying to setup Zigbee. I tried a bunch of things that I saw on this and many other threads, like disabling selinux and running the container as privileged and still the same issue from HA. The thing that worked with minimal permissions was setting the mode to 666 instead of 660. I couldn’t find which udev rule was causing 660 because it looked like it should’ve been 666 as a tty and usb. Either way, I created my own udev rule for this specific vendor id and product id in /etc/udev/rules.d/90-sonof-zigbee.rule :

ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="55d4", MODE="0666"

I got those values from lsusb :

Bus 001 Device 031: ID 1a86:55d4 QinHeng Electronics [unknown]

or from udevadm info /dev/ttyACM0 looking for ID_VENDOR_ID and ID_MODEL_ID.

I’ve got Bluetooth over dbus working with the following SELinux policy, haven’t seen anyone post a full policy for this, so just in case it helps anyone:

module container-dbus 1.0;

require {
  type bluetooth_t;
  type container_t;
  type system_dbusd_t;
  class dbus send_msg;
  class unix_stream_socket connectto;
}

#============= bluetooth_t ==============
allow bluetooth_t container_t:dbus send_msg;

#============= container_t ==============
allow container_t bluetooth_t:dbus send_msg;
allow container_t system_dbusd_t:dbus send_msg;
allow container_t system_dbusd_t:unix_stream_socket connectto;

Install the same way as the tty file, just switch out the names.

Mounting dbus into the container may be worse than avoiding it, but definitely much better than using --privileged or --security-opt label=disabled.

Fedora also seems to suggest migrating to podman-systemd which automatically generates the .service files from a more specific DSL to allow better support from the OS (podman auto-update, etc). My file for this looks like this:

# /etc/containers/systemd/homeassistant.container or ~/.config/containers/systemd/homeassistant.container
[Unit]
Description=HomeAssistant

[Container]
# dhcp sniffing
AddCapability=CAP_NET_RAW
AutoUpdate=registry
HostName=homeassistant.local
Image=ghcr.io/home-assistant/home-assistant:stable
Network=host
Timezone=local
Volume=/mnt/media/homeassistant:/config:Z
# bluetooth mounting
Volume=/var/run/dbus/:/var/run/dbus/:ro

[Install]
WantedBy=default.target

This format still allows the [Service] section as well for the ExecStartPre commands in the first post.

Nice! Migrating to this was on my list for possible holiday break projects, but I didn’t get to it…

Thanks for this topic!

I use Raspbian so no selinux, so Bluetooth works just fine by maping /run/dbus and pass --userns=keep-id to podman run.

If it can help, to be able to use DHCP discovery, --cap-add=CAP_NET_RAW,CAP_NET_BIND_SERVICE has to be passed in the podman run arguments.

In fact the only remaining point for me is the unclean shutdown which logs database warning when Home Assistant starts.
If someone found a solution, all could be perfect in my case. I don’t ses what could be different between docker stop and podman stop…

Hi! Sorry to interrupt in the midle of the thread… I’ve been trying to get my ZwaveJS2MQTT container to work on a rootless podman container and I get a really weird behaviour. When I start the container, I can see the permisions and access the dev like this:
podman exec zwavejs /bin/sh -c ‘stty -a -F /dev/zwave’
speed 115200 baud;stty: /dev/zwave: Not a tty
line = 0;

But after some 20 seconds the permissions get dropped and the same command gives me a ‘stty: can’t open ‘/dev/zwave’: Permission denied’

Checking the permission right after start of the container I get:
podman exec zwavejs /bin/sh -c ‘stat /dev/zwave’
File: /dev/zwave
Size: 0 Blocks: 0 IO Block: 4096 character special file
Device: 5h/5d Inode: 1319 Links: 1 Device type: a6,0
Access: (0660/crw-rw----) Uid: (65534/ nobody) Gid: (65534/ nobody)
Access: 2024-04-11 10:40:16.843642310 +0200
Modify: 2024-04-11 10:40:16.843642310 +0200
Change: 2024-04-11 10:39:43.843642310 +0200

But after some 20 secs it changes on itself to:
File: /dev/zwave
Size: 0 Blocks: 0 IO Block: 4096 character special file
Device: 5h/5d Inode: 1343 Links: 0 Device type: a6,0
Access: (0000/c---------) Uid: (65534/ nobody) Gid: (65534/ nobody)
Access: 2024-04-11 10:47:01.290191907 +0200
Modify: 2024-04-11 10:47:01.290191907 +0200
Change: 2024-04-11 10:47:04.845254517 +0200

I’m completely baffled by this. I assume that something inside the container is changing the permission for some reason? SELinux inside the container? Any ideas on how to mak it work?

Of course the container as root works flawlessly
Regards,
/Nacho

Hmm — no idea. It’s still working for me with thr above config.

It’s not SELinux inside the container, though, because that isn’t possible (it’s not namespaced). Also, that wouldn’t show a delay — it’d just fail.

Do you see any errors logged on the host system?