Unix Single Sign On using realmd and Microsoft Active Directory (2019)



Intro

A lot has changed in this space over the last 6 years as more organisations adopt this kind of SSO facility.

There are still some age-old annoying issues that just don't want to go away - like the 15 character limit for Netbios names, Windows admins leaving the user's primary AD group at the default of "Domain Users" which contains that problematic space character, and automatic updates in DNS when joining domains, fortunately these can be worked around.

In this example I will set up a Unix machine to use Active Directory for its authentication, and just to add some extra functionality I will force all users to belong to a local group on the host.

This document is current at the time of writing (January 2019) and assumes some basic Windows and Unix sysadmin knowledge. It has been successfully implemented in a commercial environment running RedHat 7.6 and Windows Server 2016.


Windows: Extending the Active Directory schema

Forget it. Accept the fact that Microsoft have lived up to their threat of dropping support of their Posix extension for Active Directory and get used to having a big Unix UID generated from a Microsoft SID. If you are still keen then with a bit of effort you can manually extend Active Directory with Posix attributes but this now appears to be just a legacy thing, move on.

A major benefit of doing this SSO configuration the new way is that you no longer have to manage UIDs and GIDs and trying to keep them unique in large organisations.

Ultimate control of UID and GID now comes from Active Directory, although the default Active Directory group of "Domain Users" still poses a problem on Unix systems there are workarounds to help deal with it.

One big security flaw with this approach is that you must lock down your SSO on your Unix hosts to only operate on just one Windows domain (or within trusted domains). Otherwise any untrusted Windows domain could replicate the same Unix accounts in their own domain and gain login ability.

One big question still left unanswered is can you transition a bunch of Microsoft SIDs to a new Windows domain so that your derived UID and GID values used throughout your Unix environment are preserved? Methinks you can't without resorting to kludges which defeats the whole purpose of choosing this approach for its simplicity in the first place.


Windows: Create an OU in Active Directory for joined Unix hosts

Create an OU structure in Active Directory for joined Unix hosts. E.g.

OU=Linux,OU=Devices,OU=corp,DC=mycompany,DC=com



Windows: Create a join user just for Unix hosts

Most Windows sysadmins don't understand Unix needs so you are better off creating a dedicated Active Directory join user in the Windows system with its own password just for joining Unix hosts to the Windows domain, and also allows device records to be written into the OU described above.

unix_join_user

No further permissions are required for this account.


Unix: Install client packages

On the Unix hosts install the following packages:

yum install adcli sssd sssd-ad realmd oddjob oddjob-mkhomedir samba-common-tools krb5-workstation



Unix: Installing Kerberos client

We only need to run the client so do not install a Kerberos server. There are two flavours of Kerberos available: MIT restricted to USA, and Heimdal for everyone else.

Install a recent version of the Kerberos client satisfying all dependencies. Older versions of the Kerberos client could not operate in a multi Windows domain environment due to an internal bug.

A large number of configuration directives are not implemented in the Heimdal version making the man page painful.

Ensure the clock on your Unix host is within 5 minutes of the clock on your Windows Domain Controllers otherwise Kerberos will fail all login attempts. You should install either chronyd or the Network Time Protocol (NTP) daemon on all your hosts to keep your computer clocks in sync to avoid this problem.

Configure krb5.conf (usually /etc/krb5.conf) as follows:

# Configuration snippets may be placed in this directory as well
includedir /etc/krb5.conf.d/

[logging]
 default = FILE:/var/log/krb5libs.log
 kdc = FILE:/var/log/krb5kdc.log
 admin_server = FILE:/var/log/kadmind.log

[libdefaults]
 dns_lookup_kdc = true
 dns_lookup_realm = false
 ticket_lifetime = 24h
 renew_lifetime = 7d
 forwardable = true
 rdns = false
 default_realm = CORP.MYCOMPANY.COM
 default_tkt_enctypes = aes256-cts-hmac-sha1-96 arcfour-hmac
 default_tgs_enctypes = aes256-cts-hmac-sha1-96 arcfour-hmac
 permitted_enctypes = aes256-cts-hmac-sha1-96 arcfour-hmac

[realms]
 CORP.MYCOMPANY.COM = {
   kdc = dc1.corp.mycompany.com
   kdc = dc2.corp.mycompany.com
   admin_server = dc1.corp.mycompany.com
   admin_server = dc2.corp.mycompany.com
}

[domain_realm]
 .corp.mycompany.com = CORP.MYCOMPANY.COM
 corp.mycompany.com = CORP.MYCOMPANY.COM



Unix: nsswitch.conf

Ensure the Name Service Switch is using SSSD.

