Archive

Archive for March, 2016

Integrating FreeBSD w/ FreeIPA/SSSD

March 24, 2016 11 comments

 

It is February 2018 and almost 2 years have passed since this post was originally published. It has become this blog’s most popular post. Hopefully it has been insightful and useful to those who have read it. Since the initial phases of this project, there have been some new experiences and lessons learned. Therefore, I thought it was a good idea to update this post integrating those experiences. The updated post also integrates solutions to the problem described in the first comment. Please enjoy!

This post is best viewed within a desktop environment due to the expression of config files containing lines longer than smaller displays. Therefore, it is not particularly well suited for mobile browsing though reader view may alleviate some formatting issues.

 

Integrating FreeBSD w/ FreeIPA/SSSD

 

One of my more recent projects was to integrate FreeBSD into a Kerberos-secured authentication and authorization system based on the FreeIPA architecture. This post is an aggregate HOWTO with information sourced from a couple public (and one private) websites and a mailing list in addition to my own personal experience. The architecture and infrastructure of the FreeIPA system are beyond the scope of this post and will not be covered here. The focus is installing and configuring a client to use the FreeIPA-based authentication and authorization system.

 

Prerequisites and Requirements

  1. Client must have A & PTR records in DNS
  2. Client hostname must be set to it’s FQDN
  3. Client host table must include a matching FQDN/IP address record
  4. Configured and running NTP
  5. Client should have a so-called break-glass UNIX account
  6. Binary package repository w/ packages supporting FreeIPA
  7. A Kerberos keytab for the client

Failure to meet requirements 1-3 generally introduces latency from the perspective of the client particularly when logging in remotely with ssh for example. NTP should also be configured and running.

The break-glass account is a local, UNIX-based account on the client filtered via SSSD policy/config.  It will not use SSSD for authentication and it can be arbitrary in nature, so long as it is filtered.  This is useful when remote access is lost due to service outage or similar.  It is also useful to open a session to this user while this process is executed.

Client services won’t start and the client will fail authenticate and authorization against FreeIPA infrastructure without the proper software installed. Custom options are required to support FreeIPA. This can be done via Ports or packages; Described below is the latter.

Finally, the host keytab file must be generated in the FreeIPA infrastructure and securely copied to the client. The keytab facilitates system authentication via Kerberos.

 

Overview

The goal is to integrate FreeBSD into the FreeIPA architecture to provide services to ssh and sudo.  No other authentication or authorization mechanisms were tested. Additionally, this post is constructed from notes and memory.  Hopefully nothing has been missed and everything is accurate, but it is possible this is not the case.  Help me keep this accurate, if necessary.

There are two high level tasks to be completed; Generate the binary package repository with FreeIPA-enabled packages and client installation/configuration. The binary package repository only needs to be done once. Ongoing maintenance of binary repositories is necessary to make newer versions available when necessary.

 

Building The Binary Package Repository

Execute this process to build a binary package repository containing the necessary packages w/ custom options enabled. It is assumed a pre-existing Poudriere build and distribution environment is available.

$jail_id is the Poudriere jail ID within which the repo is to be built

$portstree_id is the Poudrere Ports tree ID on which to execute the build

$ cat >> $jail_id-make.conf <<-EOF
WITH_GSSAPI="YES"
WANT_OPENLDAP_SASL="YES"
KRB5_IMPL="mit"
EOF
$ cat > pkg_list <<EOF
security/cyrus-sasl2-gssapi
security/sssd
security/sudo
security/openssh-portable
security/pam_mkhomedir
EOF
$ poudriere options -c -n -p $portstree_id security/sssd security/sudo security/openssh-portable security/cyrus-sasl-gssapi
$ poudriere bulk -f pkg_list -j $jail_id -p $portstree_id

The kerberos implementation deployed in IPA dictates the kerberos implementation software is built to support. MIT kerberos is the predominant implementation in use within the US. The above configuration supports MIT kerberos.

Step 3 presents dialog boxes populated with various radio options. A null radio is a deselected option while options denoted by an “X” are selected. Configure options for each of the packages as described here:

security/sssd: Enable SMB (Builds and installs the IPA provider)
security/sudo: Enable SSSD backend
security/openssh-portable: Enable MIT Kerberos and KERB_GSSAPI

KERB_GSSAPI can cause the openssh-portable port to be marked as BROKEN because it’s dependent on an unmaintained patch, which has been updated occasionally by the Debian project. This generally appears to be the case when the port is updated to a newer version.

 

Installing/Configuring The Client

FreeIPA includes a client installer for Linux that is unavailable for FreeBSD. Rumors suggest porting it to FreeBSD is non-trivial. It was not done for this project. It would simplify this process significantly however.

The procedure below includes commands executed on the command line as well as descriptions of operations needed to be completed such as editing or creating a file. Several commands and/or files include hostnames inherently describing a dependency on DNS. Open a standby shell in order to maintain access to the system during this operation as access to the system can be halted in executing it.

