published: 2022-08-26
title: LDAP authentication on Home Assistant
tags: free-software, miscellaneous, wiki
previous [‘Introducing Blogging Friday’] next [‘How I started programming’] parent directory [‘Blog’]
Last week I wrote a few sentences about a beautiful script[4] I
ldapauth [‘beautiful script’] found, to authenticate against an LDAP server, which could be used e.g. on the Home Assistant[5], a platform to manage home automation and
homeassistant [‘Home Assistant’] the like. We deployed a Home Assistant instance at work, to monitor temperatures in various rooms and fringes, and to raise notifications and alarms, should temperatures exceed certain thresholds. All team members should be able to log into the system, using their central login credentials from the LDAP server.
The shell script uses either of the command line utilities
ldapsearch
(from the openldap-clients package) or
curl
to make a request to the LDAP server, which requires a
valid username and password. Both scripts will return an error code >
0 if something goes wrong; as usual, the exit code 0 will let us know if
the command worked and thus if the username/password combination was
correct. Further, the LDAP server can be queried for some extra
attributes like the displayName
or others, which can be
mapped into the requesting system.
However, there was one issue I hadn’t anticipated; neither
ldapsearch
nor curl
compiled with LDAP
support was available on the Home Assistant.
There are plenty of ways to deploy Home Assistant. We had a spare Raspberry Pi and decided to use the HassOS distribution that is recommended when installing on a Pi. HassOS (the Home Assistant Operating System) is a minimalistic operating system that deploys the individual modules of Home Assistant as containers. The containers that are deployed are usually built on Alpine images. However, there were two problems:
As proof of concept, I installed an SSH integration that would at least let me communicate with parts of the Home Assistant system via ssh. The ssh container per default also mounts the config and other persistent directories of Home Assistant.
So I downloaded the ldap-auth.sh
script to the
persistent config
folder and started by adding the
ldapsearch
tool, with apk add openldap-clients
and configured ldap-auth.sh
until I was able to
authenticate. I updated the Home Assessment config with an
auth_provider section like this:
homeassistant:
auth_providers:
- type: command_line
command: /config/scripts/ldap-auth.sh
meta: true
- type: homeassistant
Beware! Do include type: homeassistant
in your list of
auth providers or you will lock yourself out of the system if the script
does not work correctly (just like I did).
After reloading the config, login with the command_line
type of course failed, but I didn’t find any logs that would propagate
the error message, so I added some echo
lines in the script
manually, to find out that ldapsearch
cannot be found by
the authenticating container.
So I tried my luck with curl
; however I could not make
any reasonable request without the built-in LDAP support.
So I figured I basically had three possibilities:
openldap-clients
baked
into the container images, or build (and maintain) the image myself
orcurl
for my target container with all the needed
functions linked statically into one binary.I assumed that all containers in the Pi’s Home Assistant ecosystem
would be the same architecture, which is Alpine on aarch64
for the ssh container. So I installed all dependencies I needed on the
ssh container, cloned the curl repo and started configuring, installing
missing dependencies on the fly.
./configure --with-openssl --with-ldap --disable-shared
Choosing the ssl library is mandatory; --disable-shared
should prevent the use of any shared library, so any dependency I had to
install that would not be available on the target machine later.
The built went through and I had an LDAP enabled curl
that I could test my requests with, so again I tinkered with the
ldap-auth.sh
script until it would succeed.
However, when used from the web interface it would not work, again, this time complaining about missing dependencies, which I thought I had all included.
Checking the compiled binary I found 769.4K, so much bigger than my 199K system curl, so something must have been linked statically. Looking up shared object dependencies revealed what was missing:
[core-ssh ~]$ ldd curl
/lib/ld-musl-aarch64.so.1 (0x7f930c0000)
libssl.so.1.1 => /lib/libssl.so.1.1 (0x7f92f76000)
libcrypto.so.1.1 => /lib/libcrypto.so.1.1 (0x7f92d26000)
libldap.so.2 => /lib/libldap.so.2 (0x7f92cc1000)
liblber.so.2 => /lib/liblber.so.2 (0x7f92ca3000)
libc.musl-aarch64.so.1 => /lib/ld-musl-aarch64.so.1 (0x7f930c0000)
libsasl2.so.3 => /lib/libsasl2.so.3 (0x7f92c79000)
While this is still a lot less dependencies than my system installed curl:
[`which curl`](ldd)
linux-vdso.so.1 (0x00007ffc8fdb6000)
libcurl.so.4 => /usr/lib/libcurl.so.4 (0x00007fce55263000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007fce55057000)
libnghttp2.so.14 => /usr/lib/libnghttp2.so.14 (0x00007fce5502c000)
libidn2.so.0 => /usr/lib/libidn2.so.0 (0x00007fce5500a000)
libssh2.so.1 => /usr/lib/libssh2.so.1 (0x00007fce54fc9000)
libpsl.so.5 => /usr/lib/libpsl.so.5 (0x00007fce54fb6000)
libssl.so.1.1 => /usr/lib/libssl.so.1.1 (0x00007fce54f1f000)
libcrypto.so.1.1 => /usr/lib/libcrypto.so.1.1 (0x00007fce54c3f000)
libgssapi_krb5.so.2 => /usr/lib/libgssapi_krb5.so.2 (0x00007fce54bea000)
libzstd.so.1 => /usr/lib/libzstd.so.1 (0x00007fce54b41000)
libbrotlidec.so.1 => /usr/lib/libbrotlidec.so.1 (0x00007fce54b33000)
libz.so.1 => /usr/lib/libz.so.1 (0x00007fce54b19000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fce55380000)
libunistring.so.2 => /usr/lib/libunistring.so.2 (0x00007fce5496b000)
libkrb5.so.3 => /usr/lib/libkrb5.so.3 (0x00007fce54892000)
libk5crypto.so.3 => /usr/lib/libk5crypto.so.3 (0x00007fce54862000)
libcom_err.so.2 => /usr/lib/libcom_err.so.2 (0x00007fce5485c000)
libkrb5support.so.0 => /usr/lib/libkrb5support.so.0 (0x00007fce5484d000)
libkeyutils.so.1 => /usr/lib/libkeyutils.so.1 (0x00007fce54846000)
libresolv.so.2 => /usr/lib/libresolv.so.2 (0x00007fce54831000)
libbrotlicommon.so.1 => /usr/lib/libbrotlicommon.so.1 (0x00007fce5480e000)
there were still way too many shared libraries involved for my taste.
I even asked in #curl
in the libera net what I could
have done wrong or misunderstood.
14:57:34 schubisu | hi everyone! I'm trying to build a statically linked curl
| and configured with `--with-openssl --with-ldap --disable-shared`.
| However, when I run the binary on another machine it says
| it cannot find the shared libraries libldap and liblber. Did I
| misunderstand static linking?
15:27:25 bagder | static linking is a beast
Well, it was nice to hear that it may not have been entirely my fault :) bagder pointed me to Static curl[6], a github repository that builds
staticcurl [‘Static curl’] static releases for multiple platforms (YAY), but sadly also with disabled LDAP support (AWWW). Running the build script with LDAP enabled also didn’t run through.
Having spent way too much time on this issue, I went ahead with
something that may be an ugly hack, but it’s also a “works for
me”: I had already copied the statically linked curl in the
persistent config
folder already, so I would just add the
missing libraries there as well.
I figured that from the 7 shared dependencies, 4 were available in the standard Alpine image anyway, so I was missing only three files:
that I copied from my ssh container into the persistent storage. I
adjusted the ldap-auth.sh
script one last time to add one
line:
export LD_LIBRARY_PATH="/config/scripts"
and that did the trick.
I also confirmed that on the fresh system after re-boot, everything is still in place and working beautifully :)