Linux and Windows: A tale of Kerberos, SSSD, DFS, and black magic.

Posted on March 13, 2018

Overview

Interfacing with Active Directory is kind of a pain in the rear. Here’s what I’ve implemented. This is somewhat lightweight, in that AD is only used for authentication - it is not used (directly) for access control, GPO is not utilized, no print services, etc. I do not go over autofs configuration, because it’s pretty straightforward.

  1. Confirm hostname
  2. Install packages
  3. Configure Kerberos
  4. Initialize Kerberos ticket
  5. Join to domain
  6. Configure SSSD
  7. Configure DFS mounts

Details

Hostname

You should set your hostname to be your FQDN, uppercased. This doesn’t have to look ugly, if you have systemd! This will fix it to uppercase FQDN where it matters, but for display purposes you should see a lowercase short name.

You don’t have to do this, but you may have problems with adcli later on, for example when it attempts to update the machine account in AD.

hostnamectl --static set-hostname "FOO.AD.EXAMPLE.COM"
hostnamectl --pretty set-hostname "foo"
hostnamectl --transient set-hostname ""

Packages

This depends on your distribution, of course. You will need the krb5 userspace, adcli, realmd, sssd, the openldap client, and the PAM modules for krb5 and sssd. For mounting DFS, you’ll need the CIFS tools.

Kerberos Configuration

This is pretty straightforward. Note that realm names in Kerberos are case-sensitive and should be uppercased - contrary to DNS. Example /etc/krb5.conf file follows.

[libdefaults]
default_realm = AD.EXAMPLE.COM
dns_lookup_realm = true
dns_lookup_kdc = true
ticket_lifetime = 24h
renew_lifetime = 7d
rdns = false
forwardable = true
default_ccache_name = KEYRING:persistent:%{uid}

[domain_realm]
.ad.example.com = AD.EXAMPLE.COM
ad.example.com = AD.EXAMPLE.COM

Kerberos Ticket

This is also pretty straightforward - the complexity comes with automation, if you want to do that. Here’s an example ansible task, for inspiration. You can also see how to do it manually, if you don’t care about automating it. Note that GetDomainAdminPassword is left as an exercise for your imagination. Something like Hashicorp Vault may be ideal. Either way, you should take care with the credential. Again, the Kerberos realm is case sensitive!

- name: initialize Kerberos ticket
  shell: |
    set timeout 300
    spawn /usr/bin/kinit -V adminuser@AD.EXAMPLE.COM
    expect -exact "Password for adminuser"
    send -- "{{ lookup('pipe', '/usr/local/bin/GetDomainAdminPassword') }}\r"
    expect eof
    catch wait result
    exit [lindex $result 3]
  args:
    executable: /usr/bin/expect
  changed_when: false
  no_log: true

Domain Join

This next step, using realmd to join the domain, should not require authentication. If it asks, something is wrong! It should use the previously created Kerberos ticket. Note, that if the user does not have permissions to create the computer account, it will silently fail and realmd will ask for a password on the TTY. You should use an uppercase computer name (FQDN, not hostname!), but the domain argument (the last one) can be lowercase. Also note the “static” hostname you set back in step 1 should match the computer name supplied here.

The following example is a single command, I have broken the arguments out to individual lines for clarity.

/usr/sbin/realm
  --unattended
  join
  --automatic-id-mapping=no
  --client-software=sssd
  --computer-name=FOO.AD.EXAMPLE.COM
  --computer-ou='OU=Linux,OU=Servers,DC=ad,DC=example,DC=com'
  ad.example.com

SSSD Configuration

This part will need some customization on your part. I’m not going to go into all the details, rather I’m going to show you a working example. Note that some of the domain name entries are uppercase - in those cases it’s referring to the Kerberos realm (and as such are case sensitive - thanks, MIT!), so keep them uppercase.

[sssd]
config_file_version = 2
domains = ad.example.com
reconnection_retries = 3
services = nss, pam

[nss]
entry_cache_nowait_percentage = 75
filter_groups = root
filter_users = root
reconnection_retries = 3

[pam]
reconnection_retries = 3

[domain/AD.EXAMPLE.COM]
access_provider = simple
account_cache_expiration = 7
ad_domain = ad.example.com
auth_provider = ad
cache_credentials = True
default_shell = /bin/bash
dns_discovery_domain = AD.EXAMPLE.COM
dyndns_update = false
enumerate = true
fallback_homedir = /home/%u
id_provider = ad
krb5_realm = AD.EXAMPLE.COM
krb5_store_password_if_offline = True
ldap_id_mapping = False
ldap_schema = ad
realmd_tags = manages-system joined-with-samba
simple_allow_groups = AdminGroup, DevelGroup, OpsGroup
simple_allow_users = bob, alice, charlie
use_fully_qualified_names = False

DFS Mounts with KRB5

The /etc/fstab config is pretty self-explanatory. Note that sec=krb5 only uses Kerberos for authentication. Traffic is not necessarily private or guaranteed integrity. Check man mount.cifs for possible settings (ignore the non-Kerberos items)

//domain/root/path /mnt/path cifs noauto,nofail,rw,user,sec=krb5 0 0

However, unless your AD/DNS admins have gone out of their way to set up a bunch of normally-useless DNS records, you will not have much luck with DFS without a further tweak. This is because authentication will use the provided target hostname, which is probably not the name of the host actually responding. The secret sauce? cifs.upcall needs an additional argument, -t, which tells it to trust DNS and perform a PTR lookup, and authenticate against that hostname. Don’t do this if you don’t trust your DNS servers (trust as in authentic, not reliable).

Find the create cifs.spnego line(s) calling cifs.upcall in /etc/request-key.conf and/or /etc/request-key.d/* and add the -t argument. It should look something like this:

create  cifs.spnego    * * /usr/sbin/cifs.upcall -t %k

Bonus: this works for standalone server shares, too, not just DFS!

Things Left Undone

If there is one thing that stands out as not implemented, it’s using a DFS share for an autofs home mount. It’s certainly doable - but there’s two gotchas that come to mind. First, the user would need to have a kerberos ticket. Second, the mount command would need to be executed as the user, not as root. Fortunately, autofs has available a concept of a “program” map type that may be abused for the purpose. This is simply a map file that’s been set with executable permissions (be sure to include a shebang). The program should accept a single argument (the “key” - the first field in a normal map) and should output via STDOUT the remaining fields of a normal map. Of course, nothing stops you from doing other things in the script/program, and it does run as root…

Disclaimer/Copyright

The information, views, and opinions published on this website were done so in the author's personal capacity. The information, views, and opinions expressed in this article are the author's own and do not reflect the view of their employer, or any other entity unless explicitly stated otherwise.

All data and information provided on this site is for informational purposes only. This website and it's operators makes no representations as to accuracy, completeness, currentness, suitability, or validity of any information on this site and will not be liable for any errors, omissions, or delays in this information or any losses, injuries, or damages arising from its display or use. All information is provided on an as-is basis.

All original content on this website is, unless explicitly stated otherwise, licensed under the MIT license. Full license text is available here. Non-original content that is included on this website in whole or in part, linked, or otherwise made available remains under copyright of the original owners.