LDAP+SSSD 部署

选择 openldap 与 sssd 的理由

最近有在不同 Linux 节点(非集群)上统一管理用户的需求,这个时候开始觉得 Windows AD 确实很方便。其实 Linux 机器也可以加入 AD 域, samba 也有相关的功能,暂时还没有探索多少。

NIS 也是一个轻量的目录服务协议,但配置起来相对死板麻烦。值得注意的是, Ubuntu 22.04 中将 nis 版本更新到了 4 ,配置方式和之前有很大不同,在配置时踩了很多坑。供参考的资料大多也不适用于现在的情况,加上不太能搞懂组件间的联系等,最后放弃了这条道路。

FreeIPA 是 RH 牵头搞的、全家桶型的身份管理系统。它不仅内置了 bind 来配置 DNS ,有 acme server 来签发证书,支持 kerberos 认证,还有一个 Web 界面来调整设置,非常方便。可以说自己配一套配置到最后,也是需要配好这一坨东西。但由于 bind 的版本依赖问题, Ubuntu 竟然从 20.04 开始就没打包 freeipa-server 这个包了。 Docker 版本也试了下,第一次启动很慢,运行时占用资源也稍多,启动中间还出了很多错误,暂时也没有精力去排查,更没有换 rh 系发行版的打算,只能搁置了。另外 Docker 封装里有 systemd ,这也是个小问题。

由于需求其实比较简单,服务器区域准备设置防火墙并没有公开服务,也没有必要去搞得很复杂。所以最后决定自己搭一套 LDAP 。

最开始尝试的是 ApacheDS ,这是一个用 Java 写的 LDAP 和 Kerberos 服务器。 Ubuntu 里有 ApacheDS 这个包,和 Debian 上游的完全一样,官方源里面这个包可谓非常之坑,我不懂 Java ,它对我来说就更坑了:一个是假如存密码用了 CRYPT 系列的编码,那么 ApacheDS 就完全登录不了了,提示 NoClassDefFoundError 。看了下是缺少 libcommons-codec-java 这个包的库,但它已经作为依赖被安装了。在我手动把这个库的 jar 复制到 ApacheDS 所在的 lib 目录后,这个问题解决了。但当设置 TLS 的时候,又出现了 No Cipher Suites in Common 的问题,搜了下网上的意思,都是说应该是别的地方出问题,这下我没有魔改的思路了。但这是发行版打包的问题,因为如果安装并使用的是 ApacheDS 自己打包的 deb ,那就完全不会出现这些问题。他们自己打包的 deb 没有 systemd 整合,其实这还是小问题。关键是他没有源,需要三天两头去检查升级。发行版都有 security 更新,直接更新就好了,这自己去检查可太累了。再加上之前 log4j 爆出问题,谁知道这么一坨没有时常更新的服务什么时候会炸。浅瞄一眼没有合适的 Docker 封装,算了算了。

最后尝试了最经典的 openldap ,还是它好用啊,一路下来基本没出现过啥问题,妙啊。

SSSD 可以缓存登录凭据,这样认证服务器挂了(在我的场景下)问题也不算还大,暂时没看 pam-ldap 是否有同样的选项。据说 sssd 还有其他高级功能,没研究。

openldap server 的配置

设置 hostname 和 FQDN

首先设置好 hostname 和 FQDN 。因为 sssd 需要加密,加密需要证书。

$ sudo hostnamectl hostname ldap
$ sudo vim /etc/hosts
...
127.0.0.1 localhost.localdomain localhost
127.0.0.1 ldap.example.com      ldap
...

检查设置:

$ hostname
ldap
$ hostname -f
ldap.example.com

设置 openldap server

$ sudo apt update
$ sudo apt install slapd ldap-utils

安装中可以跳过 openldap 服务器设置,安装完后再进行:

$ sudo dpkg-reconfigure slapd

创建证书

如果有证书就可以跳过自签证书的步骤,直接放好证书给服务用就好了。

$ sudo apt install gnutls-bin ssl-cert

创建证书模板:

$ sudo mkdir /etc/ssl/templates

/etc/ssl/templates/ca_server.conf:

cn = LDAP Service CA
ca
cert_signing_key
expiration_days = 3650

/etc/ssl/templates/ldap_server.conf:

organization = "Example Organization"
cn = ldap.example.com
tls_www_server
encryption_key
signing_key
expiration_days = 3650

创建 CA 私钥:

$ sudo certtool -p --outfile /etc/ssl/private/ca_server.key

创建 CA 证书:

$ sudo certtool -s --load-privkey /etc/ssl/private/ca_server.key --template /etc/ssl/templates/ca_server.conf --outfile /etc/ssl/certs/ca_server.pem

域名的私钥:

$ sudo certtool -p --sec-param high --outfile /etc/ssl/private/ldap_server.key

域名的证书:

$ sudo certtool -c --load-privkey /etc/ssl/private/ldap_server.key --load-ca-certificate /etc/ssl/certs/ca_server.pem --load-ca-privkey /etc/ssl/private/ca_server.key --template /etc/ssl/templates/ldap_server.conf --outfile /etc/ssl/certs/ldap_server.pem

让服务有权限访问这个证书:

$ sudo mkdir /etc/ssl/slapd
$ sudo cp /etc/ssl/private/ldap_server.key /etc/ssl/slapd/
$ sudo chown -R openldap /etc/ssl/slapd/
$ sudo chmod 710 /etc/ssl/slapd

