Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'bluetooth' group and policy to home-assistant container #68

Closed
MrMarble opened this issue Sep 15, 2022 · 12 comments
Closed

Add 'bluetooth' group and policy to home-assistant container #68

MrMarble opened this issue Sep 15, 2022 · 12 comments

Comments

@MrMarble
Copy link

MrMarble commented Sep 15, 2022

Latest versions of home-assistant has support for Bluetooth using BlueZ and DBus.

BlueZ is already installed in the image but mounting dbus to the container shows a privilege error on home-assistant logs, by default DBus only allows root user to communicate with the socket.

This is the default config file running cat /usr/share/dbus-1/system.d/bluetooth.conf inside the home-assistant pod

<!-- This configuration file specifies the required security policies
     for Bluetooth core daemon to work. -->

<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>

  <!-- ../system.conf have denied everything, so we just punch some holes -->

  <policy user="root">
    <allow own="org.bluez"/>
    <allow send_destination="org.bluez"/>
    <allow send_interface="org.bluez.AdvertisementMonitor1"/>
    <allow send_interface="org.bluez.Agent1"/>
    <allow send_interface="org.bluez.MediaEndpoint1"/>
    <allow send_interface="org.bluez.MediaPlayer1"/>
    <allow send_interface="org.bluez.Profile1"/>
    <allow send_interface="org.bluez.GattCharacteristic1"/>
    <allow send_interface="org.bluez.GattDescriptor1"/>
    <allow send_interface="org.bluez.LEAdvertisement1"/>
    <allow send_interface="org.freedesktop.DBus.ObjectManager"/>
    <allow send_interface="org.freedesktop.DBus.Properties"/>
    <allow send_interface="org.mpris.MediaPlayer2.Player"/>
  </policy>

  <policy context="default">
    <allow send_destination="org.bluez"/>
  </policy>

</busconfig>

Looking at the file in my laptop, there's an extra piece that is missing inside the container

<busconfig>
...
  <!-- allow users of bluetooth group to communicate -->
  <policy groups="bluetooth">
    <allow send_destination="org.bluez"/>
  </policy>
</busconfig>

Adding that group to the user kah (568) or maybe just creating it, making it optional to use with supplementalGroups and adding the policy to the dbus config will allow the use of Bluetooth adapters without root

@onedr0p
Copy link
Owner

onedr0p commented Sep 15, 2022

Any chance you can submit a PR to help get this working? Otherwise I'll get to it when I have time.

@MrMarble
Copy link
Author

Any chance you can submit a PR to help get this working? Otherwise I'll get to it when I have time.

Sure! Let me try!

@MrMarble
Copy link
Author

MrMarble commented Sep 15, 2022

I've been trying to make it works since the last comment, without luck...

I added this to the Dockerfile and used docker run --rm --group-add bluetooth --privileged --net=host home-assisant to run the container:

RUN addgroup -S bluetooth --gid 117 \
    && mkdir -p /etc/dbus-1/system.d \
    && echo $'<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" \n\
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd"> \n\
<busconfig> \n\
  <policy group="bluetooth"> \n\
    <allow send_destination="org.bluez"/> \n\
  </policy> \n\
</busconfig>' > /etc/dbus-1/system.d/bluetooth.conf \
&& chmod 644 /etc/dbus-1/system.d/bluetooth.conf

But got the same authentication error dbus_next.errors.AuthError: authentication failed: REJECTED: ['EXTERNAL'], tried different policies, files and permissions, even disabled appArmor following this related issues edgexfoundry-holding/device-bluetooth-c#4, home-assistant/core#76392, home-assistant/core#76429

The only time I got D-Bus working was running the container as root --user root

Will continue to search for a solution

@onedr0p
Copy link
Owner

onedr0p commented Sep 15, 2022

I figured hass would have issues running in rootless mode for some people, you may want to stick with the official upstream image if you cannot figure it out 😢

@fat-tire
Copy link

fat-tire commented Sep 17, 2022

I just found a solution for podman rootless FWIW (which also solves issue #76429 ).

So- to back up ab it- my problem was dbus was not able to authenticate from a rootless container (podman).

Using strace I was able to find a little more detail about where exactly the authentication was failing, and when googling some of what I found there, I came across some comment in the podman issues queue that sometimes the effective UID (euid) may not be matching the actual UID it wanted. In my case, that made sense-- it seemed that the container's UID (0 running as root) was not matching the host's UID (1002 for the user running the container), which was authorized for dbus. They don't match.

So what to do?

I tried explicitly making the container run as user 1002 with --user 1002 but this would crash the container after about 15 seconds. Shortly after, I came across this discussion on redhat's site.

As a throwaway, it mentions:

Just as an addendum, rootless Podman has another cool option: --userns=keep-id.

I tried adding --userns=keep-id -- and boom! The bluetooth device is detected!

For what it's worth-- this is on a raspberry pi running straight up debian bullseye (not raspbian or raspberryos or whatever it's called) with the built-in bluetooth hw, not a dongle or adapter.