$cblrserver is the hostname or IP of the  server from which to grab content.  This is simply an HTTP accessible resource.

$ipaserver is the hostname or IP of the IPA server with which to communicate

$domain is the domain of the target host

$hostname is the fully qualified hostname

1) Configure the binary package repository and install the required packages:

$ cat > /etc/pkg/ipa.conf <<-EOF
ipa: {
  url: "http://$cblrserver/cobbler/repo_mirror/freebsd-10_3-amd64-pkgs-ipa",
  mirror_type: "none",
  enabled: yes
}
EOF
$ pkg update -f
$ mkdir -p /etc/ipa /usr/compat/linux/proc /var/log/sssd /var/run/sss/private /var/db/sss
$ pkg install -y -r ipa cyrus-sasl-gssapi sssd sudo openssh-portable pam_mkhomedir

2) Configure Kerberos and SSSD

$ cat /etc/krb5.conf
[libdefaults]
  default_realm = EXAMPLE.COM
  default_keytab_name = FILE:/etc/krb5.keytab
  default_tkt_enctypes = aes256-cts des-cbc-crc aes128-cts arcfour-hmac
  default_tgs_enctypes = aes256-cts des-cbc-crc aes128-cts arcfour-hmac
  dns_lookup_realm = false
  dns_lookup_kdc = false
  rdns = false
  ticket_lifetime = 24h
  forwardable = yes

[realms]
  EXAMPLE.COM = {
    kdc = $ipaserver:88
    master_kdc = $ipaserver:88
    admin_server = $ipaserver:749
    default_domain = $domain
    pkinit_anchors = FILE:/etc/ipa/ca.crt
  }

[domain_realm]
  .example.com = EXAMPLE.COM

[logging]
  kdc = FILE:/var/log/krb5/krb5kdc.log
  admin_server = FILE:/var/log/krb5/kadmin.log
  kadmin_local = FILE:/var/log/krb5/kadmin_local.log
  default = FILE:/var/log/krb5/krb5lib.log

[edit]
$
$
$ cat /usr/local/etc/sssd/sssd.conf
[domain/$domain]
#debug_level = 9
cache_credentials = True
krb5_store_password_if_offline = True
krb5_realm = EXAMPLE.COM
ipa_domain = $domain
id_provider = ipa
auth_provider = ipa
access_provider = ipa
ipa_hostname = $hostname
chpass_provider = ipa
ipa_server = _srv_, $ipaserver
ldap_tls_cacert = /etc/ipa/ca.crt
krb5_keytab = /etc/krb5.keytab

[sssd]
services = nss, pam, ssh, sudo
config_file_version = 2
domains = $domain

[nss]
filter_users = root,smash
homedir_substring = /home

[pam]

[sudo]

[ssh]
$
$

chpass_provider is irrelevant to FreeBSD because the IPA tools are required and currently unavailable to FreeBSD. This service can be provided to users on separate infrastructure using Linux instead. Also note filter_users includes root in addition to the break-glass account.

3) Configure NSS similarly to the below:

$ egrep "sss|netgroup" /etc/nsswitch.conf
group: files sss
passwd: files sss
sudoers: files sss
netgroup: files

NSS interrogates the data source in the order in which it’s listed. The above policy first validates authentication/authorization for local UNIX accounts before SSSD. NSS validates via SSSD when the invoking user is not a local UNIX user.

4) Configure and mount the linproc filesystem

# echo "linproc /usr/compat/linux/proc linprocfs rw 0 0" >> /etc/fstab
# mount -a

5) Configure rc.conf and domainname

$ cat /etc/rc.conf
rpcbind_enable=“YES”
sshd_enable=“NO”
openssh_enable="YES"
ntpd_enable="YES"
nisdomainname=“example.com”
sssd_enable="YES"
$ sudo domainname example.com

6) Configure openssh-portable similarly to:

$ grep GSSAPI /usr/local/etc/ssh/sshd_config
GSSAPIAuthentication yes
GSSAPICleanupCredentials yes

7) Download the CA certificate required by kerberos

$ wget -O /etc/ipa/ca.crt http://$ipaserver/$uri/ca.crt

8) Configure PAM according to these configs:

$ cat /etc/pam.d/sshd
# auth
auth sufficient pam_opie.so no_warn no_fake_prompts
auth requisite pam_opieaccess.so no_warn allow_local
auth sufficient /usr/local/lib/pam_sss.so use_first_pass ignore_unknown_user
auth required pam_unix.so no_warn try_first_pass

# account
account required pam_nologin.so
account sufficient /usr/local/lib/pam_sss.so ignore_unknown_user
account required pam_login_access.so
account required pam_unix.so

# session
session required pam_lastlog.so no_fail
session required /usr/local/lib/pam_mkhomedir.so