If not already present after the SSSD RPM install then configure specific entries in nsswitch.conf (usually /etc/nsswitch.conf) as follows:

passwd:     files sssd
shadow:     files sssd
group:      files sssd



Unix: Configuring realmd

Configure a realmd.conf (usually /etc/realmd.conf) as follows:

[users]
default-home = /home/%U
default-shell = /bin/bash

[active-directory]
default-client = sssd

[corp.mycompany.com]
computer-ou = OU=Linux,OU=Devices,OU=corp,DC=mycompany,DC=com
fully-qualified-names = no
manage-system = no

I did find it odd that realmd is as the name implies shipped as a daemon but according to systemctl isn't running. Maybe I missed something in the documentation, nonetheless, just leave the realmd run-status the way it was installed.


Unix: Joining the Windows domain

Before you perform this step:

Before you join the domain, count how many characters there are in your Unix short host name (i.e without the domain suffix). If your Unix short name is greater than 15 characters in length then the Netbios object that is created in Active Directory will simply be truncated to 15 characters in length. This means that if you join another host to the same domain and OU that starts with the same 15 characters then it will clobber any existing Netbios object in there rendering that previous host broken (but your new host will work). The workaround with Unix hosts that have short names longer than 15 characters is to manually specify a unique Netbios name to the realm command that is less than or equal to 15 characters in length. Any adjusted Netbios name will also need to be referenced in the SSSD config.

There is a funny bug between realm and SSSD in trying to be too smart with the handling of the case of the Netbios name that is used in the Kerberos principal name. I've found converting the Netbios name to be all uppercase for the realm command line option avoids this bug.

The following realm command is what I use to join the Windows domain:

realm -v join "<ad_join_server>" -U "<ad_join_user>" --computer-name="<ad_netbios_name_uppercase>" --os-name="<os_name>" --os-version="<os_version>"
Password: <ad_join_user_password>

Note: If your Unix shortname is less than or equal to 15 characters then you don't need to supply the --computer-name option.
It is good practice to supply the --os-name and --os-version options to aid in searches/reports of joined computers in your Active Directory system.

For the following example of joining a Linux RedHat 7.6 server called marketing-webhost-01.corp.mycompany.com to the Windows domain I would execute the following:

realm -v join "dc1.corp.mycompany.com" -U "unix_join_user" --computer-name="MARKETING-WH-01" --os-name="RedHat Enterprise Linux Server" --os-version="7"
Password: <unix_join_user_password>

The join should update both the target OU with the Netbios name you specified and the /etc/krb5.keytab file, listing it should display the principal names.

klist -k /etc/krb5.keytab

The realmd tool generates a rather useless sssd.conf file which we will clobber with our own specific version of it later.


Unix: Configuring PAM

There are many ways to configure PAM which is complicated further by the many differing implementation techniques used by different Unix systems. The goal however is the same and that is to load the pam_sssd.so library at the correct point. You may want to search the web or consult your own system's documentation on how to go about this on your own computer(s).

Fortunately with RedHat 7 the installation of the SSSD RPM will update and configure PAM for you.

However, in this setup, I'd also like to restrict login access to this host to users who belong to an Active Directory group called unix_users. I can achieve this by using an SSSD feature called proxy which effectively calls PAM again from SSSD to make use of the /etc/security/access.conf file for AD logins.

A note of caution, if you are not careful with calling PAM a second time you can inadvertently create an infinite loop. To safely prevent such a thing from happening then create a dedicated PAM config file called sssd-proxy (usually /etc/pam.d/sssd-proxy) as follows:

account     required      pam_access.so

Now rebuild your PAM as follows:

authconfig --updateall



Unix: Configuring SSSD

Unfortunately due to the Netbios name length limitation of 15 characters we cannot have a simple standard sssd.conf for all Unix hosts on the domain - thanks Microsoft (not).

For configuring sssd.conf (usually /etc/sssd/sssd.conf) for a Unix host called marketing-webhost-01.corp.mycompany.com it would look something like the following:

[sssd]
domains = CORP.MYCOMPANY.COM
config_file_version = 2
services = nss, pam

[pam]
reconnection_retries = 3

[domain/CORP.MYCOMPANY.COM]
ad_server = dc1.corp.mycompany.com,dc2.corp.mycompany.com
ad_domain = corp.mycompany.com
krb5_realm = CORP.MYCOMPANY.COM
realmd_tags = manages-system joined-with-adcli
cache_credentials = True
id_provider = ad
krb5_store_password_if_offline = True
dyndns_update = False
default_shell = /bin/bash
override_homedir = /home/%u
ldap_id_mapping = True
ldap_group_nesting_level = 1
auto_private_groups = True
use_fully_qualified_names = False
# The next two lines are only necessary if using an adjusted netbios name
ldap_sasl_mech = GSSAPI
ldap_sasl_authid = host/marketing-wh-01
access_provider = proxy
proxy_pam_target = sssd-proxy

