bluetooth without root

How can I run bluetoothctl without root? It works fine as root user (scan, etc), but without I get

$ bluetoothctl
Waiting to connect to bluetoothd...dbus[63]: arguments to dbus_connection_get_object_path_data() were incorrect, assertion "connection != NULL" failed in file ../../../dbus/dbus-connection.c line 5905.
This is normally a bug in some application using the D-Bus library.

  D-Bus not built with -rdynamic so unable to print a backtrace
Aborted (core dumped)

The user is in the bluetooth group.
My setup is Ubuntu, udev=on, io.balena.features.dbus: 1, bluez, udev.

I realize now that this may be more difficult than anticipated.
I’ve not found any solution that accesses Bluetooth from docker without root.

Hi,

Can you share your Docker (compose) file?
That will allow us to also test some things.

I found this reddit post and this issue that mention the same error you have.

Supposedly the issue in the first link was that the bluetoothd service wasn’t started properly.
The second link suggests they fixed the error in docker.

I’m not quite sure yet why this would behave so different for root vs bluetooth user; might have something to do with permissions for the dbus system bus?

Thanks for the offer to look into this!

First, I want to make clear that with ROOT bluetooth is working properly. The error occurs only for a non-root user.

My setup is a bit involved, and therefore I tried to make a “minimal” example. Embarrassingly now I run into another error (even with root). Calling bluetoothctl gets stuck:

# bluetoothctl
Waiting to connect to bluetoothd...

I tried the same command on the host, same result.

Here’s my slimmed-down example:

Dockerfile:

FROM balenalib/%%BALENA_ARCH%%-ubuntu-python:3.9-focal-run

