How-To: join Debian 12 to an Active Directory domain

What’s this about?

In this post, we’ll go through the steps of getting a computer, running GNU/Linux Debian 12 “bookworm”, be a member of an Active Directory domain.

Specifically, this computer will not be a server. What we’re aiming for is a workstation behaving like a Windows domain member: the end user will be able to log in using his credentials and access corporate network shares and internal web applications seamlessly.

These steps are designed to be generic enough so they can be deployed to an entire fleet of GNU/Linux computers through a master image that could potentially be cloned and restored over a network with CloneZilla.

Let’s Do It

The Basics

Required Packages

We will use SSSD and pam_mount to provide domain joining, authentication, authorization and remote resources to the system.

SSSD (standing for System Security Services Daemon), a Red Hat project, is a set of services and tools that manage the domain connectivity part. SSSD has different, configurable providers like sssd-ldap or sssd-ad and provides interfaces to PAM and KRB5, allowing common GNU/Linux programs to be backed by distant identity, authentication and authorization mechanisms without them having to be linked to another set of libraries or support such protocols internally.

pam_mount is a PAM module that can, among other things, automatically mount partitions, shares, virtually anything that has a mountable filesystem. It can be configured globally or per-user. We will use pam_mount to automatically mount network shares at user login. This will be configured at the system level, meaning that the user cannot opt out of this automatic mounting nor modify it without root privileges.

# apt install realmd sssd samba-common krb5-user adcli libsss-sudo sssd-tools libsasl2-modules-ldap packagekit libpam-mount

Joining the Domain

Joining the domain is just a matter of configuring the basics of KRB5 and using realm join (you will need Domain Administrator credentials if you’ve restricted join operations to administrators):

# nano /etc/krb5.conf
[libdefaults]
default_realm = MYDOMAIN.LOCAL
# realm join --user=[ADMIN USERNAME] MYDOMAIN.LOCAL -v
# realm list
mydomain.local
  type: kerberos
  realm-name: MYDOMAIN.LOCAL
  domain-name: mydomain.local
  configured: kerberos-member
  server-software: active-directory
  client-software: sssd
  required-package: sssd-tools
  required-package: sssd
  required-package: libnss-sss
  required-package: libpam-sss
  required-package: adcli
  required-package: samba-common-bin
  login-formats: %U@mydomain.local
  login-policy: allow-realm-logins

realm list outputs the configured domains and through login-policy indicates that any account existing in the Active Directory domain can be used for logging in.

Tuning our Membership

We will now configure SSSD‘s global and per-domain settings to streamline its operations.

Since we have only one domain, we set default_domain_suffix to our domain name so that it’s possible to log in using short user names.

To better integrate SSSD with Debian 12, we set implicit_pac_responder to false and remove the services parameter.

SSSD‘s services correspond to socket units dynamically handled by systemd, having them statically declared in sssd.conf messes with that. Also, having implicit_pac_responder set to true periodically crashes some SSSD services, and because it is not essential we disable it.

# nano /etc/sssd/sssd.conf
[sssd]
domains = mydomain.local
default_domain_suffix = mydomain.local
config_file_version = 2
implicit_pac_responder = false

[domain/mydomain.local]
access_provider = ad
id_provider = ad
krb5_realm = MYDOMAIN.LOCAL
krb5_store_password_if_offline = True
krb5_ccachedir = /tmp
krb5_ccname_template = FILE:%d/.krb5cc_%U
ad_domain = mydomain.local
full_name_format = %1$s
default_shell = /bin/bash
fallback_homedir = /home/%u@%d
override_homedir = /home/%u@%d
cache_credentials = True
use_fully_qualified_names = False
ldap_id_mapping = True
realmd_tags = manages-system joined-with-adcli

dydns_update = False

In our domain section, we define a predictable path to the KRB5 ticket cache file in the /tmp directory, it’ll be useful later. We also configure our users’ home directory (/home/user@mydomain.local) and allow credential caching in the event of a network loss while the user is logged in.

Now it’s time to use pam-auth-update and enable the following PAM modules:

# pam-auth-update
[*] SSS authentication
[*] Mount volumes for user
[*] Create home directory on login

Enabling pam_sss, pam_mkhomedir and pam_mount enables the system to:

  • Validate the user’s credentials against the domain configured in SSSD;
  • Create the user’s home directory upon first login;
  • Mount the user’s network shares.

pam_mkhomedir can further be configured to use a more restrictive umask:

# nano /etc/pam.d/common-session
session required        pam_unix.so
session optional                        pam_sss.so
session optional        pam_systemd.so
session optional                        pam_mkhomedir.so umask=0077
session optional        pam_mount.so

We can also configure sudoers based on a domain group of privileged users:

# nano /etc/sudoers.d/mydomain
%privilegedusers ALL=(ALL:ALL) ALL

Lastly, we configure the Samba basics to point to our domain:

# nano /etc/samba/smb.conf
[global]
   workgroup = MYDOMAIN
   realm = MYDOMAIN.LOCAL
   encrypt passwords = yes
   client protection = encrypt