# Don't partake otherwise
[domain/default]
cache_credentials = False
access_provider = deny

The key points of this configuration file are:

Restart SSSD, sometimes it can be peace-of-mind to also blow away the SSSD cache before a restart.

rm -f /var/lib/sss/db/* /var/lib/sss/mc/*
rm -fr /var/cache/realmd/*
systemctl restart sssd
systemctl status sssd

At this stage you should now be able to talk to Active Directory and query the user database using standard Unix commands such as id, groups, getent passwd, getent group, etc.


Unix: Configure access

Assuming you have an Active Directory group called unix_users which contains members that are allowed to log into Unix hosts, then configure an access.conf (usually /etc/security/access.conf) as follows:

+ : (login) : ALL
+ : root : LOCAL

+ : unix_users : ALL

- : ALL : ALL

if you want to restrict login to the host to only specific users, then create an Active Directory group just for this Unix host and use the host name in some part of the group name.


Unix: /etc/group

I've found the reliability of SSSD to be much better these days that this step is now not really necessary. However not every situation can be catered for so to save you any potential headaches with any unreliability caused by SSO such as domain controllers becoming unreachable, I'll repeat what I said 6 years ago.

Tools like cron, sudo and sshd are often configured using group privileges that refer to the group name and not the GID, for example most Unix admins often restrict sshd to only allow users in a specified group to log in e.g. unix_users. When the association between GID and group name cannot be resolved then these tools can start causing problems.

The fix is to either identify the situation that causes the group name to not resolve to its GID, or add the group name in /etc/group using the same GID defined in Active Directory. I recommend the latter approach if your Unix admins don't have full Windows domain administrator privileges.

unix_users:*:19224598:

In sshd_config (usually /etc/sshd/sshd_config) I have a line as follows (Note the double entry to handle both cases).

AllowGroups unix_users CORP\unix_users

I do something similar for sudoers (usually /etc/sudoers.d/unix_users) I have a line as follows (Note the double entry to handle both cases).

%unix_users,+unix_users ALL = (build) /usr/bin/make



Unix: Forcing AD users into local group membership

You may have an application that requires your users to become a member of a local group in order to use the application. Typically your options are:

The first option is an administrative chore unless you run some kind of software configuration management tool like Puppet or Ansible. The second option is weakening the resilience of your application because it now also depends on AD being available. It also gets messy if your application needs to define its own GID to be used.

The workaround is to use a PAM module called pam_group.so that can automatically assign users to local groups once they log in.

Assuming a local group called myapp is already configured on your Unix host as follows:

myapp:*:1450:

Configure a group.conf file (usually /etc/security/group.conf) as follows:

# Allow all SSH users to also be in the myapp group
*;*;*;Al0000-2400;myapp

The above forces all logged in users to become a member of the group myapp. See the group.conf man page for further details on how to use it.

If you just want to restrict membership of the myapp group to an AD group called unix_users then configure the group.conf file as follows:

# Allow members of AD group unix_users to also be in the myapp group
*;*;%unix_users;Al0000-2400;myapp

The next step is to install the pam_group.so library into PAM. This can be tricky as pam_group.so only supports the auth function class which means there are limited places you can use it effectively. In the case below I am adding it to SSHD (usually /etc/pam.d/sshd) as follows:

#%PAM-1.0
auth       required     pam_sepermit.so
auth       substack     password-auth
auth       include      postlogin
# Extend group membership control
auth       optional     pam_group.so
# Used with polkit to reauthorize users in remote sessions
-auth      optional     pam_reauthorize.so prepare
account    required     pam_nologin.so
account    include      password-auth
password   include      password-auth
# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open env_params
session    required     pam_namespace.so
session    optional     pam_keyinit.so force revoke
session    include      password-auth
session    include      postlogin
# Used with polkit to reauthorize users in remote sessions
-session   optional     pam_reauthorize.so prepare

Once again rebuild your PAM as follows:

authconfig --updateall

Log in and execute the groups command. You should now see that you are a member of a local Unix group called myapp.

Interestingly, run the same command but this time specify your username as an argument, e.g. groups me. You no longer appear to be a member of the myapp group, why? I haven't straced the execution but I think when no argument is used it reads some kernel allocated environment that was set up during SSH login, and with an argument supplied then SSH hasn't been invoked and thus pam_group.so hasn't run. Told you it was tricky.