SmartCard Authentication

01 Jul 2014

I had the pleasure recently of having to figure out how to get SmartCard(CaC) authentication up and running on our Red Hat workstations. The workstations are mostly STIG’d already, but now they required us to get CaC authentication up and runnning. I was happy really to do this as it removes the rediculous additional password that I have to remember and also do not have to try and keep 20 passwd/shadow/group files in sync.

Hopefully this will help somebody configure this as well. There are quite a few steps but once you get it configured at the end I show you how to save the config which you can very very easily deploy to other workstations. It takes us about 5 minutes now to do a workstation from scratch!

First, what we wanted out of this was as follows:

  • CaC authentication
  • Kerberos ticket issued through login
  • Access to any CIFS shares via kerberos ticket
  • SSH access via kerberos ticket to any servers we may have

The process was quite bumpy figuring out all the certificate issues and what not, but the end result was a single shell script that is easily run by anybody that can join a computer to the domain.

Really, in the long run, authconfig is your best friend. It does a pretty good job of getting the basics going, then you just have to modify the /etc/krb5.conf and /etc/samba/smb.conf files.

First define some variables that we can use

yourdomain='your.example.com'
adminuser='domain.admin'
computerou='some/ou/path'

Now you can run these little gems to setup the rest of the variables you need

adinfo=$(dig _kerberos.${yourdomain} | awk '/SOA/ {printf("%s %s\n",$1,$5)}')
adserver=${adinfo#*. }
adserver=${adserver%%.}
domain=${adinfo%*. *}
workgroup=${domain%%.*}
echo "--- Info ---" 
echo "Server: $adserver" 
echo "Domain: $domain" 
echo "Workgroup: $workgroup" 

Update and Install some dependencies

Most of these packages are contained inside of package groups, but apparently

our local repo didn’t sync the group lists. So you get all the package names instead

yum update -y
yum install -y ccid coolkey esc gdm-plugin-smartcard pam_pkcs11 \
opencryptoki krb5-pkinit-openssl pam_krb5 python-ldap ipa-client \
oddjob-mkhomedir sssd ypbind certmonger hesinfo krb5-appl-clients \
krb5-pkinit-openssl krb5-workstation ldapjdk libsss_sudo nscd \
nss-pam-ldapd openldap-clients pam_krb5 pam_ldap samba-winbind \
cifs-utils gdm gdm-plugin-smartcard plymouth-gdm-hooks gdm-libs

Have authconfig setup your config files

authconfig --enablesmartcard --enablekrb5 --enablewinbind \
--enablewinbindauth --krb5kdc=${adserver} \
--krb5adminserver=${adserver} --krb5realm ${domain} \
--passalgo=sha512 --winbindseparator='+' \
--winbindtemplateshell='/bin/bash' --smbsecurity=ads \
--smbservers=${adserver} --smbworkgroup ${workgroup} \
--smbrealm=${domain} --smartcardmodule='coolkey' \
--smartcardaction=0 --enablemkhomedir --update

Tweak /etc/samba/smb.conf

If you have multiple password servers(aka AD servers) put them all after password server speparated by spaces.

I’ve removed the information for my workplace so just make sure your’s is correct.

DOMAINNETBIOSNAME is the short name for your domain and should probably be in uppercase

[global]
#--authconfig--start-line--

# Generated by authconfig on ......
# DO NOT EDIT THIS SECTION (delimited by --start-line--/--end-line--)
# Any modification may be deleted or altered by authconfig in future

   workgroup = DOMAINNETBIOSNAME
   password server = adserver1.example.com adserver2.example.com              
   realm = EXAMPLE.COM
   security = ads
   winbind separator = +
   template shell = /bin/bash
   winbind use default domain = yes
   winbind offline logon = false
    
   idmap config DOMAINNETBIOSNAME: backend = rid
   idmap config DOMAINNETBIOSNAME: range = 10000000-19999999
   winbind enum users = no 
   winbind enum groups = no
   kerberos method = system keytab
   dedicated keytab file = /etc/krb5.keytab
   idmap uid = 10000-19999
   idmap gid = 10000-19999
   template homedir = /home/%D/%U
   template shell = /bin/bash
   winbind use default domain = yes

Tweak /etc/krb5.conf

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

[libdefaults]
 default_realm = EXAMPLE.COM
 dns_lookup_realm = false
 dns_lookup_kdc = false
 ticket_lifetime = 24h
 renew_lifetime = 7d
 forwardable = true
 allow_weak_crypto = 1
 default_keytab_name = FILE:/etc/krb5.keytab

[realms]
 EXAMPLE.COM = {
  kdc = adserver1.example.com
  kdc = adserver2.example.com
  admin_server = adserver1.example.com
# ---------- PKINIT CAC Section ----------
  pkinit_kdc_hostname = ADSERVER1.example.com
  #preferred_preauth_types = 30,31
  pkinit_identities = PKCS11:/usr/lib64/pkcs11/libcoolkeypk11.so
  # This specifies the certificate on your CaC 
  #that has your Microsoft Principal Name in form of EPID@mil
  pkinit_cert_match = <SAN>^[0-9]{10}@mil$

  pkinit_anchors = FILE:/etc/pki/CA/certs/adcerts.pem
  pkinit_eku_checking = kpServerAuth
  # ---------- END PKINIT ------------------
 }

[domain_realm]
 .example.com = EXAMPLE.COM 
 example.com = EXAMPLE.COM
 
[appdefaults]
    autologin = true
    forward = true
    encrypt = true
    forwardable = true
    krb5_run_aklog = false
    default_lifetime = 0d 10h 0m 0s

Setup ntp to sync from the AD server

Not really sure if this is the correct ntp config, but it seems to be working

sed -i 's/^server/#server/' /etc/ntp.conf
echo "server $adserver" >> /etc/ntp.conf 
service ntpd restart

Setup AD certificates

setup_certs

Here is a python script I created that should pull all necessary certificates from your AD server and import them where needed.

I really don’t know enough about how the internals of AD work with regards to certificates, but I do know you need the certificates from AD so that you can authenticate your SmartCard as well as log in and such.

I realized when I downloaded this script there was a specific part to our setup, but you should be able to change it by editing the script and changing the

‘cn=Configuration,dc=example,dc=com’

to your domain

Then you should be able to just run

python setup_certs.py ${adminuser} ${adserver}

Setup your /etc/pam_pkcs11/cn_map file

There are quite a few different mappers that you can look at using, but we ended up just using the cn_map.

You just put in that file all SmartCard users and their AD user name one per line

For instance:

LASTNAME.FIRSTNAME.MIDDLE.EPID -> firstname.lastname

You can use pklogin_finder debug to get the LASTNAME.FIRSTNAME.MIDDLE.EPID stuff. I might consider making a plugin at some point to do this stuff automagically, but for now, cn_map it is.

Ensure permissions are correct

chmod 744 /etc/pki/CA/certs
chmod 444 /etc/pki/CA/certs/*
chmod 644 /etc/krb5*

Edit /etc/hosts

Change the top two lines that begin with 127.0.0.1 and ::1 such that your fully qualified domain name is the first entry on that line. So something like this.

If you don’t do this, your computer will join to AD with the dnsHostname of localhost.localdomain or something funky and kerberos will be cranky for some things

127.0.0.1 myworkstation.example.com myworkstation localhost....
::1 myworkstation.example.com myworkstation  localhost....

Restart services

chkconfig winbind on
service winbind restart

Join the computer to the domain

net ads join -S $adserver -U $adminuser createcomputer="${computerou}" 

If your AD server does not handle dynamic DNS updates then this command will likely say that it joined you to the domain, but the DNS updated failed which is fine.

Recently, the command has hung after joining to the domain and I have had to CTRL+C it, but it still joins the account to the domain so all is good in the world.

Initialize machine kerberos ticket

net ads kerberos kinit -P

Setup kerberos keytab stuff on bootup

This is a bit ugly and really it is only needed if you are doing CIFS mounts that require kerberos and using the sec=kerberos,multiuser options.

I suspect there is a more elegant solution(like doing something to tell CIFS to ensure kerberos keytab before mounting) but time is money and this works too

cat <<EOF >> /etc/rc.local
net ads kerberos kinit -P
mount -a
EOF

Save your authconfig!!!!

If you are sure everything is working, you can save all the config

authconfig --savebackup=/root/workingauthconfig
tar czvf certs_config.tgz \
/etc/pki/CA/certs \
/etc/pki/nssdb \
/etc/ntp.conf \
/etc/pam_pkcs11/cn_map

And you can restore the config(maybe to deploy to other workstations…)

tar xzvf certs_config.tgz
authconfig --restorebackup=/root/workingauthconfig

When all the planets are not aligned and things don’t work

This probably will not work the first time you try. Lots of pieces in motion. Here are some things that will hopefully help you identify what piece is not working

Account lockout and SmartCard PIN lock

If you put your pin in with this command and it gives an error, it likely means your PIN is locked

pklogin_finder debug

This command will check to see if an account is locked in AD

[[ "$(net ads search '(sAMAccountName=tyghe.vallard)' lockoutTime -P)" =~ "lockoutTime: 0" ]] \
&& echo "Account is not locked" || echo "Account is locked"

Test your AD connection and such

net ads testjoin

Should return: Join is OK

Gives you information about the machine’s account in AD you can use to debug with

net ads status -P

Make sure you get a passwd line back from wbinfo

wbinfo -i ad.user.name

This should return a passwd entry. The only time it seemed to fail was when we didn’t have /etc/samba/smb.conf setup correctly for rid uid/gid mapping

If you fix the /etc/samba/smb.conf file here is the command to flush your stuff

net cache flush; service winbind restart;

Test your kinit/kerberos/certificates

Run this command and put in the pin for your CaC

export KRB5_TRACE=/dev/stdout
kinit

We were getting this error

Preauth module pkinit (17) (flags=1) returned: -1765328308/KDC name mismatc

This error indicates that the pkinit_kdc_hostname you put in the /etc/krb5.conf file does not match the certificate you imported from AD. Most likely, the host name needs to be uppercase.

Verify your connection to AD’s ldap works(really if the setup_certs.py script works this shouldn’t be a problem)

openssl s_client -connect adserver1.example.com:636 \
-CAfile /etc/pki/CA/certs/adcerts.pem 2>&1 | grep 'Verify return code'
«« Previous Post Next Post »»