Other things to make sure you've done-- I had previously installed bluez and dbus on the debian host

ii  bluez                                            5.55-3.1+rpt1                    arm64        Bluetooth tools and daemons
ii  dbus                                             1.12.20-2                        arm64        simple interprocess messaging system (daemon and utilities)

and tested it was working on the debian host-- that is, I was able to scan from the host's bluetoothctl with no problem. (Note that I'm only using 5.55 for bluez not the 5.63 that's apparently required for integration and finding entities and stuff so that's gonna have to come next)

I also made sure to add the user running the container to the bluetooth group, and I tested that I could scan from the user's bluetoothctl as well.

I also added the following to the run command to map the local /run/dbus to the container's

  -v /run/dbus:/run/dbus:ro \

Anyway, the device shows up in the UI (with no entitites listed yet, though I can manually pair to a speaker via bluetoothctl) and I don't get the authentication error any more. I'm convinced this is working, I just need to update bluez to a newer version.

For more on the above setup, see the bluetooth instructions.

Now I can finally also scan from the container, and I see that it is running as uid 1002!

Cheers!

@benblasco
Copy link

@fat-tire I am still wrestling with the same solution, and end up with the pod complaining of being able to write to /config. Is there any chance you could share a sanitised version of your Dockerfile and any other consolidated instructions you used to get this going? I may write this up as a blog when I am done getting it working!

@fat-tire
Copy link

Sure. It's been a couple months so I'm not sure about everything still, but here's what I've got. I should mention that the rpi seems VERY slow for playing audio (it stutters) to a bluetooth speaker, which kind of sucks. But I bet for non media usage this would probably be okay. I can detect other types of bluetooth devices from within home assistant within docker (actually podman):

Stuff I did to run this (rootless) w/the official home assistant docker image using Podman. Docker is probably near identical, but I think rootless/Podman is a little safer maybe ?

Running debian 11 bullseye (not raspiOS) on a rpi 4. Some packages that may be relevant (?)

ii  dbus                                             1.12.24-0+deb11u1                arm64        simple interprocess messaging system (daemon and utilities)
ii  bluetooth                                        5.55-3.1+rpt2                    all          Bluetooth support (metapackage)
ii  bluez                                            5.55-3.1+rpt2                    arm64        Bluetooth tools and daemons
ii  bluez-firmware                                   1.2-4+rpt10                      all          Firmware for Bluetooth devices
ii  bluez-obexd                                      5.55-3.1+rpt2                    arm64        bluez obex daemon
ii  libbluetooth-dev:arm64                           5.55-3.1+rpt2                    arm64        Development files for using the BlueZ Linux Bluetooth library
ii  libbluetooth3:arm64                              5.55-3.1+rpt2                    arm64        Library to use the BlueZ Linux Bluetooth stack
ii  libspa-0.2-bluetooth:arm64                       0.3.19-4                         arm64        libraries for the PipeWire multimedia server - bluetooth plugins
ii  pi-bluetooth                                     0.1.19                           all          Raspberry Pi 3 bluetooth
ii  podman                                           3.0.1+dfsg1-3+deb11u1            arm64        engine to run OCI-based containers in Pods

Don't know if this is all needed but it's what I have installed. Also not sure if you're doing sound but I do remember installing pipewire:arm64 and I see pulseaudio installed too. You may have to play with this.

Again, ultimately when I got audio playng and it sound SO stuttery I gave up on the pi.

On host:

$ groups
esphome dialout audio bluetooth rtkit pulse-access

Note this is a regular user (uid/guid=1002 fwiw) and is NOT in the sudo group.

I have the bluetooth.conf in /etc/dbus-1/system.d/bluetooth.conf from earlier in this thread. No idea why but I also see

  <policy user="pulse">
    <allow send_destination="org.bluez"/>
    <allow send_interface="org.bluez.MediaEndpoint1"/>
  </policy>

  <!-- allow users of bluetooth group to communicate -->
  <policy group="bluetooth">
    <allow send_destination="org.bluez"/>
  </policy>

was added there. Don't think that's doing much but it's there .

Here's the build.sh I use (for podman)

#!/bin/bash

buildah bud -t home-assistant:latest --format docker 

And here's the relevant bits of run.sh I use to start the container. I edited it slightly from my own setup, but hopefully it still works.