Fix GVFS

GVFS is a userspace virtual filesystem that enables unprivileged users to securely and dynamically mount local or remote storage without requiring root. GVFS can use the user’s Kerberos ticket to mount an SMB share without asking for credentials (SSO). Unfortunately, the KRB5CCNAME variable isn’t set in gvfsd‘s environment, meaning that, by default, it cannot use such tickets.

What we do is edit the service unit file and explicitly set the KRB5CCANME environment variable with a value that can predictably match the one we defined in sssd.conf:

# nano /usr/lib/systemd/user/gvfs-daemon.service
[Unit]
Description=Virtual filesystem service
PartOf=graphical-session.target

[Service]
Environment="KRB5CCNAME=FILE:/tmp/.krb5cc_%U"
ExecStart=/usr/libexec/gvfsd
Type=dbus
BusName=org.gtk.vfs.Daemon
Slice=session.slice

Automatically mount corporate shares

pam_mount gets its configuration from user-defined and system-global XML files.

The following configuration file tells pam_mount to:

  • disable its verbose debug output;
  • disregard user-defined configuration files;
  • deny security-unwise mount options;
  • unmount everything when the user’s last session exits;
  • mount a CIFS volume at the remote location on a mountpoint in the user’s home directory with the given options (duplicate the volume element for additional mounts).
# nano /etc/security/pam_mount.conf.xml
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE pam_mount SYSTEM "pam_mount.conf.xml.dtd">

<pam_mount>
                <debug enable="0" />

		<!--
                <luserconf name=".pam_mount.conf.xml" />
                -->

		<mntoptions deny="suid,dev,exec" />
                <mntoptions allow="*" />
                <mntoptions require="nosuid,nodev,noexec" />

		<logout wait="0" hup="no" term="no" kill="no" />

		<volume
                        fstype="cifs"
                        sgrp="domain users"
                        server="myserver.mydomain.local"
                        path="share"
                        mountpoint="~/Network Drives/Share"
                        options="vers=3.0,sec=krb5i,cruid=%(USERUID),nodev,nosuid,noexec,rw"
                />

		<mkmountpoint enable="1" remove="true" />
</pam_mount>

the sgrp attribute is mandatory and should always be set to the by-default, domain users group.

The mount options explicitly require SMB3+ and a Kerberos authentication with signing enforced. To enforce encryption instead (can induce a bigger load), use krb5p in lieu of krb5i. The cruid option is also mandatory and should always be set to %(USERUID).

Is Everything Alright?

Test #1: System Status

After a reboot, the first test should always be to check if something’s not right at the system level:

# systemctl status
● debian12
    State: running
    Units: 365 loaded (incl. loaded aliases)
     Jobs: 0 queued
   Failed: 0 units
    Since: Thu 2023-12-28 11:45:42 CET; 2h 15min ago
  systemd: 252.19-1~deb12u1
   CGroup: /
           ├─init.scope
           │ └─1 /sbin/init
           ├─system.slice
           │ ├─sssd-ifp.service
           │ │ └─4293 /usr/libexec/sssd/sssd_ifp --uid 0 --gid 0 --dbus-activated --logger=files
           │ ├─sssd-nss.service
           │ │ └─4092 /usr/libexec/sssd/sssd_nss --logger=files --socket-activated
           │ ├─sssd.service
           │ │ ├─649 /usr/sbin/sssd -i --logger=files
           │ │ └─857 /usr/libexec/sssd/sssd_be --domain mydomain.local --uid 0 --gid 0 --logger=files

Observation: the global state isn’t degraded and SSSD‘s services are running

Result: PASS

Test #2: Domain Status

We know that Debian is properly running, let’s see if SSSD is doing well too:

# sssctl domain-status mydomain.local
Online status: Online

Active servers:
AD Global Catalog: dc01.mydomain.local
AD Domain Controller: dc01.mydomain.local

Discovered AD Global Catalog servers:
- dc01.mydomain.local
- dc02.mydomain.local

Discovered AD Domain Controller servers:
- dc01.mydomain.local
- dc02.mydomain.local

Observation: domain is online, servers are properly discovered

Result: PASS

Test #3: Hello, I’m logging in

It is now time to log in for the first time:

root@debian12:~# login myuser
Password: ************
Linux debian12 6.1.0-16-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.67-1 (2023-12-12) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Creating directory "/home/myuser@mydomain.local".

Hooray! The user was able to log in using his existing credentials. pam_mkhomedir successfully created their home directory.

myuser@debian12:~$ echo $KRB5CCNAME
FILE:/tmp/.krb5cc_[UID]
myuser@debian12:~$ ls -al /tmp/.krb5cc*
-rw------- 1 myuser      domain users 1470 28 déc.  13:39 /tmp/.krb5cc_[UID]
-rw------- 1 anotheruser domain users 1625 28 déc.  13:37 /tmp/.krb5cc_[ANOTHER UID]
myuser@debian12:~$ klist
Ticket cache: FILE:/tmp/.krb5cc_[UID]
Default principal: myuser@MYDOMAIN.LOCAL