# password
password sufficient /usr/local/lib/pam_sss.so
password required pam_unix.so no_warn try_first_pass
$
$
$ cat /etc/pam.d/system
# auth
auth sufficient pam_opie.so no_warn no_fake_prompts
auth requisite pam_opieaccess.so no_warn allow_local
auth sufficient /usr/local/lib/pam_sss.so use_first_pass
auth required pam_unix.so no_warn try_first_pass nullok

# account
account sufficient /usr/local/lib/pam_sss.so ignore_unknown_user
account required pam_login_access.so
account required pam_unix.so

# session
session required pam_lastlog.so no_fail
session required /usr/local/lib/pam_mkhomedir.so

# password
password sufficient /usr/local/lib/pam_sss.so use_first_pass
password required pam_unix.so no_warn try_first_pass

It may be desirable to symlink /etc/pam.d/sudo to /etc/pam.d/system or /etc/pam.d/su depending on the desired behavior.

9) Create /etc/netgroup with the following script. Set this script to run via cron to rebuild the file periodically.

This script is ported from one of the references to update /etc/netgroup locally on FreeBSD hosts. LDAPSRV is the hostname or IP of the LDAP server to query. This is generally the same as the IPA servers. Executing this script via cron will keep the file updated.

Note the patch below may be useful if /etc/netgroup tends to be large file. If LDAP returns netgroups in response to a query, sudo calls innetgr(), which subsequently loads and iterates the file to determine if the host belongs to a netgroup in which the invoking user is a member. NSS’ netgroup database provides the data source for innetgr() which supports neither sss nor ldap. The time required to iterate the file is proportional to it’s size and can introduce severe performance degradation. The patch below — when applied to the script — ensures only relevant entries exist thus reducing the file’s size. The patch assumes the script is called mknetgroup.sh.

FreeBSD PR 226119 is open as a feature request to implement a LDAP data source for NSS’ netgroup database.

diff --git a/mknetgroup.sh b/mknetgroup.sh
index 48eda74..5ba4e6d 100755
--- a/mknetgroup.sh
+++ b/mknetgroup.sh
@@ -7,6 +7,7 @@

 PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
 LDAPSRV=__IPASERVER
+HOSTNAME=$(hostname -s)
 export PATH

 progrname=$(basename $0)
@@ -46,7 +47,9 @@ ldapsearch -LLLx -H ldap://${LDAPSRV} \
     elif [ "$key" = "nisNetgroupTriple" ]; then
         host=${value%%,cn*}
         host=${host##fqdn=}
-        members="$members $host"
+        case $host in
+            *${HOSTNAME}*) members="$members $host" ;;
+        esac
     fi
 done
#!/bin/sh
#
# Construct a netgroup file from LDAP hostgroup definitions.
# This is a hack for FreeBSD IPA clients because they can't get netgroup
# data through LDAP or sssd backends (lacking nsswitch/nsdb support).
#

PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
LDAPSRV=ipa.example.com
export PATH

progname=$(basename $0)
tmpf=$(mktemp)

trap "rm -f $tmpf" EXIT

ldapsearch -LLLx -H ldap://${LDAPSRV} \
  -b 'dc=example,dc=com' \
  '(objectClass=nisNetgroup)' cn nisNetgroupTriple \
| while read line; do
# new line between records; this means a record ended.
if [ "$line" = "" ]; then
  # output netgroup line if we have members.
  if [ "$members" != "" ]; then
    echo "$groupname \\" >>$tmpf
    for host in $members; do
      echo "$host \\" >>$tmpf
    done
    echo "" >>$tmpf
  fi

# reset data
groupname=""
members=""
continue
fi

# parse "key: value" from LDAP
key=${line%%: *}
value=${line##*: }

if [ "$key" = "dn" ]; then
  continue
elif [ "$key" = "cn" ]; then
  groupname=$value
elif [ "$key" = "$netgroupobj" ]; then
  host=${value%%,cn*}
  host=${host##fqdn=}
  members="$members $host"
fi
done

if [ ! -s "$tmpf" ]; then
  echo "$progname: refusing to install an empty file, bailing" >&2
  exit 1
fi

install -m 0644 -o root -g wheel $tmpf /etc/netgroup
rc=$?
if [ $rc -ne 0 ]; then
  echo "$progname: error installing /etc/netgroup (rc = $rc)" >&2
  exit 2
fi

exit 0

10) Restart services or reboot

$ sudo service sssd start
$ sudo service sshd stop && sudo service openssh start

Upon completion, the target host has been configured to communicate with the authentication and authorization frameworks. Reboot is not required, but certainly recommended.

 

References

  1. The FreeBSD Forums
  2. Opposite Blog
  3. FreeIPA-USERS Mailing List

Disclaimer

Data and information described on this blog are for informational purposes only. The author of this blog provides no warranty/guarantee, expressed or implied, that this data and information will function as described here. Readers are expected to exercise due diligence when researching, developing, and deploying techniques and methods for use within their environments.

Comments posted are the explicit opinions of the comment poster themselves and does not necessarily reflect the views and opinions of the author of this blog.

 

Categories: FreeBSD