#!/bin/bash

# you can also just replace ${MYUID} with the user id of whatever account is running this.
export MYUID=`id -u`
export XDG_RUNTIME_DIR=/run/user/${MYUID}
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/${MYUID}/bus
export TZ=`cat /etc/timezone`

# remove any existing container
/usr/bin/podman stop $(/usr/bin/podman ps -a -q)
/usr/bin/podman rm $(/usr/bin/podman ps -a -q) --force

/usr/bin/podman --cgroup-manager=cgroupfs run -d \
  --name homeassistant \
  --restart=unless-stopped \
  -e TZ=${TZ} \
  -v /home/${USER}/config:/config \
  -v /etc/localtime:/etc/localtime:ro \
  -v /run/dbus:/run/dbus:ro \
  -v /etc/dbus-1:/etc/dbus-1:ro \
  -v /dev/bus:/dev/bus:z \
  -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
  -v /run/user:/run/user \
  -v /run/mpd:/run/mpd \
  -e container=podman \
  -e DBUS_SESSION_BUS_ADDRESS:${DBUS_SESSION_BUS_ADDRESS} \
  -e XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR} \
  --systemd=true \
  --network=host \
  --userns=keep-id \
  home-assistant:latest

From configuration.yaml:

bluetooth:

device_tracker:
  - platform: bluetooth_tracker
    interval_seconds: 10
    consider_home: 300
    request_rssi: true
    new_device_defaults: 
      track_new_devices: false

Be sure to add the bluetooth integration from the Settings->Integrations menu.

(see here and here and here for configurations)

When running, I see "Error: you must provide at least one name or id" probably due to one of those lines but I never bothered to debug it since it works. I'm sure it's one of those lines in the run line.

Hope this helps. If you do write a blog about it, do post a link!

@benblasco
Copy link

benblasco commented Apr 3, 2023

Hello, thank you so much for sharing this @fat-tire . Super kind of you to make the time to reply! My use case is a bit different to yours in that I simply want to integrate some Xiaomi temperature and humidity sensors which are passive bluetooth LE devices as far as I understand it. I have been trying to pass less privileges to the container than you have been and still keep getting stuck, but am going to go the whole hog and copy all your permissions/parameters wholesale in an effort to save my sanity. Will update with findings!

@benblasco
Copy link

Hi again @fat-tire I have my sensors working with the bluetooth dongle attached now. The key blocker is that I am running the container on Fedora, with SELinux enabled (as it should be), which was blocking dbus calls. I added what's called a custom SELinux module, and then all my devices started updating super frequently!

Have a read from here onwards in case you are curious as to what I had to do:

https://community.home-assistant.io/t/work-in-progress-configuration-for-running-a-home-assistant-in-containers-with-systemd-and-podman-on-fedora-iot/399253/124?u=benblasco

On another note, here's the yaml version of my container configuration which can be run by podman kube play. I called it h3 because it's my 3rd test configuration :)

apiVersion: v1
kind: Pod
metadata:
  name: h3
spec:
  #hostUsers: false
  containers:
    - name: h3
      image: ghcr.io/home-assistant/home-assistant:stable
      volumeMounts:
        - mountPath: /config
          name: h3-config
        # DBUS required for Bluetooth to work
        - mountPath: /run/dbus
          name: run-dbus
          readOnly: true
        - mountPath: /var/run/dbus
          name: var-run-dbus
      env:
        - name: TZ
          value: Australia/Melbourne
      securityContext:
        #allowPrivilegeEscalation: true
        capabilities: {}
        privileged: true
        runAsUser: 1000
        runAsGroup: 1000
        #fsGroup: 1000
        #fsGroupChangePolicy: "Always"
 
  volumes:
    - name: h3-config
      persistentVolumeClaim:
        claimName: h3-config
    - name: run-dbus
      hostPath:
        path: /run/dbus
        readOnly: true
    - name: var-run-dbus
      hostPath:
        path: /var/run/dbus

I then execute this with the specific command of podman kube play --userns=keep-id --network=host pod-h3.yml.

Thank you again for the info you shared. You helped make things work for me! The critical changes I needed to make that I took from your work were --network=host because I now understand bluetooth is part of the network stack, the bluetooth.conf work, and the mapping of your own User ID (userns=keep-id and the runAsUser and runAsGroup config).

The final issue I run into and have to work around is a bug in podman where the volume h3-config is created with the wrong permissions. This is documented here:

podman run is not honoring --userns=keep-id --user=1000:1000 settings while creating volumes

@fat-tire
Copy link

fat-tire commented Apr 6, 2023