Valid starting       Expires              Service principal
28/12/2023 13:39:29  28/12/2023 23:39:29  krbtgt/MYDOMAIN.LOCAL@MYDOMAIN.LOCAL
        renew until 29/12/2023 13:39:29

SSSD successfully created the user’s Kerberos ticket cache file. The environment variable is properly populated. You can even see that another user is also logged in and has their ticket cache file in /tmp, only accessible to them.

myuser@debian12:~$ ls -al /home/myuser@mydomain.local
total 40
drwx------ 1 myuser domain users   84 28 déc.  13:39 .
drwxr-xr-x 1 root   root          144 28 déc.  13:39 ..
-rw------- 1 myuser domain users  220 28 déc.  13:39 .bash_logout
-rw------- 1 myuser domain users 3526 28 déc.  13:39 .bashrc
-rw------- 1 myuser domain users  807 28 déc.  13:39 .profile

The user’s home directory also has the required restrictive permissions.

Observation: the system successfully handled the user’s first login

Result: PASS

Test #4: GVFS can do SSO

$ cat /proc/$(pidof gvfsd)/environ | xargs --null -n1
HOME=/home/myuser@mydomain.local
LANG=fr_FR.UTF-8
LOGNAME=myuser
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
SHELL=/bin/bash
SYSTEMD_EXEC_PID=1033
USER=myuser
XDG_RUNTIME_DIR=/run/user/[UID]
GTK_MODULES=gail:atk-bridge
QT_ACCESSIBILITY=1
QTWEBENGINE_DICTIONARIES_PATH=/usr/share/hunspell-bdic/
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/[UID]/bus
DISPLAY=:0
XAUTHORITY=/home/myuser/.Xauthority
XDG_CURRENT_DESKTOP=LXQt
MANAGERPID=950
INVOCATION_ID=cdaffc87ee5c4540a61fabfd797369f5
JOURNAL_STREAM=8:8517
KRB5CCNAME=FILE:/tmp/.krb5cc_[UID]

Seems like gvfsd has the path to the Kerberos ticket cache file in its environment! Let’s see if it works…

$ gio mount smb://myserver.mydomain.local/share/
$ gio mount -l
Mount(0): share on myserver.mydomain.local -> smb://myserver.mydomain.local/share/
$ klist
Ticket cache: FILE:/tmp/.krb5cc_[UID]
Default principal: myuser@MYDOMAIN.LOCAL

Valid starting       Expires              Service principal
28/12/2023 13:39:29  28/12/2023 23:39:29  krbtgt/MYDOMAIN.LOCAL@MYDOMAIN.LOCAL
        renew until 29/12/2023 13:39:29
28/12/2023 13:45:31  28/12/2023 23:45:31  cifs/myserver.mydomain.local@MYDOMAIN.LOCAL
        renew until 29/12/2023 13:45:31

No questions asked. GVFS mounted the SMB share, and a new ticket has been appended to the cache file.

Observation: GVFS was able to mount an SMB share with SSO

Result: PASS

Test #4: Mount shares at login

This test is pretty simple. Is the share mounted like pam_mount was supposed to?

$ mount
//myserver.mydomain.local/share on /home/myuser@mydomain.local/Network Drives/Share type cifs (rw,nosuid,nodev,noexec,relatime,vers=3.0,sec=krb5i,cruid=[UID],cache=strict,username=myuser,uid=[UID],noforceuid,gid=[GID],noforcegid,addr=[IP OF MYSERVER],file_mode=0755,dir_mode=0755,soft,nounix,serverino,mapposix,rsize=4194304,wsize=4194304,bsize=1048576,echo_interval=60,actimeo=1,closetimeo=1)

YES IT IS! Options are correct, mount point as well.

Observation: the required SMB share was properly mounted at login

Result: PASS

Final Word

With the right set of tools, it is possible to consider using GNU/Linux workstations in an Active Directory (and consequentially Microsoft-dominated) environment.

During this how-to, you’ve learned how to use such tools to:

  • join an Active Directory domain;
  • properly adapt the system to foster accounts from this distant domain;
  • authorize administrative actions (sudo) based on domain groups;
  • authenticate domain users;
  • allow access to remote resources using their credentials.

Most importantly, everything in this how-to is configured at the system level. It is possible to implement these steps in a model system that can be cloned and deployed to multiple machines across a network.

One Last Thing

If you also want to use SSO with websites and web applications hosted on servers with SPNEGO enabled, you can configure Mozilla Firefox to allow Kerberos negotiation via a set of policies. Red Hat’s documentation on the matter is simple enough so here it is.

By Pierre Blazquez

I mess with computers, drink too much coffee and listen to music at max volume.

2 comments

  1. Hey, cool post You can check if there’s a problem with your website with Internet Explorer. Because of this issue, many readers will overlook your excellent writing because IE is still the most popular browser.

    1. Thanks for your input – what kind of issues are you experiencing? Since IE is deprecated having this website fully working with it isn’t a top priority. Regards.

Leave a comment

Your email address will not be published. Required fields are marked *