From cd1ce69f104686bbb33e049c2c4c112e78febd36 Mon Sep 17 00:00:00 2001 From: Cullum Smith Date: Wed, 25 Sep 2024 21:38:13 -0400 Subject: finish idm client stuff --- files/etc/krb5.conf.common | 29 ++++++ files/etc/login.conf.freebsd | 3 +- files/usr/local/etc/nslcd.conf.common | 14 +++ .../usr/local/etc/openldap/.krb5/config.idm_server | 2 + files/usr/local/etc/openldap/ldap.conf.common | 9 ++ files/usr/local/etc/openldap/slapd.ldif.idm_server | 2 +- files/usr/local/etc/ssh/sshd_config.freebsd | 2 +- files/usr/local/var/krb5kdc/kadm5.acl.idm_server | 4 +- lib/40-user | 111 ++++++++++++-------- lib/60-kerberos | 51 +++++++++ lib/60-ldap | 30 +++++- scripts/common/10-vars | 3 + scripts/hostclass/idm_server/10-slapd | 34 +++--- scripts/hostclass/idm_server/30-kdc | 6 -- scripts/hostclass/idm_server/90-idm | 16 ++- scripts/os/freebsd/50-idm | 114 +++++++++++++++++++++ vars/common | 5 +- vars/hostclass/freebsd_hypervisor | 1 + vars/hostclass/idm_server | 4 + vars/hostname/pkg1 | 3 + 20 files changed, 371 insertions(+), 72 deletions(-) create mode 100644 files/etc/krb5.conf.common create mode 100644 files/usr/local/etc/nslcd.conf.common create mode 100644 files/usr/local/etc/openldap/.krb5/config.idm_server create mode 100644 files/usr/local/etc/openldap/ldap.conf.common create mode 100644 lib/60-kerberos create mode 100644 scripts/os/freebsd/50-idm create mode 100644 vars/hostname/pkg1 diff --git a/files/etc/krb5.conf.common b/files/etc/krb5.conf.common new file mode 100644 index 0000000..aa2c11f --- /dev/null +++ b/files/etc/krb5.conf.common @@ -0,0 +1,29 @@ +[libdefaults] + default_realm = ${realm} + dns_lookup_kdc = true + dns_lookup_realm = false + allow_weak_crypto = false + permitted_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96 + default_client_keytab_name = /var/db/keytabs/%{euid}.keytab + # Breaks screenlockers + # verify_ap_req_nofail = true + +[appdefaults] + pam = { + minimum_uid = 1000 + ccache = FILE:/tmp/krb5cc_%u_XXXXXX + forwardable = true + ticket_lifetime = ${krb5_ticket_lifetime} + renew_lifetime = ${krb5_renew_lifetime} + } + +[realms] + ${realm} = { +$(for host in $ldap_hosts; do echo "\ + admin_server = ${host}"; done) + default_domain = ${domain} + } + +[domain_realm] + .${domain} = ${realm} + ${domain} = ${realm} diff --git a/files/etc/login.conf.freebsd b/files/etc/login.conf.freebsd index b7def42..e712b88 100644 --- a/files/etc/login.conf.freebsd +++ b/files/etc/login.conf.freebsd @@ -4,7 +4,7 @@ default:\\ :welcome=/var/run/motd:\\ :setenv=BLOCKSIZE=K:\\ :mail=/var/mail/$:\\ - :path=/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin ~/bin:\\ + :path=/sbin /bin /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin ~/bin:\\ :nologin=/var/run/nologin:\\ :cputime=unlimited:\\ :datasize=unlimited:\\ @@ -59,6 +59,7 @@ dialer:\\ # N.B. login_getpwclass(3) will use this entry for the root account, # in preference to 'default'. root:\\ + :path=/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin ~/bin:\\ :ignorenologin:\\ :memorylocked=unlimited:\\ :tc=default: diff --git a/files/usr/local/etc/nslcd.conf.common b/files/usr/local/etc/nslcd.conf.common new file mode 100644 index 0000000..6494c70 --- /dev/null +++ b/files/usr/local/etc/nslcd.conf.common @@ -0,0 +1,14 @@ +uid ${nslcd_user} +gid ${nslcd_user} + +uri ${ldap_uri} + +base ${basedn} +base passwd ${accounts_basedn} +base group ${groups_basedn} + +sasl_mech GSSAPI + +nss_min_uid ${nslcd_min_uid} +nss_initgroups_ignoreusers ALLLOCAL +nss_nested_groups yes diff --git a/files/usr/local/etc/openldap/.krb5/config.idm_server b/files/usr/local/etc/openldap/.krb5/config.idm_server new file mode 100644 index 0000000..7a92f86 --- /dev/null +++ b/files/usr/local/etc/openldap/.krb5/config.idm_server @@ -0,0 +1,2 @@ +[libdefaults] + default_keytab_name = FILE:${slapd_keytab} diff --git a/files/usr/local/etc/openldap/ldap.conf.common b/files/usr/local/etc/openldap/ldap.conf.common new file mode 100644 index 0000000..b56dc94 --- /dev/null +++ b/files/usr/local/etc/openldap/ldap.conf.common @@ -0,0 +1,9 @@ +URI ${ldap_uri} +BASE ${basedn} +USE_SASL yes +ROOTUSE_SASL yes +SASL_MECH GSSAPI +SASL_REALM ${realm} +GSSAPI_SIGN yes +GSSAPI_ENCRYPT yes +SUDOERS_BASE ${sudo_basedn} diff --git a/files/usr/local/etc/openldap/slapd.ldif.idm_server b/files/usr/local/etc/openldap/slapd.ldif.idm_server index 9dc0086..d63641e 100644 --- a/files/usr/local/etc/openldap/slapd.ldif.idm_server +++ b/files/usr/local/etc/openldap/slapd.ldif.idm_server @@ -119,7 +119,7 @@ olcAccess: {1}to dn.base="cn=Subschema" by * read olcAccess: {3}to * by dn.exact=${slapd_replicator_dn} read - by dn.exact=krbPrincipalName=${boxconf_username},${robots_basedn} manage + by dn.exact=${boxconf_dn} manage by set="[cn=${slapd_admin_role},${roles_basedn}]/member* & user" manage by * break olcAccess: {4}to dn.subtree=${sudo_basedn} diff --git a/files/usr/local/etc/ssh/sshd_config.freebsd b/files/usr/local/etc/ssh/sshd_config.freebsd index df46af6..52d9bfe 100644 --- a/files/usr/local/etc/ssh/sshd_config.freebsd +++ b/files/usr/local/etc/ssh/sshd_config.freebsd @@ -3,7 +3,7 @@ Include /etc/ssh/sshd_config.d/*.conf PermitRootLogin prohibit-password AuthorizedKeysFile .ssh/authorized_keys AuthorizedKeysCommand /usr/local/libexec/idm-ssh-authorized-keys %u -AuthorizedKeysCommandUser ${ssh_authzkeys_user} +AuthorizedKeysCommandUser ${ssh_authzkeys_username} KbdInteractiveAuthentication no PasswordAuthentication yes diff --git a/files/usr/local/var/krb5kdc/kadm5.acl.idm_server b/files/usr/local/var/krb5kdc/kadm5.acl.idm_server index c2a454b..9f7507e 100644 --- a/files/usr/local/var/krb5kdc/kadm5.acl.idm_server +++ b/files/usr/local/var/krb5kdc/kadm5.acl.idm_server @@ -1,2 +1,2 @@ -*/admin@${realm} * * -maxlife 1h -postdateable -${boxconf_username}@${realm} * * -maxlife 5m -postdateable +*/admin@${realm} * * +${boxconf_username}@${realm} * * diff --git a/lib/40-user b/lib/40-user index 305fab6..bb3fc05 100644 --- a/lib/40-user +++ b/lib/40-user @@ -29,61 +29,92 @@ add_user(){ # Add a local user if it doesn't exist. # options: mostly same as `pw useradd` # $1 = username - _bcalu_homedir_mode=700 - _bcalu_create_homedir= - _bcalu_homedir= - _bcalu_comment= - _bcalu_shell=/sbin/nologin - _bcalu_pgroup= - _bcalu_grouplist= - _bcalu_uid= - _bcalu_password= - - while getopts c:d:G:g:mM:p:s:u: _bcalu_opt; do - case $_bcalu_opt in - c) _bcalu_comment=$OPTARG ;; - d) _bcalu_homedir=$OPTARG ;; - G) _bcalu_grouplist=$OPTARG ;; - g) _bcalu_pgroup=$OPTARG ;; - M) _bcalu_homedir_mode=$OPTARG ;; - m) _bcalu_create_homedir=true ;; - p) _bcalu_password=$OPTARG ;; - s) _bcalu_shell=$OPTARG ;; - u) _bcalu_uid=$OPTARG ;; + _bcau_homedir_mode=700 + _bcau_create_homedir= + _bcau_homedir= + _bcau_comment= + _bcau_shell=/sbin/nologin + _bcau_pgroup= + _bcau_grouplist= + _bcau_uid= + _bcau_password= + + while getopts c:d:G:g:mM:p:s:u: _bcau_opt; do + case $_bcau_opt in + c) _bcau_comment=$OPTARG ;; + d) _bcau_homedir=$OPTARG ;; + G) _bcau_grouplist=$OPTARG ;; + g) _bcau_pgroup=$OPTARG ;; + M) _bcau_homedir_mode=$OPTARG ;; + m) _bcau_create_homedir=true ;; + p) _bcau_password=$OPTARG ;; + s) _bcau_shell=$OPTARG ;; + u) _bcau_uid=$OPTARG ;; esac done shift $((OPTIND - 1)) - _bcalu_username=$1 - : ${_bcalu_homedir:="/home/${_bcalu_username}"} - : ${_bcalu_comment:="${_bcalu_username} user"} + _bcau_username=$1 + : ${_bcau_homedir:="/home/${_bcau_username}"} + : ${_bcau_comment:="${_bcau_username} user"} case $BOXCONF_OS in freebsd) - if pw usershow "$_bcalu_username" > /dev/null 2>&1; then - log "local user ${_bcalu_username} already exists" + if pw usershow "$_bcau_username" > /dev/null 2>&1; then + log "local user ${_bcau_username} already exists" return 0 fi pw useradd \ - -n "$_bcalu_username" \ - -c "$_bcalu_comment" \ - -s "$_bcalu_shell" \ - -M "$_bcalu_homedir_mode" \ - -d "$_bcalu_homedir" \ - ${_bcalu_create_homedir:+-m} \ - ${_bcalu_grouplist:+-G ${_bcalu_grouplist}} \ - ${_bcalu_pgroup:+-g ${_bcalu_pgroup}} \ - ${_bcalu_uid:+-u ${_bcalu_uid}} - - log "added local user ${_bcalu_username}" + -n "$_bcau_username" \ + -c "$_bcau_comment" \ + -s "$_bcau_shell" \ + -M "$_bcau_homedir_mode" \ + -d "$_bcau_homedir" \ + ${_bcau_create_homedir:+-m} \ + ${_bcau_grouplist:+-G ${_bcau_grouplist}} \ + ${_bcau_pgroup:+-g ${_bcau_pgroup}} \ + ${_bcau_uid:+-u ${_bcau_uid}} + + log "added local user ${_bcau_username}" ;; *) - die "add_local_user unimplemented for ${BOXCONF_OS}" + die "add_user unimplemented for ${BOXCONF_OS}" ;; esac - if [ -n "${_bcalu_password}" ]; then - set_password "$_bcalu_user" "$_bcalu_password" + if [ -n "${_bcau_password}" ]; then + set_password "$_bcau_user" "$_bcau_password" fi } + +add_group(){ + # Add a local group if it doesn't exist. + # options: mostly same as `pw groupadd` + # $1 = groupname + _bcag_gid= + + while getopts g: _bcag_opt; do + case $_bcag_opt in + g) _bcag_gid=$OPTARG ;; + esac + done + shift $((OPTIND - 1)) + + _bcag_groupname=$1 + + case $BOXCONF_OS in + freebsd) + if pw groupshow "$_bcag_groupname" > /dev/null 2>&1; then + log "local group ${_bcag_groupname} already exists" + return 0 + fi + + pw groupadd -n "$_bcag_groupname" ${_bcag_gid:+-g ${_bcag_gid}} + log "added local group ${_bcag_groupname}" + ;; + *) + die "add_group unimplemented for ${BOXCONF_OS}" + ;; + esac +} diff --git a/lib/60-kerberos b/lib/60-kerberos new file mode 100644 index 0000000..a323e94 --- /dev/null +++ b/lib/60-kerberos @@ -0,0 +1,51 @@ +#!/bin/sh + +_boxconf_kadmin() { + case $BOXCONF_OS in + freebsd) _boxconf_kadmin=/usr/local/bin/kadmin ;; + *) _boxconf_kadmin=kadmin ;; + esac + + "$_boxconf_kadmin" -p "$boxconf_username" -w "$boxconf_password" "$@" +} + +_boxconf_kinit(){ + case $BOXCONF_OS in + freebsd) /usr/local/bin/kinit "$@" ;; + *) kinit "$@" ;; + esac +} + +add_principal(){ + # Create a kerberos principal, if it doesn't already exist. + # Arguments are the same as MIT kadmin' add_principal. + # Final argument must be the principal name. + eval "_kap_princ=\$$#" + _boxconf_kadmin get_principal "$_kap_princ" \ + || _boxconf_kadmin add_principal "$@" +} + +ktadd(){ + # Add a principal's keys to a keytab. + # Arguments are the same as MIT kadmin's ktadd. + _kkta_ktarg=false + _kkta_keytab=/etc/krb5.keytab + eval "_kkta_princ=\$$#" + + # Extract the keytab argument from $@. + for _kkta_arg; do + if [ "$_kkta_ktarg" = true ]; then + _kkta_keytab=$_kkta_arg + break + else + case $_kkta_arg in + -k|-keytab) _kkta_ktarg=true ;; + esac + fi + done + + # Check if we can kinit with the keytab. If not, get fresh keys. + if ! _boxconf_kinit -kt "$_kkta_keytab" -c MEMORY: "$_kkta_princ" 2>/dev/null; then + _boxconf_kadmin ktadd "$@" + fi +} diff --git a/lib/60-ldap b/lib/60-ldap index 37c0c0a..d262849 100644 --- a/lib/60-ldap +++ b/lib/60-ldap @@ -4,10 +4,16 @@ ldap_add(){ # Add a DN if it doesn't already exist. Takes ldif-formatted attributes on stdin. # $1 = the DN _ldap_add_dn=$1; shift - if ldapsearch -QLLL -s base -b "$_ldap_add_dn" dn > /dev/null 2>&1; then + if ldap_search -s base -b "$_ldap_add_dn" dn > /dev/null 2>&1; then log "${_ldap_add_dn} already exists" else - { printf 'dn: %s\n' "$_ldap_add_dn"; cat; } | ldapadd -Q "$@" + { printf 'dn: %s\n' "$_ldap_add_dn"; cat; } | { + if [ "${BOXCONF_LDAP_SASL:-}" = true ]; then + ldapadd -Q "$@" + else + ldapadd -ZZ -D "$boxconf_dn" -w "$boxconf_password" "$@" + fi + } fi } @@ -15,19 +21,33 @@ ldap_modify(){ # Modify a DN. Takes ldif-formatted attributes on stdin. # $1 = the DN _ldap_modify_dn=$1; shift - { printf 'dn: %s\nchangetype: modify\n' "$_ldap_modify_dn"; cat; } | ldapmodify -Q "$@" + { printf 'dn: %s\nchangetype: modify\n' "$_ldap_modify_dn"; cat; } | { + if [ "${BOXCONF_LDAP_SASL:-}" = true ]; then + ldapmodify -Q "$@" + else + ldapmodify -ZZ -D "$boxconf_dn" -w "$boxconf_password" "$@" + fi + } } ldap_delete(){ # Delete a DN. # $1 = the DN - ldapdelete -Q "$@" + if [ "${BOXCONF_LDAP_SASL:-}" = true ]; then + ldapdelete -Q "$@" + else + ldapdelete -ZZ -D "$boxconf_dn" -w "$boxconf_password" "$@" + fi } ldap_search(){ # Perform an LDAP search # $1..$N = same as ldapsearch. - ldapsearch -QLLL "$@" + if [ "${BOXCONF_LDAP_SASL:-}" = true ]; then + ldapsearch -QLLL "$@" + else + ldapsearch -o ldif_wrap=no -LLLZZ -D "$boxconf_dn" -w "$boxconf_password" "$@" + fi } ldap_add_attribute(){ diff --git a/scripts/common/10-vars b/scripts/common/10-vars index 2f0dc9d..3b01cd2 100644 --- a/scripts/common/10-vars +++ b/scripts/common/10-vars @@ -5,6 +5,7 @@ if [ "${idm_bootstrap:-}" = true ]; then pkg_host=$pkg_host_ip else : ${resolvers:="$(echo "$idm_server_list" | awk '{print $3}')"} + : ${pkg_host:="pkg.${domain}"} fi idm_hostnames=$(echo "$idm_server_list" | awk '{print $1}') @@ -32,3 +33,5 @@ dns_basedn="ou=dns,${basedn}" kdc_basedn="cn=kdc,${basedn}" mail_basedn="ou=mail,${basedn}" mail_domains_basedn="ou=domains,${mail_basedn}" + +boxconf_dn="krbPrincipalName=${boxconf_username}@${realm},${robots_basedn}" diff --git a/scripts/hostclass/idm_server/10-slapd b/scripts/hostclass/idm_server/10-slapd index 204c405..83cdbb6 100644 --- a/scripts/hostclass/idm_server/10-slapd +++ b/scripts/hostclass/idm_server/10-slapd @@ -37,6 +37,17 @@ pkg install -y \ # Create ZFS dataset for OpenLDAP DB. create_dataset -o "mountpoint=${slapd_data_dir}" "${state_dataset}/openldap-data" +# To prevent a circular dependency in poudriere, we have to make a special "set" +# of packages for the IDM hosts in which cyrus-sasl-gssapi is built with the +# Heimdal libraries in base, rather than MIT. +# +# Heimdal does not support the KRB5_KTNAME environment variable with slapd. +# However, you *can* specify a keytab by creating a ~/.krb5/config file in +# the slapd user's home directory. +pw user mod "$slapd_user" -d "$slapd_conf_dir" +install_directory -m 0755 "${slapd_conf_dir}/.krb5" +install_template -m 0644 "${slapd_conf_dir}/.krb5/config" + # Copy TLS certificate for LDAP server. install_certificate -o "$slapd_user" -g "$slapd_user" slapd "$slapd_tls_cert" install_certificate_key -o "$slapd_user" -g "$slapd_user" slapd "$slapd_tls_key" @@ -69,18 +80,6 @@ if [ ! -d "${slapd_conf_dir}/slapd.d" ]; then chown -R "${slapd_user}:${slapd_user}" "${slapd_conf_dir}/slapd.d" fi -# Enable OpenLDAP in /etc/rc.conf, and start it. -# Note: whatever LDAP IP you specified in $slapd_server_list must be present in -# the `-h` argument to slapd. That's how slapd figures out its own server ID. -sysrc -v \ - slapd_enable=YES \ - slapd_cn_config=YES \ - slapd_flags="-h '${slapd_ldapi_uri}/ ldap://0.0.0.0/ ldaps://0.0.0.0/ ldaps://${BOXCONF_DEFAULT_IPV4}/'" \ - slapd_sockets="$slapd_socket" \ - slapd_krb5_ktname="$slapd_keytab" - -service slapd restart - # Copy the LDAP client configs. install_template -m 0644 "${slapd_conf_dir}/ldap.conf" @@ -90,11 +89,20 @@ install_template -m 0644 /usr/local/lib/sasl2/slapd.conf # Allow slapd to read the saslauthd socket. install_directory -m 0750 -o "$saslauthd_user" -g "$slapd_user" "$saslauthd_runtime_dir" -# Enable and start saslauthd. +# Enable OpenLDAP in /etc/rc.conf, and start it. +# Note: whatever LDAP IP you specified in $slapd_server_list must be present in +# the `-h` argument to slapd. That's how slapd figures out its own server ID. sysrc -v \ + slapd_enable=YES \ + slapd_cn_config=YES \ + slapd_flags="-h '${slapd_ldapi_uri}/ ldap://0.0.0.0/ ldaps://0.0.0.0/ ldaps://${BOXCONF_DEFAULT_IPV4}/'" \ + slapd_sockets="$slapd_socket" \ + slapd_krb5_ktname="$slapd_keytab" \ saslauthd_flags='-a kerberos5' \ saslauthd_enable=YES + service saslauthd restart +service slapd restart # Create directory tree. if is_primary_server; then diff --git a/scripts/hostclass/idm_server/30-kdc b/scripts/hostclass/idm_server/30-kdc index abe040a..9347ed0 100644 --- a/scripts/hostclass/idm_server/30-kdc +++ b/scripts/hostclass/idm_server/30-kdc @@ -35,9 +35,3 @@ sysrc -v \ service kdc restart service kadmind restart - -# Create the boxconf administrative user. -if is_primary_server; then - kadmin.local get_principal -terse "$boxconf_username" \ - || kadmin.local add_principal -pw "$boxconf_password" -x "containerdn=${robots_basedn}" "$boxconf_username" -fi diff --git a/scripts/hostclass/idm_server/90-idm b/scripts/hostclass/idm_server/90-idm index 0a28491..adfdf36 100644 --- a/scripts/hostclass/idm_server/90-idm +++ b/scripts/hostclass/idm_server/90-idm @@ -40,11 +40,11 @@ associatedDomain: ${rdns} EOF # Create host principal. -kadmin.local get_principal -terse "host/${fqdn}" \ +kadmin.local get_principal "host/${fqdn}" \ || kadmin.local add_principal -nokey -x "dn=cn=${BOXCONF_HOSTNAME},${hosts_basedn}" "host/${fqdn}" # Create ldap service principal. -kadmin.local get_principal -terse "ldap/${fqdn}" \ +kadmin.local get_principal "ldap/${fqdn}" \ || kadmin.local add_principal -nokey -x "containerdn=${services_basedn}" "ldap/${fqdn}" # Create state dataset to persist keytabs across OS rebuilds. @@ -89,3 +89,15 @@ ln -snfs "${slapd_conf_dir}/ldap.conf" /usr/local/etc/ldap.conf install_file -m 0555 \ /usr/local/libexec/idm-ssh-known-hosts \ /usr/local/libexec/idm-ssh-authorized-keys + +# Create the boxconf administrative user. +if is_primary_server && ! ldap_dn_exists "$boxconf_dn"; then + ldap_add "$boxconf_dn" <