ENV UDEV=on

ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update -q --yes \
 && apt-get install --yes --no-install-recommends \
    bluez \
 && apt-get clean && rm -rf /var/lib/apt/lists/*

RUN useradd -m -s /bin/bash -G bluetooth iot

WORKDIR /home/iot

CMD [ "/bin/bash", "-c", "while true; do sleep 3000; done;" ]

dockercompose.yml:

version: '2'

services:

    ble:
        build: ./ble
        restart: unless-stopped
        privileged: true
        network_mode: host
        labels:
            io.balena.features.dbus: 1
            io.balena.features.supervisor-api: 1
        environment:
            - DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket

Maybe I’m missing some library?

Hi,

I’ve tried your examples (on raspberry pi 3B+) and can see the bluetoothctl working for ROOT user, but not for the IOT user.

I made 3 changes compared to what you posted.

  1. When you use the %%BALENA_ARCH%% tag, your filename needs to be Dockerfile.template rather than Dockerfile.
  2. It’s docker-compose.yml rather than dockercompose.yml
  3. I changed the CMD line to run balena-idle

Now that I can reproduce this, I will investigate this further later today.

Hi,

I’ve been looking into this for a little bit now, but did not get it working quite yet.

Supposedly you should be able to add the right permissions in /etc/dbus-1/system.d/bluetooth.conf, but I think it’s using the permissions from the HostOS rather than the container.

Here they suggest you can use dbus to disable bluez in the HostOS and run it in your own in container, but again the iot user will lack the permissions to do this.

Can you explain why exactly you want to run bluetoothctl as non-root?
Maybe there are easier alternatives.

If it’s any consolation, I did get hcitool (specifically the BLE part) to work for the iot user. :slight_smile:
For reference, I did this with the following additions to the Dockerfile.template:

  1. Install package libcap2-bin
  2. Add the command RUN setcap 'cap_net_raw,cap_net_admin+eip' $(which hcitool)

Hi TJvV,

Thank you for these suggestions. I also had seen the option to edit policy in bluetooth.conf, but surmised that dbus was presumably contacting bluetooth on the host. Would it be a solution to modify that file? Presumably I could do this with a script (ssh root@172.17.0.1 -p 22222 script). Maybe I’ll give this a try.

Just out of curiosity: setcap is in addition to privileged? What actually does setcap specified on the container do? Applies to all programs running in that container? I’d played with that option, without success.

The motivation for root-free bluetooth: I’ve built a Balena app for programming microcontrollers. It includes a battery of tools, editors, compilers, backup, etc. I even installed the balena cli and use it to update the app from within! bluetoothctl is just a minimal example, really I want to run some Python library, e.g. bleak (which works fine as root).

I found this setup preferable over running on a local machine (e.g. laptop) since everything comes preinstalled and things like --privileged on containers are less of an issue on a dedicated machine.

Hopefully it will soon be ready for publishing as an open fleet.

FYI: tried editing bluetooth.conf on the host. Since it’s a readonly filesystem, this probably would need to be done with a special os build. I don’t want to go there …

Hi,

If you just want to play around with some files on the HostOS for testing (won’t persist Host updates, not easily carried over to other devices), you could run mount -o remount,rw / in the HostOS to make the root writable.

Basically the setcap commands specifies what the capabilities of a specific file/command are.
The $(which hcitool) part simply resolves to the installed hcitool.
These capabilities are required for accessing the BLE part of the device.
Without it, you can scan normal bluetooth devices just fine (hcitool -i hci0 scan), but get a permission error when trying hcitool -i hci0 lescan.

Something to keep in mind (which I also conveniently forgot) when playing around with docker permissions, is that the UIDs and GIDs are shared between Host and container.
So you should be able to create a group or user with a specific UID/GID in Host, add a policy for that specific UID/GID in Host, and then specify the same UID/GID for your new user in Container and it might work.

I haven’t tried this yet myself, but it does come with the same persistence and portability caveats.

Your application sounds very interesting, but I’m not quite sure your explanation makes it clear to me why you can’t just do it as a root user in the container though.

Hi,

I can and this is what I am doing currently. Since the container is used for development (with and editor, command line, etc), running as root is not ideal.

I’ve also observed that sudo does not solve the problem. I.e. if I exit the Dockerfile with USER iot and then run sudo bluetoothctl in a terminal window, I still get an error. Exiting with USER root works. A bit strange, I don’t understand why this is the case.

This is an interesting option to keep in mind.

Thanks!

I verified that patching bluetooth.conf on the host enables root-free bluetooth in containers.

Run the script below in the container to make the necessary modifications on the host to enable bluetooth for user iot:

cd /etc/dbus-1/system.d

if ! grep -q '<policy user="iot">' bluetooth.conf
then
    echo add user iot to bluetooth.conf
    mount -o remount,rw /
    cp /etc/passwd /etc/passwd.bak
    echo 'iot:x:1000:100::/home/iot:/bin/sh' >>/etc/passwd
    cp bluetooth.conf bluetooth.conf.bak
    sed -i 's|</busconfig>|  <policy user="iot"> \
    <allow own="org.bluez"/> \
    <allow send_destination="org.bluez"/> \
    <allow send_interface="org.bluez.GattCharacteristic1"/> \
    <allow send_interface="org.bluez.GattDescriptor1"/> \
    <allow send_interface="org.freedesktop.DBus.ObjectManager"/> \
    <allow send_interface="org.freedesktop.DBus.Properties"/> \
  </policy> \
</busconfig>|' bluetooth.conf
fi
echo bluetooth enabled for user iot
EOF

Note: no need for root to run the script. I’ve tried this with a Development container, that’s presumably a requirement!

Hi,

Glad to hear you got it working.

Just out of curiousity (and for completeness), how did you patch the HostOS?
I’m assuming running the same script?

I would expect it would safer/more portable to map the policy to a specific UID rather than a username.
You can use the same syntax in the bluetooth.conf, but use something like <policy user=1001>.
Assigning the permissions to a group/GID might be even more desirable.

Sidenote about sed: you can specify an extension for a backup file when using -i.
For example sed -i.bak bluetooth.conf will save a backup to bluetooth.conf.bak, this can save you a cp command :slight_smile:

I run the script on the container that wants access to bluetooth. It changes bluetooth.conf on the host.

Permissions are usually controlled by the bluetooth group. It does not exist on the host but I could create it, rather than the iot user.

How does the host check permissions? Is it sufficient if any of

  • user name
  • user id
  • group name
  • group id

match the corresponding item of the caller in the client container? For example, if I create and add the bluetooth group to the profile on the host and the caller is a member of that group but the group ids differ on the host and client container, will permission still be granted?

Hi,

I’m fairly certain (but not entirely) that the user/group name is translated to a UID/GID when checking permissions.
This would mean that user Container1 with UID 1001 in the container would have the same permissions as user Host1 with UID 1001 in the Host OS.