配置 openldap server 使用证书

创建 addcerts.ldif

dn: cn=config
changetype: modify
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ssl/certs/ca_server.pem
-
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ssl/certs/ldap_server.pem
-
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ssl/private/ldap_server.key

顺序必须是: CA 证书、服务证书、服务私钥,不然报实现自定义错误。证书权限忘调整也报错。

应用更改:

$ sudo ldapmodify -H ldapi:// -Y EXTERNAL -f addcerts.ldif
$ sudo systemctl restart slapd

openldap 客户机配置

如果是自签发证书,需要信任自己的 CA 证书:

$ sudo cp /etc/ssl/certs/ca_server.pem /etc/ldap/ca_certs.pem

编辑 /etc/ldap/ldap.conf ,调整 CA 证书文件:

TLS_CACERT /etc/ldap/ca_certs.pem

测试 STARTTLS 通信:

$ ldapwhoami -H ldap:// -x -ZZ
anonymous

由于 ldaps:// 已经过时,不应该再使用。

配置 sssd 认证登录

安装软件包:

$ sudo apt-get install sssd-ldap ldap-utils

创建 /etc/sssd/sssd.conf ,权限 0600 ,所有权 root:root

[sssd]
config_file_version = 2
domains = example.com

[domain/example.com]
id_provider = ldap
auth_provider = ldap
chpass_provider = ldap
ldap_uri = ldap://ldap.example.com
cache_credentials = True
ldap_search_base = dc=example,dc=com

[pam]
offline_credentials_expiration = 60000

启动 sssd 服务:

$ sudo systemctl start sssd

自动创建家目录:

$ sudo pam-auth-update --enable mkhomedir

在 LDAP 上创建用户并测试

推荐 Apache Directory Studio 作为 GUI 工具,不用也没啥。

dn: dc=example,dc=com
objectClass: dcObject
objectClass: organization
objectClass: top
dc: example
o: example.com

dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: People

dn: ou=Group,dc=example,dc=com
objectClass: organizationalUnit
objectClass: top
ou: Group

dn: uid=usera,ou=People,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
cn: User A
gidNumber: 10001
homeDirectory: /home/usera
sn: usera
uid: usera
uidNumber: 10001
loginShell: /bin/bash
userPassword: wow

dn: cn=usera,ou=Group,dc=example,dc=com
objectClass: posixGroup
cn: usera
gidNumber: 10001

导入后,在 ldap 客户机上应该能查询到相关用户,并且以该用户身份登录:

$ getent passwd usera
usera:*:10001:10001:User A:/home/usera:/bin/bash
$ id usera
uid=10001(usera) gid=10001(usera) groups=10001(usera)

SSH 配置

LDAP Server

修改 LDAP 的 schema ,加上用户的公钥字段:

dn: cn=openssh-lpk,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: openssh-lpk
olcAttributeTypes: ( 1.3.6.1.4.1.24552.500.1.1.1.13 NAME 'sshPublicKey'
    DESC 'MANDATORY: OpenSSH Public key'
    EQUALITY octetStringMatch
    SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
olcObjectClasses: ( 1.3.6.1.4.1.24552.500.1.1.2.0 NAME 'ldapPublicKey' SUP top AUXILIARY
    DESC 'MANDATORY: OpenSSH LPK objectclass'
    MAY ( sshPublicKey $ uid )
    )

Client

新建 /usr/local/bin/ssh-keyldap:

#!/bin/sh
# vim: set ts=4:
#
# This script finds and prints authorized SSH public keys in LDAP for the
# username specified as the first argument.
#
# The program must be owned by root and not writable by group or others.
# It expects configuration file /etc/ssh/ldap.conf in format of ldap.conf(5).
#
# sshd_config for OpenSSH 6.2+:
#
#   AuthorizedKeysCommand /usr/local/bin/ssh-keyldap
#   AuthorizedKeysCommandUser nobody
#
set -eu

LDAPCONF='/etc/ssh/ldap.conf'

log() {
        logger -s -t sshd -p "auth.$1" "$2"
}

uid="$1"
export LDAPCONF

if [ ! -r "$LDAPCONF" ]; then
        log err "file $LDAPCONF does not exist or not readable"
        exit 1
fi

if ! expr "$uid" : '[a-zA-Z0-9._-]*$' 1>/dev/null; then
        log err "bad characters in username: $uid"
        exit 2
fi

keys=$(ldapsearch -x -LLL -ZZ -o ldif-wrap=no "(&(uid=$uid)(sshPublicKey=*))" \
        'sshPublicKey' | sed -n 's/^sshPublicKey:\s*\(.*\)$/\1/p')
keys_count=$(echo "$keys" | grep '^ssh' | wc -l)

log info "Loaded $keys_count SSH public key(s) from LDAP for user: $uid"

echo "$keys"

新建 /etc/ssh/ldap.conf:

# /etc/ssh/ldap.conf

# See ldap.conf(5) for details
# This file should be world readable but not world writable.

BASE ou=people,dc=itszzz,dc=top
URI ldap://hn00.itszzz.top

编辑 /etc/ssh/sshd_config ,加入:

AuthorizedKeysCommand /usr/local/bin/ssh-keyldap
AuthorizedKeysCommandUser nobody