Thanks! I'm so happy to hear something I posted was helpful and that you got it going :)

One workaround I found useful in fixing volume/mount permission errors is to manually mount them and then set them while logged into the container as root. Should only need to be done once-- in the build script or perhaps just before the podman run command. I have an example in a PR I made to the invokeai repo that could probably be easily modified.

The first version I did of this was to do it from the host, but I had hard-coded the volume location and it's probably more "correct" to do it from the "inside" within the container as in this example.

@dezza
Copy link

dezza commented May 22, 2023

Hey guys. Thanks for the writeup, it was quite helpful.

I discovered I needed a third step on a CentOS Stream distro (RHEL 9.3 beta), possibly because selinux policies for containers have been revised. You can generate a custom selinux policy with udica

sudo dnf udica podman setools-console git container-selinux
# as container user
podman inspect homeassistant > homeassistant.json
# as root
udica -j /home/user/homeassistant.json homeassistant
semodule -i homeassistant.cil /usr/share/udica/templates/base_container.cil

It needed a few more permissions than udica detected, so here is what I ended up with:

(block homeassistant
    (blockinherit container)
    (allow process http_cache_port_t ( tcp_socket (  name_bind )))
    (allow process unreserved_port_t ( tcp_socket (  name_bind )))
    (allow process ephemeral_port_t ( tcp_socket ( name_connect )))
    (allow process howl_port_t ( udp_socket ( name_bind )))
    (allow process ssdp_port_t ( udp_socket ( name_bind )))
    (allow process node_t ( tcp_socket ( node_bind )))
    (allow process node_t ( udp_socket ( node_bind )))
    (allow process self ( tcp_socket ( listen )))
    (allow process system_dbusd_t ( dbus ( send_msg )))
    (allow process bluetooth_t ( dbus ( send_msg )))
    (allow process http_port_t ( tcp_socket ( name_connect )))
    (allow process usb_device_t ( dir ( getattr ioctl lock open read search )))
    (allow process usb_device_t ( file ( getattr ioctl lock open read )))
    (allow process usb_device_t ( fifo_file ( getattr open read lock ioctl )))
    (allow process usb_device_t ( sock_file ( getattr open read )))
    (allow process locale_t ( dir ( getattr ioctl lock open read search )))
    (allow process locale_t ( file ( getattr ioctl lock open read )))
    (allow process locale_t ( fifo_file ( getattr open read lock ioctl )))
    (allow process locale_t ( sock_file ( getattr open read )))
    (allow process locale_t ( dir ( getattr ioctl lock open read search )))
    (allow process locale_t ( file ( getattr ioctl lock open read )))
    (allow process locale_t ( fifo_file ( getattr open read lock ioctl )))
    (allow process locale_t ( sock_file ( getattr open read )))
    (allow process user_home_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
    (allow process user_home_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
    (allow process user_home_t ( fifo_file ( getattr read write append ioctl lock open )))
    (allow process user_home_t ( sock_file ( append getattr open read write )))
    (allow process system_dbusd_t ( unix_stream_socket ( connectto )))
    (allow process system_dbusd_var_run_t ( dir ( getattr ioctl lock open read search )))
    (allow process system_dbusd_var_run_t ( file ( getattr ioctl lock open read )))
    (allow process system_dbusd_var_run_t ( fifo_file ( getattr open read lock ioctl )))
    (allow process system_dbusd_var_run_t ( sock_file ( getattr open read write )))
    (allow .bluetooth_t process (dbus ( send_msg )))
)

... at the end here you might be wondering what this means :

(allow .bluetooth_t process (dbus ( send_msg )))

This refers to the global namespace (outside our homeassistant block-scope and results in a selinux-rule like

allow bluetooth_t homeassistant.process:dbus send_msg;

Which keeps all needed permissions for both the container and the system related to homeassistant in one module, it still only applies to containers labelled homeassistant of course.

diff -U 0 --left-column homeassistant_org.cil homeassistant.cil

--- homeassistant_org.cil       2023-05-23 19:32:36.515381026 +0200
+++ homeassistant.cil   2023-05-23 19:49:03.549805579 +0200
@@ -1 +1 @@
-(block homeassistant_org
+(block homeassistant
@@ -5,0 +6,9 @@
+    (allow process ephemeral_port_t ( tcp_socket ( name_connect )))
+    (allow process howl_port_t ( udp_socket ( name_bind )))
+    (allow process ssdp_port_t ( udp_socket ( name_bind )))
+    (allow process node_t ( tcp_socket ( node_bind )))
+    (allow process node_t ( udp_socket ( node_bind )))
+    (allow process self ( tcp_socket ( listen )))
+    (allow process system_dbusd_t ( dbus ( send_msg )))
+    (allow process bluetooth_t ( dbus ( send_msg )))
+    (allow process http_port_t ( tcp_socket ( name_connect )))
@@ -21,0 +31 @@
+    (allow process system_dbusd_t ( unix_stream_socket ( connectto )))
@@ -25 +35,2 @@
-    (allow process system_dbusd_var_run_t ( sock_file ( getattr open read )))
+    (allow process system_dbusd_var_run_t ( sock_file ( getattr open read write )))
+    (allow .bluetooth_t process (dbus ( send_msg )))

Then you can run container as such with the custom type:

ExecStart=/usr/bin/podman run \
        --security-opt label=type:homeassistant.process\
        --userns=keep-id \
                --pull=newer \
        --cidfile=%t/%n.ctr-id \
--cgroups=no-conmon \
        --sdnotify=conmon \
        --replace \
        -d \
        --name homeassistant \
        -v /run/dbus:/run/dbus:ro \
        -v /etc/localtime:/etc/localtime:ro \
        -v /home/ct/homeassistant:/config:Z \
        -v /dev/bus/usb/002/002:/dev/bus/usb/002/002:ro \
        -p 8123:8123 \
        -p 8300:8300 \
        docker.io/homeassistant/home-assistant:stable

I removed

       --systemd=true \
       -v /etc/dbus-1:/etc/dbus-1:ro \
       -v /sys/fs/cgroup:/sys/fs/cgroup:ro \
       --cgroup-manager=cgroupfs \
       -v /run/user/%U:/run/user/%U \

as it wasn't needed from my experience, maybe you can elaborate why these were included @fat-tire ?

@Gillingham
Copy link

@dezza thanks, this is finally what let me get bluetooth working. I had to add a few more permissions for other integreations that needed network connectivity though, ended up with:

(block homeassistant
    (blockinherit container)
    (allow process http_cache_port_t ( tcp_socket ( name_bind )))
    (allow process unreserved_port_t ( tcp_socket ( name_bind name_connect )))
    (allow process unreserved_port_t ( udp_socket ( name_bind )))
    (allow process ephemeral_port_t ( tcp_socket ( name_connect )))
    (allow process pop_port_t ( tcp_socket ( name_connect )))
    (allow process sge_port_t ( tcp_socket ( name_connect )))
    (allow process howl_port_t ( udp_socket ( name_bind )))
    (allow process ssdp_port_t ( udp_socket ( name_bind )))
    (allow process node_t ( tcp_socket ( node_bind )))
    (allow process node_t ( udp_socket ( node_bind )))
    (allow process self ( tcp_socket ( listen )))
    (allow process system_dbusd_t ( dbus ( send_msg )))
    (allow process bluetooth_t ( dbus ( send_msg )))
    (allow process http_port_t ( tcp_socket ( name_connect )))
    (allow process usb_device_t ( dir ( getattr ioctl lock open read search )))
    (allow process usb_device_t ( file ( getattr ioctl lock open read )))
    (allow process usb_device_t ( fifo_file ( getattr open read lock ioctl )))
    (allow process usb_device_t ( sock_file ( getattr open read )))
    (allow process locale_t ( dir ( getattr ioctl lock open read search )))
    (allow process locale_t ( file ( getattr ioctl lock open read )))
    (allow process locale_t ( fifo_file ( getattr open read lock ioctl )))
    (allow process locale_t ( sock_file ( getattr open read )))
    (allow process locale_t ( dir ( getattr ioctl lock open read search )))
    (allow process locale_t ( file ( getattr ioctl lock open read )))
    (allow process locale_t ( fifo_file ( getattr open read lock ioctl )))
    (allow process locale_t ( sock_file ( getattr open read )))
    (allow process user_home_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
    (allow process user_home_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
    (allow process user_home_t ( fifo_file ( getattr read write append ioctl lock open )))
    (allow process user_home_t ( sock_file ( append getattr open read write )))
    (allow process system_dbusd_t ( unix_stream_socket ( connectto )))
    (allow process system_dbusd_var_run_t ( dir ( getattr ioctl lock open read search )))
    (allow process system_dbusd_var_run_t ( file ( getattr ioctl lock open read )))
    (allow process system_dbusd_var_run_t ( fifo_file ( getattr open read lock ioctl )))
    (allow process system_dbusd_var_run_t ( sock_file ( getattr open read write )))
    (allow .bluetooth_t process (dbus ( send_msg )))
)

I actually had to translate audit2allow rules for the new ones and udica didnt seem to want to run with my NFS and iscsi binds, so made it a few more hurdles to jump through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants