[Ubuntu] 在 Ubuntu 上設定 SSSD 的詳細教學 === ###### tags: `LDAP` ###### tags: `LDAP`, `Ubuntu`, `SSSD`, `/etc/sssd/sssd.conf` <br> [TOC] <br> ## 簡介 ### 認識角色 - **NSS** = 查「是誰」 - **PAM** = 查「能不能進、怎麼進」 - **SSSD** 則是 NSS & PAM 的「中介 + 快取」 ### 角色用途 - **SSSD(System Security Services Daemon)** 統一向 LDAP/AD 等目錄服務取「身分資料、群組、密碼驗證」,並在本機快取。 - **NSS(Name Service Switch)** 系統查「帳號/群組資訊」的機制(例如 `id`、`getent` 會走 NSS)。 - **PAM(Pluggable Authentication Modules)** 系統做「驗證/授權/建家目錄等登入流程」的機制(例如 `su/ssh/login` 會走 PAM)。 ### 文件 - [[Arch manual] sssd.conf - the configuration file for SSSD](https://man.archlinux.org/man/sssd.conf.5) - debug_level - id_provider - auth_provider <br> --- <br> ## 在 Ubuntu 上用 SSSD 連 OpenLDAP > by gpt-5 (2025/09/23) > 範例假設: > - LDAP 伺服器:`ldap://10.78.26.241:389` > - Base DN:`dc=www,dc=google,dc=com` > - 單一使用者:`uid=tj_tsai,...` > - 群組:`cn=gid10001,...`、`gidNumber=10001`、`memberUid=tj_tsai` > - 先確認你已把 `userPassword` 寫入 **SSHA 雜湊**(可用 `slappasswd` 產生) <br> ### 步驟1:確認已在 LDAP 匯入個人資料 (測試資料) - ### 安裝 LDAP 相關指令(`ldapsearch`, `ldapwhoami` 等 LDAP CLI) ```bash sudo apt update sudo apt install -y ldap-utils ``` - ### 查詢 POSIX 帳號: tj_tsai ```ldif $ ldapsearch -x -H ldap://10.78.26.241:389 \ -b "dc=www,dc=google,dc=com" \ uid=tj_tsai -LLL dn: uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=asus,dc=com objectClass: inetOrgPerson objectClass: posixAccount objectClass: shadowAccount cn: tj_tsai sn: tsai givenName: tj displayName: tj_tsai mail: tj_tsai@gmail.com telephoneNumber: 39981 uid: tj_tsai uidNumber: 10001 gidNumber: 10001 homeDirectory: /home/tj_tsai loginShell: /bin/bash gecos: tj_tsai ``` <br> ### 步驟2:先用 LDAP 工具確認「能綁定登入」 - ### 透過使用者的帳/密,進行綁定登入 ```bash # 用帳密測試能否 bind(最重要!) # $ ldapwhoami -x -H ldap://10.78.26.241:389 \ -D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -w '123456' dn:uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=asus,dc=com ``` - `-D` 是帳號 - `-w` 是密碼 (是該使用者的明碼密碼,與 LDAP 內 userPassword 對應) - 能回 `dn:uid=...` 就代表 **認證本身沒問題**,再來才進 SSSD。 - 回應 `ldap_bind: Invalid credentials (49)` 表示帳密錯誤 <br> ### 步驟3:安裝 SSSD 服務所需的必要套件 - ### 安裝 SSSD 服務、NSS & PAM 外掛、LDAP CLI (前面已安裝) ```bash sudo apt update sudo apt install -y sssd sssd-ldap libnss-sss libpam-sss ldap-utils ``` > 安裝 (`sssd`, `sssd-ldap`, `libnss-sss`, `libpam-sss`) 任一套件, > 皆會自動安裝整套:(`sssd`, `sssd-ldap`, `libnss-sss`, `libpam-sss`, `ldap-utils`),以及 (`sssd-tools`, ...) > - **結論**:安裝 `sssd` 就可以安裝相關套件 - **SSSD(System Security Services Daemon)** - NSS & PAM 的「中介 + 快取」 - 統一向 LDAP/AD 等目錄服務取「身分資料、群組、密碼驗證」,並在本機快取。 - **NSS(Name Service Switch)** - 查詢「是誰」 - 系統查「帳號/群組資訊」的機制(例如 `id`、`getent` 會走 NSS)。 - **PAM(Pluggable Authentication Modules)** - 查詢「能不能進、怎麼進」 - 系統做「驗證/授權/建家目錄等登入流程」的機制(例如 `su/ssh/login` 會走 PAM)。 - 備註:套件`libpam-mkhomedir` -> 選用? <br> ### 步驟4:建立 `sssd.conf` :::danger ### :warning: 注意事項: 1. `sssd.conf` - :warning: 設定值後面不能有 `# 這是註解`,否則**註解**會變成**設定值的一部分** 造成 SSSD 啟動時遇到不明 error (看不出錯誤原因)) - 帳密不加引號,否則會查不到資料 2. 檔案權限必須是 600,否則 SSSD 不會啟動。 ```bash sudo chown root:root /etc/sssd/sssd.conf sudo chmod 600 /etc/sssd/sssd.conf ``` 3. 不加密 vs StartTLS / TLS: - 實務上,可先做**最小不加密**連線 - 確認流程 OK 後再啟用 StartTLS / TLS ::: ```ini= sudo tee /etc/sssd/sssd.conf >/dev/null <<'CONF' [sssd] services = nss, pam config_file_version = 2 domains = ldapdomain [nss] [pam] # 可離線快取密碼(網路斷線仍可登入,選配) offline_credentials_expiration = 0 [domain/ldapdomain] id_provider = ldap auth_provider = ldap chpass_provider = ldap # 你的 OpenLDAP ldap_uri = ldap://10.78.26.241 ldap_search_base = dc=www,dc=google,dc=com # 你的群組是 rfc2307 的 memberUid 形式(posixGroup) ldap_schema = rfc2307 ldap_group_member = memberUid # 若用 member(含 DN) 形式,則使用 rfc2307bis # [選用] 指定使用者與群組搜尋起點: # - 填了可加快查詢 # - 若目錄很大才加速用 # ldap_user_search_base = ou=users,dc=www,dc=google,dc=com # ldap_group_search_base = ou=groups,dc=www,dc=google,dc=com # 不列舉全部帳號,查到才回應,省資源 enumerate = false # 家目錄與 shell 來源於 LDAP 的 homeDirectory/loginShell;做為保險給 fallback override_homedir = /home/%u fallback_homedir = /home/%u default_shell = /bin/bash # 初期先放行(確認打得通);之後可改成 ldap/自訂規則 access_provider = permit # 如果你有 StartTLS/LDAPS,再改用下列(先註解起來): # # CaseA: for dev # # [備註] ssh 連線預設會使用 tls,所以要它搭配 ldap_tls_reqcert = never ldap_id_use_start_tls = False ldap_tls_reqcert = never # (NOTE: never: 測試用(無憑證時);上線請改為 'demand' 並配置 CA) # # CaseB: for prod # ldap_id_use_start_tls = true # ldap_tls_reqcert = demand # ldap_tls_cacert = /etc/ssl/certs/ca-certificates.crt # 使用管理者帳密查詢,並以 Bind DN 查詢(非匿名) # [備註] SSSD 連線到 LDAP 預設使用 Bind DN 查詢(非匿名查詢) ldap_default_bind_dn = cn=admin,dc=www,dc=google,dc=com ldap_default_authtok = YourSecurePassword #(NOTE: 純文字;僅限測試!正式環境改用讀檔或憑證) # 其他常見選項 # ------------------------------------------------ # enumerate = false # Default: FALSE (註解和設定不能在同一行) # cache_credentials = true # Default: FALSE (註解和設定不能在同一行) # 當使用者沒有 gidNumber 時,SSSD 自動建立一個與 uidNumber 相同的私有群組 # auto_private_groups = true # 強制設定 home 目錄為: # override_homedir = /home/%u # (可選)限制可登入的人:確定後再開 # access_provider = ldap # ldap_access_filter = (|(uid=tj_tsai)(uid=其他人)) CONF sudo chmod 600 /etc/sssd/sssd.conf ``` - `ldap_default_authtok`:就是 **Bind DN 的密碼本體**。 - **使用 ldapwhoami 快速驗證** > docs: [指令 - ldapwhoami (連線測試、帳號綁定測試)](/YWXj3DaKT3SDV4Rq2OTPrw#指令---ldapwhoami-連線測試、帳號綁定測試) ``` $ ldapwhoami -x \ -H ldap://10.78.26.241 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "實際管理者密碼" dn:cn=admin,dc=www,dc=google,dc=com ``` - **使用 ldapsearch 快速驗證** > docs: [指令 - ldapsearch (連線測試)](/YWXj3DaKT3SDV4Rq2OTPrw#指令---ldapsearch-連線測試)) ```bash $ ldapsearch -x \ -H ldap://10.78.26.241 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "實際管理者密碼" # 沒有錯誤訊息 -> 正常 # 失敗 -> ldap_bind: Invalid credentials (49) ``` - **注意事項**: - 測試環境可直接寫在 `sssd.conf`; - 正式環境請改用更安全的做法 (例如限制檔案權限、或改走 TLS/憑證、或以 `ldap_sudo_*` 等更細緻設定)。 - [docs](https://man.archlinux.org/man/sssd.conf.5) - [DOMAIN_SECTIONS](https://man.archlinux.org/man/sssd.conf.5#DOMAIN_SECTIONS) <br> ### 步驟5:讓 NSS / PAM 走 SSSD - ### (5-1) 讓 NSS 走 SSSD,需修改 `/etc/nsswitch.conf` - **確保有 `sss`**: ``` cat /etc/nsswitch.conf ``` - 若沒有 sss 服務,則進行修改: ```bash sudo sed -i \ 's/^passwd:.*/passwd: files sss/; s/^group:.*/group: files sss/; s/^shadow:.*/shadow: files sss/' \ /etc/nsswitch.conf ``` - **備註:安裝完 `sssd` 套件後,系統就會更新 `/etc/nsswitch.conf`** - **安裝前**: ```bash= $ cat /etc/nsswitch.conf # /etc/nsswitch.conf # # Example configuration of GNU Name Service Switch functionality. # If you have the `glibc-doc-reference' and `info' packages installed, try: # `info libc "Name Service Switch"' for information about this file. passwd: files group: files shadow: files gshadow: files hosts: files dns networks: files protocols: db files services: db files ethers: db files rpc: db files netgroup: nis ``` - **安裝後**: ```bash= $ cat /etc/nsswitch.conf # /etc/nsswitch.conf # # Example configuration of GNU Name Service Switch functionality. # If you have the `glibc-doc-reference' and `info' packages installed, try: # `info libc "Name Service Switch"' for information about this file. passwd: files sss group: files sss shadow: files sss gshadow: files hosts: files dns networks: files protocols: db files services: db files sss ethers: db files rpc: db files netgroup: nis sss automount: sss ``` - Snapshots - **安裝前:** ![](https://hackmd.io/_uploads/HyPMO6Onxe.png) - **安裝後:** ![](https://hackmd.io/_uploads/SyxIdpu2le.png) - ### (5-2) 啟用 PAM 模組(含自動建家目錄) ```bash sudo pam-auth-update ``` - **勾選**: - ✅ **SSS authentication** - ✅ **Create home directory on login**(`pam_mkhomedir.so`) - **執行過程** ``` $ pam-auth-update debconf: unable to initialize frontend: Dialog debconf: (No usable dialog-like program is installed, so the dialog based frontend cannot be used. at /usr/share/perl5/Debconf/FrontEnd/Dialog.pm line 79.) debconf: falling back to frontend: Readline debconf: unable to initialize frontend: Readline debconf: (Can't locate Term/ReadLine.pm in @INC (you may need to install the Term::ReadLine module) (@INC entries checked: /etc/perl /usr/local/lib/x86_64-linux-gnu/perl/5.38.2 /usr/local/share/perl/5.38.2 /usr/lib/x86_64-linux-gnu/perl5/5.38 /usr/share/perl5 /usr/lib/x86_64-linux-gnu/perl-base /usr/lib/x86_64-linux-gnu/perl/5.38 /usr/share/perl/5.38 /usr/local/lib/site_perl) at /usr/share/perl5/Debconf/FrontEnd/Readline.pm line 8.) debconf: falling back to frontend: Teletype PAM configuration ----------------- Pluggable Authentication Modules (PAM) determine how authentication, authorization, and password changing are handled on the system, as well as allowing configuration of additional actions to take when starting user sessions. Some PAM module packages provide profiles that can be used to automatically adjust the behavior of all PAM-using applications on the system. Please indicate which of these behaviors you wish to enable. 1. Pwquality password strength checking 3. SSS optional smart card authentication 5. SSS authentication 2. SSS required smart card authentication 4. Unix authentication 6. Create home directory on login (Enter the items or ranges you want to select, separated by spaces.) PAM profiles to enable: 5 6 ``` - ### 建立家目錄的探討 - ### 若沒啟用,會遇到: ``` $ su - tj_tsai su: warning: cannot change directory to /home/tj_tsai: No such file or directory ``` - ### [選用][手動] 直接用 CLI 快速打補丁: ```bash # 在 /etc/pam.d/common-session 追加 mkhomedir(若 pam-auth-update 沒自動加入) echo "session required pam_mkhomedir.so skel=/etc/skel/ umask=0077" \ | sudo tee -a /etc/pam.d/common-session ``` - 有設定 `umask=0077` 版本,也有設定 `umask=0022` 版本,待釐清影響(TODO) - ### [選用][自動] 如何在 K8s pod 上自動執行補丁: > 在 image, volumeMounts 同一階層,新增 lifecycle ```yaml= image: repository: ghcr.io/slinkyproject/login tag: 25.05-ubuntu24.04 volumeMounts: [] # - name: nfs-home # mountPath: /home lifecycle: postStart: exec: command: - /bin/bash - -c - | if ! grep -q "pam_mkhomedir.so" /etc/pam.d/common-session; then echo "session required pam_mkhomedir.so skel=/etc/skel umask=0022" >> /etc/pam.d/common-session fi ``` - 解釋 script ```bash # -q, --quiet, --silent suppress all normal output $ grep -q "pam_mkhomedir.so" /etc/pam.d/common-session $ echo $? # 0 $ grep -q "xxx.so" /etc/pam.d/common-session $ echo $? # 1 ``` <br> ### 步驟6:啟動 SSSD(兩種環境) - ### A. 一般 Ubuntu 主機(有 systemd) ```bash sudo systemctl enable sssd sudo systemctl restart sssd # 快速健康檢查 sudo sssctl config-check sudo sssctl domain-status ldapdomain ``` - ### B. 容器或 Pod(沒有 systemd) > - 「`systemctl: command not found`」就是這個情境。改用前景模式跑: > - 在 Pod 裡面測試時,請用 Ubuntu 基底映像,且別用 busybox/alpine(沒有 glibc NSS)。(待確認) ```bash # whereis ssd -> /usr/sbin/sssd # 終端機 1:前景模式+高除錯等級啟動(方便看 log) sssd -i -d 9 # 終端機 1:或背景模式: # sssd -D -d 9 # 終端機 2:進行測試(見下一節),log 會印在終端機 1 ``` - ### `sssd` usage: ``` Usage: sssd [OPTION...] -d, --debug-level=INT Debug level -D, --daemon Become a daemon (default) -i, --interactive Run interactive (not a daemon) ``` - 不加 `-i` 會自行 daemonize (常駐化)(背景化) <br> ### 步驟7:驗證 NSS / PAM ```bash # 7-1. 先看「身分」能不能解析到(NSS) getent passwd tj_tsai id tj_tsai getent group gid10001 # 7-2. 清快取重查(若你有反覆改 LDAP) sudo sss_cache -E # 7-3. 看 SSSD 眼中的使用者屬性 sudo sssctl user-show tj_tsai # 7-4. 測試認證(PAM) # 方式一:su(會要求輸入 tj_tsai 的 LDAP 密碼) sudo su - tj_tsai # 方式二:ssh 本機(需安裝/開啟 sshd,且 /etc/ssh/sshd_config 設 UsePAM yes) # ssh tj_tsai@127.0.0.1 ``` - ### 常見第一次登入問題 - **若出現**: ``` su: warning: cannot change directory to /home/tj_tsai: No such file or directory ``` 代表家目錄還沒建立。 - **解法**: - 已啟用 `pam_mkhomedir` 的話,**下次登入就會自動建立**。 - 或手動先建:`sudo mkdir -p /home/tj_tsai && sudo chown 10001:10001 /home/tj_tsai` - ### 執行範例 ``` $ getent passwd tj_tsai tj_tsai:*:10001:10001:tj_tsai:/home/tj_tsai:/bin/bash ``` ``` $ id tj_tsai uid=10001(tj_tsai) gid=10001(gid10001) groups=10001(gid10001) ``` ``` $ getent group gid10001 gid10001:*:10001:tj_tsai ``` ``` # 7-2. 清快取重查(若你有反覆改 LDAP) $ sudo sss_cache -E ``` ``` # 7-3. 看 SSSD 眼中的使用者屬性 $ sudo sssctl user-show tj_tsai Name: tj_tsai Cache entry creation date: 09/25/25 17:51:18 Cache entry last update time: 09/25/25 18:01:37 Cache entry expiration time: Expired Initgroups expiration time: Expired Cached in InfoPipe: No ``` - `17:51:18` -> `18:01:37`: 相差 `10:19` ``` # 7-4. 測試認證(PAM) # 方式一:su(會要求輸入 tj_tsai 的 LDAP 密碼) sudo su - tj_tsai tj_tsai@5493eba780fc:~$ ``` <br> --- ### 步驟8:你現在這份 LDAP 架構與 SSSD 對應重點 - 使用者屬性(`posixAccount`):`uid / uidNumber / gidNumber / homeDirectory / loginShell` 👉 **SSSD 直接吃得到**(不需額外對映)。 - 群組屬性(`posixGroup`):`memberUid` 👉 設 `ldap_schema = rfc2307`(預設也是),或顯式 `ldap_group_member = memberUid`,**即可解析**。 - 初期把 `access_provider = permit`,確認打得通後,你可以: - 換成 `access_provider = ldap`,或 - 簡單限制登入者: ```ini access_provider = simple simple_allow_groups = gid10001 ``` <br> --- ### 步驟9:TLS/StartTLS(選配,但正式環境建議) 若你的 OpenLDAP 有配置 CA 憑證: ```ini # sssd.conf 的 domain 區塊: ldap_id_use_start_tls = true ldap_tls_reqcert = demand ldap_tls_cacert = /etc/ssl/certs/ca-certificates.crt # 或自備 CA ``` 同時要讓系統信任你的 CA(把 CA 放到 `/usr/local/share/ca-certificates/xxx.crt`,再 `sudo update-ca-certificates`)。 --- <br> ### 除錯小抄(超好用) ```bash # 驗證設定檔語法 sudo sssctl config-check # 觀察 domain 狀態/連線 sudo sssctl domain-status ldapdomain # 顯示 SSSD 認知的使用者/群組 sudo sssctl user-show tj_tsai sudo sssctl group-show gid10001 # 清除身分與認證快取 sudo sss_cache -E # 看 log(systemd 環境) # 只有在有 systemd/journald 的環境(例如實體/VM Ubuntu)才行;容器內通常不可行。 journalctl -u sssd -f # 看檔案式 log(某些發行版/容器) sudo tail -f /var/log/sssd/sssd_*.log ``` - ### 執行範例 - ### 驗證設定檔語法 ``` $ sudo sssctl config-check Issues identified by validators: 1 [rule/allowed_nss_options]: Attribute 'enumerate' is not allowed in section 'nss'. Check for typos. Messages generated during configuration merging: 0 Used configuration snippet files: 0 ``` - ### 觀察 domain 狀態/連線 - ### A. 一般 Ubuntu(有 systemd) ```bash $ sudo sssctl domain-status ldapdomain ``` - ### B. 容器 / Pod(沒有 systemd) ``` # Terminal-1 $ sudo sssctl domain-status ldapdomain Unable to connect to system bus! ``` - ### 暫時解法:安裝 dbus > [[HackMD][docker] 安裝 dbus](/5AvN8q_wRHa3ZtoeZ-gNbA) - **Terminal-2** ```bash # terminal-1 # 安裝(若尚未) apt update apt install -y dbus # sssd sssd-ldap sssd-tools libnss-sss libpam-sss # 啟動 system bus(容器沒有 systemd,所以手動起 daemon) mkdir -p /var/run/dbus # 跑在前景 dbus-daemon --system --nofork --print-address ``` - **Terminal-3** ```bash # 現在就能查 domain 狀態了 $ sudo sssctl domain-status ldapdomain Online status: Online Active servers: LDAP: 10.78.26.241 Discovered LDAP servers: - 10.78.26.241 ``` - ### 若要重啟 dbus - 若要「正確」重啟 dbus,需要按照底下步驟: ``` # Terminal-1 # (1-1) 先中斷 sssctl 執行,等 dbus ready 再啟動 # Terminal-2 # (2-1) 刪除 socket,並重建 rm -rf /var/run/dbus mkdir -p /var/run/dbus # (2-2) 重啟 system bus dbus-daemon --system --nofork --print-address # Terminal-1 # (1-2) 重啟 sssctl 執行 # Terminal-3 # (3-1) 現在就能查 domain 狀態了 sudo sssctl domain-status ldapdomain ``` - **失敗情況** ``` $ dbus-daemon --system --nofork --print-address unix:path=/run/dbus/system_bus_socket,guid=c7ed1b2db4e2d59369d78bd768d5f3f9 dbus-daemon[4551]: [system] Activating service name='org.freedesktop.sssd.infopipe' requested by ':1.0' (uid=0 pid=4552 comm="sssctl domain-status ldapdomain" label="docker-default (enforce)") (using servicehelper) dbus-daemon[4551]: [system] Activated service 'org.freedesktop.sssd.infopipe' failed: Invalid configuration (missing or empty <user>?) ``` 避免「刪目錄」重啟 bus 的方式 - 不要在 SSSD 正在跑時 `rm -rf /run/dbus` & `/var/run/dbus`。這會把還在使用的 socket 移除,**IFP 需要時間偵測並重連**。 - 確認 `/var/run` 是否是 `/run` 的 symlink(多數發行版是): ```bash ls -ld /var/run ``` - ### 顯示 SSSD 認知的使用者/群組 ```bash $ sssctl user-show tj_tsai Name: tj_tsai Cache entry creation date: 09/25/25 17:51:18 Cache entry last update time: 09/25/25 18:27:01 Cache entry expiration time: Expired Initgroups expiration time: Expired Cached in InfoPipe: No $ sudo sssctl group-show gid10001 Name: gid10001 Cache entry creation date: 09/25/25 17:54:37 Cache entry last update time: 09/25/25 18:27:01 Cache entry expiration time: Expired Cached in InfoPipe: No ``` --- <br> ### 快速總結(照這順序做就會通) 1. **先用 `ldapwhoami` 確認帳密能 bind**。 2. 安裝 `sssd sssd-ldap libnss-sss libpam-sss`。 3. 建 `sssd.conf`(600 權限),先用 `access_provider=permit`。 4. `nsswitch.conf` 把 `files sss` 插好;`pam-auth-update` 勾 SSS 與 mkhomedir。 5. 啟動 SSSD(`systemctl restart sssd`;容器用 `sssd -i -d 9`)。 6. `id/getent/sssctl/su` 逐步驗證;缺家目錄就用 `pam_mkhomedir` 或手動建。 7. 通了再加上 **StartTLS/LDAPS** 與更嚴謹的 access 規則。 <br> --- <br> ## 在 Ubuntu 上用 SSSD 連 OpenLDAP - 2 (候選,待彙整合併,合併完即刪除) > gpt-5 (2025/09/23) ### (5) 驗證(身分查詢 → 登入) #### 5.1 檢查設定&連線 ```bash sudo sssctl config-check sudo sssctl domain-list sudo sssctl domain-status ldap -o ``` #### 5.2 身分查詢(NSS) ```bash getent passwd tj_tsai id tj_tsai getent group 10001 # 或 getent group <你的群組名> ``` **預期**:`getent passwd` 會輸出一行類似 `tj_tsai:x:10001:10001:TJ Tsai:/home/tj_tsai:/bin/bash`(GECOS 依你的資料而定) `id tj_tsai` 會顯示 `uid=10001 gid=10001 groups=...` > 若 `groups` 空或只顯示 GID 找不到對應名稱,多半是 **缺少 `posixGroup`** 或 `ldap_schema`/群組屬性不匹配(見文末)。 #### 5.3 登入驗證(PAM) 先確定 LDAP 使用者有 `userPassword`(例如 SSHA )。 然後試: ```bash su - tj_tsai # 或 ssh tj_tsai@<這台機器的 IP 或主機名> ``` **第一次登入**若啟用 `pam_mkhomedir`,會自動建立 `/home/tj_tsai`。 --- ### (6) 常見問題 & 除錯 * **`sssd` 起不來 / 沒反應**: 先 `sudo sssctl config-check`;再看 log: ``` journalctl -u sssd -e # 有 systemd sudo tail -f /var/log/sssd/sssd_ldap.log sudo tail -f /var/log/sssd/sssd.log ``` 需要更詳細:在 `[domain/ldap]` 加 `debug_level = 9`。 * **可以 `id/getent`,但無法登入**: 通常是 LDAP 使用者沒有 `userPassword`、密碼雜湊格式不被接受、或 PAM 沒載入 `pam_sss.so`。 檢查 `/etc/pam.d/common-auth`, `/etc/pam.d/common-account`, `/etc/pam.d/common-password`, `/etc/pam.d/common-session` 是否含 `pam_sss.so`。 * **群組解析不到**: 檢查是否有對應 `posixGroup`,以及你使用的 `ldap_schema` 是否與群組屬性一致(見下)。 * **TLS/StartTLS**: 如果要加密: ``` ldap_id_use_start_tls = True ldap_tls_reqcert = demand ldap_tls_cacert = /etc/ssl/certs/my-ldap-ca.pem ``` 並確保 CA 憑證正確放置與可讀。 * **快取問題**(改了 LDAP 看起來沒更新): ``` sudo sssctl cache-remove -u tj_tsai sudo systemctl restart sssd # 或:sudo sssctl flush-caches ``` --- ### (7) 群組架構小抉擇(rfc2307 vs rfc2307bis) * **rfc2307**(最常見):`posixGroup` 用 `memberUid` 儲存**使用者 uid(字串)**。 * **rfc2307bis**:`groupOfNames/posixGroup` 用 `member` 儲存**成員 DN**。 你的 `sssd.conf` 目前設 `ldap_schema = rfc2307`。那麼一個最小群組可以是: ```ldif # 建議建立與 gidNumber 匹配的群組(舉例:tj-group) dn: cn=tj-group,ou=groups,dc=www,dc=asus,dc=com objectClass: top objectClass: posixGroup cn: tj-group gidNumber: 10001 memberUid: tj_tsai ``` > 若改用 rfc2307bis,則: > > ```ini > ldap_schema = rfc2307bis > ``` > > 群組用: > > ```ldif > dn: cn=tj-group,ou=groups,dc=www,dc=asus,dc=com > objectClass: top > objectClass: groupOfNames > objectClass: posixGroup > cn: tj-group > gidNumber: 10001 > member: uid=tj_tsai,ou=users,dc=www,dc=asus,dc=com > ``` --- ### (8) 在容器/Pod 內測試的特別提醒 * 沒有 `systemctl` 很正常;用 `sssd -i -d 9` 前景跑即可。 * 容器要用 **Ubuntu/Debian glibc** 基底,才能載入 `libnss_sss.so`。 * 確保 `/etc/nsswitch.conf` 內有 `sss`。 * 測 `getent/id` 即可驗證 NSS;PAM 登入在容器內通常不適合(可改用 `su -` 簡測)。 --- #### 一鍵最小測試清單(複習) 1. 安裝套件 → 寫好 `sssd.conf`(600 權限)。 2. `nsswitch.conf` 加 `sss`;`common-session` 加 `pam_mkhomedir`(可選)。 3. `systemctl enable --now sssd`(或容器裡 `sssd -i -d 9`)。 4. `sssctl config-check` → `id tj_tsai` → `getent passwd tj_tsai`。 5.(要驗證密碼)`su - tj_tsai`。 如果你想直接用你現在的 LDAP 內容,我可以幫你補一段 **最適配的 `sssd.conf`** 與 **群組 LDIF**(看你群組是用 `memberUid` 還是 `member`)。只要回我你目前的群組條目長相就行。 <br> --- <br> ## `/etc/sssd/sssd.conf` 補充設定 ### 使用指南(案例學習) - ### by gpt-5.2 (2026/02/02) ### 1) `override_homedir = /tmp` 放在 `[nss]` vs `[domain/DEFAULT]` 有差嗎? 有差:**作用範圍與優先順序**不同。 * 放在 **`[nss]`**:視為 **全域 (global)** 的 NSS 設定,對 NSS responder 看到的使用者 home 目錄都生效(除非被某個 domain 覆蓋)。([Ubuntu Manpages][1]) * 放在 **`[domain/DEFAULT]`**:視為 **該 domain 專屬 (per-domain)** 的設定,只對 `DEFAULT` 這個 domain 的使用者生效,而且**會覆蓋** `[nss]` 裡同名設定。([Ubuntu Manpages][1]) > 你的例子目前只有一個 domain(`domains = DEFAULT`),所以「看起來」可能沒差;但一旦你之後加第二個 domain,就會有明確差異: > > * `[nss]`:所有 domain 都用同一套預設 > * `[domain/X]`:針對特定 domain 客製並覆蓋 ⚠️ 另外提醒:你現在寫的是 `override_homedir = /tmp`(沒有 `%u`),這代表**所有使用者的 home 都會變成同一個 `/tmp`**,很多程式/權限/快取會互相踩到。通常會用像 `/tmp/%u` 或 `/home/%u` 這種模板。`override_homedir` 支援格式化字串(如 `%u`)。([Ubuntu Manpages](https://manpages.ubuntu.com/manpages/jammy/man5/sssd.conf.5.html)) --- ### 2) `[xxx]` 是表示不同的 session 嗎? 不是。`[xxx]` 是 **SSSD 設定檔的「章節/區段」(section/stanza)**,用來配置不同的 **服務(responder)** 或 **domain(後端身分來源)**,不是 login session。([fedorapeople.org](https://fedorapeople.org/~sgallagh/sssd/1.7.0/man/sssd.conf.5.html)) 用你這份來對照: * **`[sssd]`**:總控區,決定要開哪些 responder(`services = nss,pam`)以及有哪些 domain(`domains = DEFAULT`)。 * **`[nss]`**:NSS responder 的設定(影響 `getent passwd`、`id` 這類查帳號/群組資訊的結果)。 * **`[pam]`**:PAM responder 的設定(影響登入驗證流程)。 * **`[domain/DEFAULT]`**:定義一個叫 `DEFAULT` 的 domain(例如用 LDAP 當 `id_provider/auth_provider`、LDAP server/bind DN 等)。 補充一個直覺理解:SSSD 內部通常會對 **每個 responder**(nss/pam/…)跟 **每個 domain** 各自跑對應的元件/程序來處理請求,但這跟「每個使用者登入 session」是不同層次的概念。 <br> --- ### 提高日誌詳細度 ```conf= [sssd] services = nss, pam, ifp domains = YOUR_DOMAIN debug_level = 9 [nss] debug_level = 9 [pam] debug_level = 9 [ifp] debug_level = 9 [domain/YOUR_DOMAIN] # 你的 ldap_* 設定… debug_level = 9 ``` - 設定檔權限務必 `sudo chmod 600 /etc/sssd/sssd.conf`。 - 重新啟動 SSSD <br> ### 確認 LDAP 跑起來後,啟用 StartTLS/LDAPS(正式服務) - ### 把 SSSD 切到 StartTLS ```ini # 仍使用原先 ldap_uri = ldap://xxx:389 (維持原先的 ip & port) # 在 [domain/ldapdomain] 裡加/改: ldap_id_use_start_tls = True ldap_tls_reqcert = demand ldap_tls_cacert = /etc/ssl/certs/openldap-ca.crt # 你的 CA 真實路徑 ``` - ### 把 SSSD 切到 LDAPS (走 `ldaps://xxx:636`) ```ini ldap_uri = ldaps://10.78.26.220:1636 ldap_tls_reqcert = demand ldap_tls_cacert = /etc/ssl/certs/openldap-ca.crt ``` - ### `demand` 用途,與其他相關設定值 > by gpt-5 (2025/10/27) > 簡單說:`ldap_tls_reqcert = demand` 表示**一定要驗證伺服器憑證**,否則就**中止連線**。 它會要求: * 伺服器必須送出憑證,且憑證鏈可由你指定的 `ldap_tls_cacert` 驗證通過; * 憑證未過期、未提前、簽章與用途正確(含 `serverAuth`); * 你連線用的主機名稱/IP 必須出現在憑證的 SAN(Subject Alternative Name)裡。若不符合就直接失敗。 ([Red Hat Documentation][1]) 對照其他值(越往下越嚴格): * `never`:不檢查憑證。 * `allow`:要求憑證;沒給或壞憑證都照常連線。 * `try`:要求憑證;沒給照常,**壞憑證中止**。 * `demand` / `hard`:**必須有且有效**,否則中止。兩者等價。 ([Red Hat Documentation][1]) 實務小抄: 1. 把簽發你 LDAP 伺服器憑證的 **CA** 放到 node2(`ldap_tls_cacert` 指向它)。 2. 憑證的 SAN 要包含你實際連線用的 DNS/IP(例如 `10.78.26.220`)。 3. 連線字串 `ldap_uri` 的主機名/IP要與 SAN 對得上。 這樣設成 `demand` 才會順利通過,否則就會因安全檢查失敗而拒連。 ([Red Hat Documentation][2]) [1]: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/6/html/deployment_guide/configuring_domains-configuring_a_native_ldap_domain?utm_source=chatgpt.com "13.2.11. Creating Domains: LDAP | Deployment Guide" [2]: https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/7/html/system-level_authentication_guide/configuring_domains?utm_source=chatgpt.com "7.3. Configuring Identity and Authentication Providers for ..." - ### 如何產生自簽憑證? > by gpt-5 (2025/10/27) A 是「一張自簽伺服器憑證」(快速測試用); B 是「自己架一個小 CA 再簽發伺服器憑證」(正式環境推薦)。 > 你的情境:OpenLDAP 在 node1,node2 會連到 **10.78.26.220**(請把 SAN 裡的 IP/DNS 換成你實際使用的位址或名稱)。 --- ### A. 最短版(測試用):直接產生「自簽伺服器憑證」 ```bash mkdir -p ~/ldap-certs && cd ~/ldap-certs # 產生一張含 SAN 的自簽伺服器憑證(1 年) openssl req -x509 -nodes -newkey rsa:2048 -days 365 \ -keyout ldap-server.key -out ldap-server.crt \ -subj "/C=TW/ST=Taiwan/L=Taipei/O=MyOrg/OU=IT/CN=ldap.local" \ -addext "subjectAltName=IP:10.78.26.220,DNS:ldap.local,DNS:openldap.openldap,DNS:openldap.openldap.svc.cluster.local" # 檢查 SAN openssl x509 -in ldap-server.crt -noout -text | grep -A1 "Subject Alternative Name" # 權限 chmod 600 ldap-server.key ``` 缺點:客戶端會看到「self-signed」錯誤,必須把這張 **同時**當作 CA 來信任(不太優雅)。正式環境請用 B。 --- ### B. 正式版(推薦):建立私有 CA,並用它簽 LDAP 伺服器憑證 #### 1) 建立私有 CA(根憑證) ```bash mkdir -p ~/ldap-ca && cd ~/ldap-ca # 產生 CA 私鑰(4096 bits) openssl genrsa -out myca.key 4096 # 產生 CA 憑證(10 年) openssl req -x509 -new -sha256 -days 3650 \ -key myca.key -out myca.crt \ -subj "/C=TW/ST=Taiwan/L=Taipei/O=MyOrg/OU=IT/CN=MyOrg Root CA" ``` #### 2) 產生伺服器私鑰與 CSR(含 SAN) ```bash mkdir -p ~/ldap-certs && cd ~/ldap-certs openssl genrsa -out ldap-server.key 2048 # 寫入 CSR 設定(請依實況調整 SAN) cat > server-openssl.cnf <<'EOF' [ req ] default_bits = 2048 prompt = no default_md = sha256 req_extensions = req_ext distinguished_name = dn [ dn ] C = TW ST = Taiwan L = Taipei O = MyOrg OU = IT CN = ldap.local [ req_ext ] subjectAltName = @alt_names [ alt_names ] DNS.1 = ldap.local DNS.2 = openldap.openldap DNS.3 = openldap.openldap.svc DNS.4 = openldap.openldap.svc.cluster.local IP.1 = 10.78.26.220 EOF # 產生 CSR openssl req -new -key ldap-server.key -out ldap-server.csr -config server-openssl.cnf ``` #### 3) 用 CA 簽發伺服器憑證(附延伸欄位) ```bash # 簽發時要再提供憑證的延伸欄位(特別是 SAN) cat > server-ext.cnf <<'EOF' authorityKeyIdentifier = keyid,issuer basicConstraints = CA:FALSE keyUsage = digitalSignature, keyEncipherment extendedKeyUsage = serverAuth subjectAltName = @alt_names [alt_names] DNS.1 = ldap.local DNS.2 = openldap.openldap DNS.3 = openldap.openldap.svc DNS.4 = openldap.openldap.svc.cluster.local IP.1 = 10.78.26.220 EOF # 回到 CA 目錄拿 CA 來簽 cd ~/ldap-certs openssl x509 -req -in ldap-server.csr \ -CA ../ldap-ca/myca.crt -CAkey ../ldap-ca/myca.key -CAcreateserial \ -out ldap-server.crt -days 825 -sha256 \ -extfile server-ext.cnf # 驗證 openssl verify -CAfile ../ldap-ca/myca.crt ldap-server.crt # 權限 chmod 600 ldap-server.key ``` --- ### C. 在「客戶端(node2)」安裝信任 CA(你剛做的 myca.crt) ```bash # 將 CA 安裝到信任庫(Ubuntu) sudo cp ~/ldap-ca/myca.crt /usr/local/share/ca-certificates/openldap-ca.crt sudo update-ca-certificates ``` > 之後 SSSD 或 ldapsearch 就能信任由這個 CA 簽發的伺服器憑證。 > 在 SSSD 的設定裡對應: > > ``` > ldap_tls_reqcert = demand > ldap_tls_cacert = /etc/ssl/certs/openldap-ca.crt > ``` --- ### D.(可選)Kubernetes 內掛憑證 把伺服器憑證與私鑰做成 Secret(給 OpenLDAP Pod 用): ```bash kubectl -n openldap create secret tls openldap-server-tls \ --cert=~/ldap-certs/ldap-server.crt \ --key=~/ldap-certs/ldap-server.key ``` 把 CA 也做成 Secret(有些映像會需要): ```bash kubectl -n openldap create secret generic openldap-ca \ --from-file=ca.crt=~/ldap-ca/myca.crt ``` > 之後依你的 OpenLDAP 映像/Chart 的方式,把這兩個 Secret 掛進去並啟用 TLS(LDAPS 或 StartTLS)。 --- ### E. 測試 * **LDAPS(636)**: ```bash ldapsearch -x -H ldaps://10.78.26.220:30636 -b "dc=www,dc=asus,dc=com" -LLL ``` * **StartTLS(389 + -ZZ)**: ```bash ldapsearch -x -H ldap://10.78.26.220:30389 -ZZ -b "dc=www,dc=asus,dc=com" -LLL ``` --- ### 常見錯誤速查 * `self signed certificate`/`unable to get local issuer certificate` → 客戶端沒安裝 **CA (myca.crt)**。 * `hostname mismatch` → 你連線用的 IP/DNS 不在 **SAN** 裡,請把 SAN 填齊(尤其 IP:10.78.26.220)。 * `sssd TLS failed` → 確認 `ldap_tls_cacert` 指到系統信任的 CA 路徑,`ldap_tls_reqcert = demand` 保持嚴格驗證。 如果你要我把以上腳本替你客製成「一鍵化」版本(只改幾個變數就能跑完),我可以直接生出來給你。 <br> ### `auto_private_groups = true` - 當 LDAP 使用者**沒有明確的 gidNumber** 時,SSSD 自動建立一個與 uidNumber 相同的私有群組 - 避免「primary group not found」錯誤 - 適用於 LDAP schema 中未強制要求 gidNumber 的環境 <br> ### `override_homedir = /home/%u` > 設定覆蓋 Home 目錄 - 強制覆寫從 LDAP 讀取的 `homeDirectory` 屬性 - `%u` 會替換為使用者名稱 - 確保所有使用者的家目錄統一為 `/home/username` 格式 - ### 當 home 已存在時的影響 - **如果 LDAP 中已有 `homeDirectory` 屬性:** | 情境 | LDAP homeDirectory | 實際使用 | 影響 | |------|-------------------|---------|-----| | **無 override_homedir** | `/home/tj_tsai` | `/home/tj_tsai` | 使用 LDAP 值 | | **有 override_homedir** | `/mnt/nfs/tj_tsai` | `/home/tj_tsai` | **強制覆寫**,忽略 LDAP 值 | | **有 override_homedir** | 未設定 | `/home/tj_tsai` | SSSD 提供預設值 | - **潛在問題:** - 如果 LDAP 中使用者的 `homeDirectory` 指向 NFS (`/mnt/nfs/tj_tsai`) - 但 `override_homedir = /home/%u` 會強制改為 `/home/tj_tsai` - 可能導致: - 使用者無法存取原本的 NFS 家目錄 - 檔案分散在兩個位置 - **建議:** - 如果 LDAP 已正確設定 homeDirectory → **移除** `override_homedir` - 如果 LDAP homeDirectory 不一致 → **保留** `override_homedir` 統一路徑 <br> --- ### `override_homedir = /work/%u` > - **用途**:強制覆蓋 home 目錄 > - **情境**:如果不需要 home 目錄,user 登錄後預設切到 work 目錄下? - ### `override_homedir = /work/%u` 的效果 - 將所有使用者的 home 目錄強制設為 `/work/<username>` - `%u`:會自動替換成使用者名稱 - 但 `/work/%u` 目錄會不存在,會直接回到 `/` (根目錄) - ### `override_homedir = /work/` 的效果 > 如果想要所有人都用同一個目錄,可以設為 `override_homedir = /work` - 使用效果: ``` tj_tsai@slurm-login-slinky-6885647d87-9fmc9:~$ pwd /work/ tj_tsai@slurm-login-slinky-6885647d87-9fmc9:~$ cd /work/ tj_tsai@slurm-login-slinky-6885647d87-9fmc9:/work$ pwd /work ``` - `~/` = `/work` (**缺點**:`~/` 看起來像 home 目錄,但實際上是 `/work`) - ### login pod 的設定:`workingDir: /work` ``` login: # -- The image to use, `${repository}:${tag}`. # Ref: https://kubernetes.io/docs/concepts/containers/images/#image-names image: repository: ghcr.io/slinkyproject/login-pyxis tag: 25.11-ubuntu24.04 workingDir: /work # 設定登入後的預設目錄 ``` - 是指透過 `kubectl exec -it ...` 方式登入時,切換到 `/work`: ``` $ kubectl exec -it pod/slurm-login-slinky-54b7c768f7-546ql -- bash root@slurm-login-slinky-54b7c768f7-546ql:/work# ``` 對照沒有加的效果,預設切到 `/tmp`: ``` $ kubectl exec -it pod/slurm-login-slinky-6885647d87-627gs -- bash root@slurm-login-slinky-6885647d87-627gs:/tmp# ``` - **`workingDir` 不會影響 SSH 使用者登入目錄**,原因如下: 1. **Container `workingDir`** 只影響容器主程序(如 sshd)的初始目錄 2. **SSH 登入目錄** 由 SSSD/PAM/NSS 從 LDAP 或其他身份驗證來源取得的使用者 home 目錄決定 3. 使用者 `tj_tsai` 的 home 目錄是從您的 LDAP 伺服器查詢得到的 `/home/tj_tsai` - ### SSHD Config 的設定:`ForceCommand cd /work && exec $SHELL` - 如果需求是要保留 `/home/${USER}`,但希望預設在 /work,可以: - [SlinkyProject](https://github.com/SlinkyProject/slurm-operator): ```yaml loginsets: main: extraSshdConfig: | # 強制 SSH 使用者切換到指定目錄 ForceCommand cd /work && exec $SHELL ``` - 添加 `exec $SHELL` 的原因 - [`/etc/ssh/sshd_config`](https://manpages.ubuntu.com/manpages/noble/man5/sshd_config.5.html) - `ForceCommand` Forces the execution of the command specified by **ForceCommand**, ignoring any command supplied by the client and ~/.ssh/rc if present. The command is invoked by using the user's login shell with the -c option. This applies to shell, command, or subsystem execution. It is most useful inside a Match block. The command originally supplied by the client is available in the SSH_ORIGINAL_COMMAND environment variable. Specifying a command of internal-sftp will force the use of an in-process SFTP server that requires no support files when used with ChrootDirectory. The default is none. > 強制執行 **ForceCommand** 所指定的指令,並忽略用戶端所提供的任何指令,以及(若存在)`~/.ssh/rc`。該指令會使用使用者的登入 shell,透過 `-c` 選項來呼叫執行。這個設定適用於 shell、command 或 subsystem 的執行。它在 `Match` 區塊內特別有用。 > > 用戶端原本提供的指令內容,會保存在 `SSH_ORIGINAL_COMMAND` 環境變數中。 > > 若指定的指令為 `internal-sftp`,則會強制使用內建(同一進程內)的 SFTP 伺服器;當與 `ChrootDirectory` 搭配使用時,不需要任何支援檔案。預設值為「無」。 <br> ### `ldap_auth_disable_tls_never_use_in_production = True` ```conf # 允許 SSSD 在沒有 TLS 保護的 LDAP 連線上,允許執行 simple bind 驗證 # i.e. 讓 SSSD 允許明文傳送密碼 (解決 refusing to do authentication) ldap_auth_disable_tls_never_use_in_production = True ``` <br> ### `use_fully_qualified_names = True` > 待確認 <br> --- <br> ## SSSD 服務管理 ### 修改 `/etc/sssd/sssd.conf` - #### SSSD 尚未處於 `active (running)` 狀態下 - 修改 `sssd.conf`,SSSD 就會立刻生效 - 不需要重啟 SSSD - #### SSSD 已經處於 `active (running)` 狀態下 - 修改 `sssd.conf`,SSSD 不會重新載入 - 需要主動重啟 SSSD ``` sudo systemctl restart sssd ``` <br> ### 查看狀態:`sudo systemctl status sssd` - ### 執行中 ``` $ sudo systemctl status sssd ● sssd.service - System Security Services Daemon Loaded: loaded (/lib/systemd/system/sssd.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2025-10-27 15:22:36 CST; 7min ago Main PID: 2293247 (sssd) Tasks: 4 (limit: 444119) Memory: 40.8M CPU: 131ms CGroup: /system.slice/sssd.service ├─2293247 /usr/sbin/sssd -i --logger=files ├─2293248 /usr/libexec/sssd/sssd_be --domain ldapdomain --uid 0 --gid 0 --logger=files ├─2293249 /usr/libexec/sssd/sssd_nss --uid 0 --gid 0 --logger=files └─2293250 /usr/libexec/sssd/sssd_pam --uid 0 --gid 0 --logger=files 十 27 15:22:35 WS-E900-G4-WS980T systemd[1]: Starting System Security Services Daemon... 十 27 15:22:36 WS-E900-G4-WS980T sssd[2293247]: Starting up 十 27 15:22:36 WS-E900-G4-WS980T sssd_be[2293248]: Starting up 十 27 15:22:36 WS-E900-G4-WS980T sssd_nss[2293249]: Starting up 十 27 15:22:36 WS-E900-G4-WS980T sssd_pam[2293250]: Starting up 十 27 15:22:36 WS-E900-G4-WS980T systemd[1]: Started System Security Services Daemon. ``` - ### 反覆啟用中 (失敗) ``` ● sssd.service - System Security Services Daemon Loaded: loaded (/lib/systemd/system/sssd.service; enabled; vendor preset: enabled) Active: activating (start) since Mon 2025-10-27 15:37:50 CST; 3s ago Main PID: 2344424 (sssd) Tasks: 1 (limit: 444119) Memory: 2.3M CPU: 58ms CGroup: /system.slice/sssd.service └─2344424 /usr/sbin/sssd -i --logger=files 十 27 15:37:50 WS-E900-G4-WS980T systemd[1]: Starting System Security Services Daemon... 十 27 15:37:50 WS-E900-G4-WS980T sssd[2344424]: Starting up 十 27 15:37:50 WS-E900-G4-WS980T sssd_be[2344472]: Starting up 十 27 15:37:51 WS-E900-G4-WS980T sssd_be[2344473]: Starting up 十 27 15:37:53 WS-E900-G4-WS980T sssd_be[2344534]: Starting up ``` <br> ### 重啟服務:`sudo systemctl restart sssd` ``` # 正常情況 $ sudo systemctl restart sssd # 有錯誤情況 $ sudo systemctl restart sssd Job for sssd.service failed because the control process exited with error code. See "systemctl status sssd.service" and "journalctl -xeu sssd.service" for details. ``` <br> ### 前景除錯(Ctrl+C 結束) > **備註**:可發現錯誤訊息,但不一定能看出什麼錯誤... ``` sudo systemctl stop sssd sudo sssd -i -d 9 ``` - ### Usage ``` Usage: sssd [OPTION...] -d, --debug-level=INT Debug level --debug-timestamps=INT Add debug timestamps --debug-microseconds=INT Show timestamps with microseconds --logger=stderr|files|journald Set logger -D, --daemon Become a daemon (default) -i, --interactive Run interactive (not a daemon) -c, --config=STRING Specify a non-default config file -g, --genconf Refresh the configuration database, then exit -s, --genconf-section=STRING Similar to --genconf, but only refreshes the given section --version Print version number and exit ``` <br> --- <br> ## [除錯] 使用 sssctl 指令找原因 ### 安裝 sssctl 指令 ```bash # Command `sssctl` & `sss_cache` not found, but can be installed with: sudo apt install sssd-tools -y ``` --- ### 檢查 `sssd.conf` > **備註**:不一定能檢查出錯誤 > ``` sudo sssctl config-check ``` - **用途**:檢查 `sssd.conf` 語法與基本有效性(含段落/選項是否可識別),同時檢查檔案權限是否安全。 - **預期結果**: - 正常時回傳 **0**,並顯示類似 *“Files provider is available / Config file is valid”*(訊息字樣可能略有不同)。 - 若有錯,會標出哪一行或哪個參數有問題;修正後重跑應變為通過。 - **什麼時候用**:編輯 `sssd.conf` 後、重啟 SSSD 前先驗證。 --- ### 清除快取方法 ``` sudo sss_cache -E ``` - **用途**:清除 SSSD 的使用者/群組等「身分目錄快取」,讓下一次查詢(如 `getent`, `id`)強制重新向 LDAP 取資料。 - **預期結果**: - 一般無輸出、回傳 **0**;之後第一次查詢會較慢(因為重新抓取),但應反映 LDAP 的最新變更。 - 不會刪除離線登入憑證(offline credentials),主要是讓目錄資料重新同步。 - **什麼時候用**:你在 LDAP 新增/修改使用者或群組,但主機上 `getent/id` 還沒看到更新時。 - **相關參數** ``` $ sudo sss_cache --help Usage: sss_cache [OPTION...] -E, --everything Invalidate all cached entries -u, --user=STRING Invalidate particular user -U, --users Invalidate all users -g, --group=STRING Invalidate particular group -G, --groups Invalidate all groups -n, --netgroup=STRING Invalidate particular netgroup -N, --netgroups Invalidate all netgroups -s, --service=STRING Invalidate particular service -S, --services Invalidate all services -a, --autofs-map=STRING Invalidate particular autofs map -A, --autofs-maps Invalidate all autofs maps -h, --ssh-host=STRING Invalidate particular SSH host -H, --ssh-hosts Invalidate all SSH hosts -r, --sudo-rule=STRING Invalidate particular sudo rule -R, --sudo-rules Invalidate all cached sudo rules -d, --domain=STRING Only invalidate entries from a particular domain ``` --- ### 清除快取方法(備案) - ### 方法一 ``` # 先停掉 SSSD 服務 sudo systemctl stop sssd # 這會備份然後清除本機快取內容 sudo sssctl cache-remove ``` - ### 方法二:手動刪快取 DB 與 memcache ``` sudo rm -f /var/lib/sss/db/*.ldb sudo rm -f /var/lib/sss/mc/* # ⚠️ 不要動 /var/lib/sss/secrets/*,除非你也想連「離線密碼驗證」一起清掉 ``` <br> --- ### 清除快取差異比較:`sss_cache -E` / `sssctl cache-expire -E` / `sssctl cache-remove` > by gpt-5 (2025/10/30) - ### 簡易說明 - `sss_cache -E` / `sssctl cache-expire -E`:**讓快取「過期」**,不刪除快取檔案。下次查詢會嘗試重抓;若後端連不到,SSSD 仍可能用**離線快取**回覆。 - `sssctl cache-remove`:**把快取資料庫整個清掉(會先備份)**。沒有任何離線資料可用,等同「乾淨重來」。 - ### 差異重點 | 項目 | `sss_cache` / `sssctl cache-expire` | `sssctl cache-remove` | | ---- | ----------------------------------- | ---------------------- | | 動作 | 標記為「過期」(expire/invalidate) | 直接**移除**快取內容 | | 檔案 | 不刪 `/var/lib/sss/db/*.ldb` | 會清空快取 DB(通常會先備份) | | 離線時 | 仍可能回覆舊資料(為了可用性) | **無資料可回**(除非重新連線抓到新資料) | | 風險 | 低、可細粒度(如 `-u/-g`) | 影響大,所有已快取的身分都清掉 | | 適用情境 | 單筆或批次「更新一下」 | 偵錯、遷移、要保證**不靠舊快取** | > 補充:`sssctl cache-expire` 只是 `sss_cache` 的包一層;本質一樣都是「expire」。 --- - ### 常用指令範例 - **只讓特定群組重抓(建議用名稱):** ```bash sudo sss_cache -g tj_tsai # 或加上網域 sudo sss_cache -g tj_tsai -d <DOMAIN> ``` - **徹底清空快取(測試用,最乾淨):** ```bash sudo systemctl stop sssd sudo sssctl cache-remove # 官方建議的清法(會備份再清) # 若版本/環境不支援,再用手動方式: # sudo rm -f /var/lib/sss/db/*.ldb /var/lib/sss/mc/* sudo systemctl start sssd ``` > 極端情況若你連**離線密碼快取**都要清(很少需要):還得移除 `/var/lib/sss/secrets/*`(**會讓離線登入失效**,請三思)。 --- - ### 為什麼清了快取還查得到? - 因為 `sss_cache -E` 只是讓資料過期; - 當 LDAP 連不到時,SSSD 為了可用性仍會用本機 DB **離線回覆**。 - 只有 `cache-remove` 或手動刪 DB,才能讓 `getent` 在離線時完全查不到該筆資料。 <br> --- ### 看 SSSD 有哪些網域 ```bash $ sudo sssctl domain-list ldapdomain ``` --- ### 查指定的網域狀態 ``` # Usage: # sudo sssctl domain-status <DOMAIN> -o -a -r # -o: 是否 online(true/false) # -a: 顯示目前使用中的伺服器 # -r: 顯示發現到的伺服器清單 $ sudo sssctl domain-status ldapdomain Online status: Online Active servers: LDAP: 10.78.26.220 Discovered LDAP servers: - 10.78.26.220 ``` - 看最近 log(是否寫著 offline / cached / LDAP 連線失敗): ``` sudo journalctl -u sssd --since "15 min ago" \ | egrep -i 'offline|online|ldap|server|cache' ``` --- ### 直接從 SSSD 問使用者(繞過 NSS) ```bash # 取得成功 $ sudo sssctl user-show tj_tsai Name: tj_tsai Cache entry creation date: 10/27/25 15:22:52 Cache entry last update time: 10/27/25 15:54:37 Cache entry expiration time: 10/27/25 17:24:37 Initgroups expiration time: 10/27/25 17:24:38 Cached in InfoPipe: No # 取得失敗 $ sudo sssctl user-show tj_tsai User tj_tsai is not present in cache. ``` - 若能取得資料,進一步確認 NSS 走 SSSD(`nsswitch.conf` 要含 `sss`)並測 ```bash grep -E '^(passwd|group|shadow):' /etc/nsswitch.conf getent passwd tj_tsai id tj_tsai ``` - 符合預期的 `/etc/nsswitch.conf`:要有 `sss` ``` passwd: files systemd sss group: files systemd sss shadow: files sss ``` or ``` passwd: files sss systemd group: files sss systemd shadow: files sss ``` - 指定從不同來源獲取 ``` $ getent -s files group 10001 # 從 files 取得 $ getent -s sss group 10001 # 從 sss 取得 ``` --- ### 直接從 SSSD 問群組(繞過 NSS) ```bash $ sudo sssctl group-show gid10001 Name: gid10001 Cache entry creation date: 09/25/25 17:54:37 Cache entry last update time: 09/25/25 18:27:01 Cache entry expiration time: Expired Cached in InfoPipe: No ``` --- ### 案例學習:強制更新 user 所屬的 project cache ```bash # Command `sssctl` & `sss_cache` not found, but can be installed with: sudo apt update && apt install sssd-tools -y # UPDATE GROUP CACHE # ----------------------------------------------------------- # [Step 1] check the current value # Cache entry last update time: 01/05/26 05:05:32 # Cache entry expiration time: 01/05/26 06:35:32 sudo sssctl group-show 0572ceb9-b4f1-4b0b-8d81-22005c2f74db # query the local value # [Step 2] invalidate the group cache sudo sss_cache -g 0572ceb9-b4f1-4b0b-8d81-22005c2f74db # [Step 3] recheck the current value # Cache entry last update time: 01/05/26 05:05:32 # Cache entry expiration time: Expired <-- expired sudo sssctl group-show 0572ceb9-b4f1-4b0b-8d81-22005c2f74db # [Step 4] sync the latest value # Cache entry last update time: 01/05/26 05:09:27 <-- updated # Cache entry expiration time: 01/05/26 06:39:27 <-- updated sudo getent -s sss group 0572ceb9-b4f1-4b0b-8d81-22005c2f74db # fetch from sss, and update the cache ``` --- ### 案例學習:強制更新 user 資訊 ```bash # Command `sssctl` & `sss_cache` not found, but can be installed with: sudo apt update && apt install sssd-tools -y # UPDATE USER CACHE # ----------------------------------------------------------- # [Step 1] check the current value # Cache entry last update time: 01/05/26 05:05:32 # Cache entry expiration time: 01/05/26 06:35:32 sudo sssctl user-show tj_tsai@asus.com@DEFAULT # query the local value # [Step 2] invalidate the user cache sudo sss_cache -u tj_tsai@asus.com@DEFAULT # [Step 3] recheck the current value # Cache entry last update time: 01/05/26 05:05:32 # Cache entry expiration time: Expired <-- expired sudo sssctl user-show tj_tsai@asus.com@DEFAULT # [Step 4] sync the latest value # Cache entry last update time: 01/05/26 05:09:27 <-- updated # Cache entry expiration time: 01/05/26 06:39:27 <-- updated sudo getent -s sss passwd tj_tsai@asus.com # fetch from sss, and update the cache # or sudo getent -s sss passwd tj_tsai@asus.com@DEFAULT ``` - ### 沒有加 domain 資訊,會出現 `Unable to parse name tj_tsai@asus.com.` - 確認 domain 資訊 - container 或 pod 實例 ``` $ sudo cat /etc/sssd/sssd.conf [sssd] domains = DEFAULT [domain/DEFAULT] ... ``` 或 ``` $ grep -n '^\[domain/' /etc/sssd/sssd.conf 17:[domain/DEFAULT] ``` - 沒有 systm bus 情況,執行 `sssctl domain-list` 會有錯誤: ``` $ sssctl domain-list Unable to connect to system bus! ``` - 沒有 container 或 pod 實例 (有 systm bus 情況) ``` $ sssctl domain-list ``` - ### `Initgroups expiration time: Expired` 代表什麼? 代表「群組列表(initgroups)那份 cache」已經過期,SSSD 下次需要重新抓群組關係。 - 你可以用任一個方式讓它刷新: ```bash id 'tj_tsai@asus.com' # 或 getent initgroups 'tj_tsai@asus.com' ``` - 如果你想強制清掉再重抓(看你環境支援的 `sss_cache` 參數),常見是: ```bash sss_cache -u 'tj_tsai@asus.com' # 清單一使用者 # 或 sss_cache -E # 清全部(慎用) ``` <br> --- <br> ## 討論 ### `systemctl restart sssd` -> `bash: systemctl: command not found` - ### Question ``` $ systemctl restart sssd bash: systemctl: command not found ``` pod 中無法啟用 `systemctl`? - ### gpt-5 (2025/09/19) 對,**Pod 裡通常沒有 systemd**,所以 `systemctl` 會是 `command not found`。在容器環境要「啟動/重啟 sssd」有幾種常見做法——挑一種最符合你部署方式的: --- ### A) 臨時手動啟動(除錯或單次測試)(★★★★★) 在 Pod 裡直接跑 **前景模式**: ```bash # 確認有安裝 sssd 與設定檔 which sssd ls -l /etc/sssd/sssd.conf # 權限要 600 (或至少只有 root 可讀) # 建好必要目錄 mkdir -p /var/lib/sss /var/log/sssd chmod 700 /var/lib/sss # 前景啟動(會把 Log 印在前景,方便除錯) /usr/sbin/sssd -i -d 3 ``` 開第二個終端(另一個 `kubectl exec -it`)做驗證: ```bash getent passwd tj_tsai id tj_tsai ``` 要「重啟」就把前景的 sssd ^C 停掉,再跑一次上面指令;或是 `killall sssd && /usr/sbin/sssd -i -d 3`。 --- ### B) 入口腳本(entrypoint)同時帶起 sssd + 你的服務 把 sssd 當背景行程啟,最後 `exec` 你的主程式(例如 sshd): ```bash #!/bin/bash set -euo pipefail # 檔案/目錄權限 chmod 600 /etc/sssd/sssd.conf || true mkdir -p /var/lib/sss /var/log/sssd chmod 700 /var/lib/sss # 啟動 sssd(背景) /usr/sbin/sssd -i -d 0 & # 啟動你的主服務(例:SSHD) exec /usr/sbin/sshd -D ``` 之後只要 **重啟 Pod** 就等於重啟 sssd: ```bash kubectl rollout restart deploy/<your-deploy> -n <ns> ``` --- ### C) 用 **supervisor/s6** 管兩個程序 若你已用 `supervisord`: ```ini ; /etc/supervisor/conf.d/services.conf [program:sssd] command=/usr/sbin/sssd -i -d 0 autorestart=true priority=10 [program:sshd] command=/usr/sbin/sshd -D autorestart=true priority=20 ``` 變更完 `sssd.conf` 後,重啟容器或 `supervisorctl restart sssd`。 --- ### D) Sidecar(較進階) 可以把 sssd 放在 **同一個 Pod 的 sidecar**,但請注意: * **libnss\_sss 透過 Unix socket**(`/var/lib/sss/pipes/*`)跟 sssd 溝通,**要用 shared volume**(`emptyDir`)把 **`/var/lib/sss`** 共享給主容器。 * `/etc/sssd/sssd.conf` 也要掛進 sidecar(通常用 Secret)。 精簡 YAML 範例(重點片段): ```yaml= volumes: - name: sss-cache emptyDir: {} - name: sssd-etc emptyDir: {} # 用 initContainer 複製 Secret 進來後改權限 600 initContainers: - name: sssd-init image: ubuntu:22.04 command: ["/bin/bash","-lc"] args: - | cp /secret/sssd.conf /etc/sssd/sssd.conf chmod 600 /etc/sssd/sssd.conf volumeMounts: - name: sssd-etc mountPath: /etc/sssd - name: sssd-secret mountPath: /secret volumes: - name: sssd-secret secret: secretName: sssd-conf containers: - name: sssd image: your-image-with-sssd command: ["/usr/sbin/sssd","-i","-d","0"] volumeMounts: - name: sss-cache mountPath: /var/lib/sss - name: sssd-etc mountPath: /etc/sssd - name: app image: your-app volumeMounts: - name: sss-cache mountPath: /var/lib/sss # 必須共享 - name: sssd-etc mountPath: /etc/sssd # 若需要 ``` > 為何不用直接把 Secret 掛到 `/etc/sssd`?因為 Secret 投影檔的權限通常是 0444,**sssd 會拒載**;所以用 `initContainer` 複製後 `chmod 600`。 --- ### E) Alpine 或極簡映像 `sssd` 不太友善 Alpine(musl),若你是 Alpine 基底,通常改用: * **nslcd(libnss-ldapd + libpam-ldapd)**,用法類似,前景啟動 `nslcd -d`,再配合 `nsswitch.conf`。 * 或乾脆把映像改成 Debian/Ubuntu,以便使用 `sssd`。 --- ### 重新載入設定的幾個實務做法 * 沒有 systemd 時,**最簡單是重啟 Pod**:`kubectl rollout restart ...` * 或在容器內 **kill sssd 讓 supervisor/entrypoint 重啟**: ```bash pkill -TERM sssd # 若沒有 supervisor,就手動再跑一次 /usr/sbin/sssd -i ``` * 檢查設定: ```bash sssctl config-check tail -f /var/log/sssd/sssd.log getent passwd tj_tsai id tj_tsai ``` --- ### 小結 * `systemctl` 在 Pod 裡正常就是沒有;**用前景模式 (`sssd -i`) 或用你的 process supervisor 來管**。 * 在 K8s 裡,**「重啟 Pod」=「重啟 sssd」** 是最乾淨的作法。 * 若要 Sidecar,**記得共享 `/var/lib/sss`**(Unix socket 在那裡)。 需要我幫你把你目前的 Deployment/StatefulSet 貼上來改成「入口腳本 + sssd」的完整範本嗎?我可以直接給你一份可套用的 YAML。 <br> --- <br> ### 解釋 sssd 的 nss & pam 術語 - ### gpt-5 (2025/09/23) 下面用最短白話把 **SSSD / NSS / PAM** 的常見術語一次釐清。重點心法是: **NSS = 查「是誰」;PAM = 查「能不能進、怎麼進」。** SSSD 則是兩者的「中介 + 快取」。 --- ### 一句話總覽 * **SSSD(System Security Services Daemon)**:統一向 LDAP/AD 等目錄服務取「身分資料、群組、密碼驗證」,並在本機快取。 * **NSS(Name Service Switch)**:系統查「帳號/群組資訊」的機制(例如 `id`、`getent` 會走 NSS)。 * **PAM(Pluggable Authentication Modules)**:系統做「驗證/授權/建家目錄等登入流程」的機制(例如 `su/ssh/login` 會走 PAM)。 --- ### NSS 相關術語(查「身分」這邊) * **`/etc/nsswitch.conf`**:定義查詢順序;例如 `passwd: files sss` 代表先查 `/etc/passwd`,再問 **SSSD**。 * **`libnss_sss`**:SSSD 的 NSS 外掛;讓 `getent passwd`, `getent group`, `id` 能從 SSSD 拿資料。 * **maps / 資料庫**:NSS 會查的類別;與 SSSD 相關的常見是 `passwd`、`group`、`shadow`(也可提供 `sudoers`、`netgroup`)。 * **`getent`**:通用查詢指令;`getent passwd <user>`、`getent group <grp>`。 * **`enumerate`**(列舉):是否「全量撈回」遠端使用者/群組。`enumerate=false`(預設)=**按需查**,對大目錄效能較佳。 * **`cache_credentials`**:啟用 SSSD 快取;搭配 PAM 可做**離線登入**(無網路時用快取)。 * **`id_provider`**:身分來源(`ldap`/`ad`/`ipa`/`proxy`)。 * **搜尋範圍**:`ldap_search_base`、`ldap_user_search_base`、`ldap_group_search_base`。 * **`ldap_schema`**:物件架構(`rfc2307` 用 `memberUid`;`rfc2307bis` 用 `member` DN)。 * **`override_homedir` / `fallback_homedir`**:覆寫或預設使用者家目錄格式(如 `/home/%u`)。 * **`use_fully_qualified_names`**:是否用 `user@domain` 命名(多網域時有用)。 * **`filter_users` / `filter_groups`**:排除本機不透過 SSSD 查詢的帳號/群組(如 `root`)。 --- ### PAM 相關術語(驗證/授權這邊) * **PAM 四階段**(Ubuntu 透過 `common-*` 組合各服務) * **`auth`**:你是誰、密碼對不對(身份驗證)。 * **`account`**:可不可以登入(帳戶狀態/期限/群組限制等)。 * **`password`**:修改密碼流程。 * **`session`**:登入/登出前後動作(建家目錄、掛載、寫 log 等)。 * **`pam_sss.so`**:SSSD 的 PAM 模組;把 PAM 的四階段委託給 SSSD。 * **`auth_provider`**:密碼驗證來源(如 `ldap`/`krb5`/`ad`)。 * **`access_provider`**:登入允許規則來源(如 `ldap`/`simple`/`permit`/`deny`)。 * **`ldap_access_filter`**:用 LDAP 過濾誰可登入(例如 `(&(objectClass=posixAccount)(uid=tj_tsai))`)。 * **`chpass_provider`**:改密碼由誰處理(如 `ldap`/`krb5`)。 * **離線驗證**:搭配 `cache_credentials=true`,在短暫斷線也能以快取認證(受到過期策略限制)。 * **`pam_mkhomedir.so`**(非 SSSD 模組):「第一次登入自動建立家目錄」。 --- ### NSS vs PAM:快速對照 | 面向 | NSS | PAM | | --------- | ----------------------------------- | --------------------------------------------------- | | 關心什麼 | 誰是誰(uid/gid、群組、家目錄…) | 能不能進、怎麼進(驗證/授權/建家目錄) | | 代表元件 | `libnss_sss` + `/etc/nsswitch.conf` | `pam_sss.so` + `/etc/pam.d/common-*` | | 常用指令 | `id`, `getent passwd/group` | `su`, `ssh`, `login`(觸發 PAM 流程) | | SSSD 連動設定 | `id_provider`、搜尋 base、`enumerate` | `auth_provider`、`access_provider`、`chpass_provider` | | 常見故障徵兆 | `id/getent` 找不到人 | 密碼對卻登入失敗、家目錄沒建 | --- ### 實務判斷小抄 * `id tj_tsai` 失敗 → **NSS/身分解析** 問題(看 `nsswitch.conf`、`libnss_sss`、`id_provider`、LDAP 群組架構)。 * `id` OK 但 `su/ssh` 登不進 → **PAM/驗證或授權** 問題(看 `pam_sss.so`、`auth_provider`、`access_provider`、LDAP `userPassword`)。 * 首登沒家目錄 → 補 `pam_mkhomedir.so` 到 `common-session`。 * 目錄很大/效能差 → 關 `enumerate`,改「按需查」。 * 多網域同名衝突 → 開 `use_fully_qualified_names = True`,用 `user@domain`。 --- ### 最少必要檢查指令 ```bash # NSS 面:能不能找到帳號/群組 getent passwd tj_tsai id tj_tsai # PAM 面:能不能通過驗證/授權 su - tj_tsai # 或 ssh 登入測試 # SSSD 狀態與除錯 sssctl config-check sssctl domain-status -o journalctl -u sssd -e tail -f /var/log/sssd/sssd_ldap.log ``` 需要的話,我也可以把你現在的 `sssd.conf` 條目逐行註解,對照 NSS 與 PAM 的意義,順便幫你挑出容易踩雷的地方(例如群組 schema、access\_filter 的寫法)。 <br> --- <br> ### Name Service Switch 如何翻譯成中文? - ### gpt-5 (2025/09/23) 建議譯法:**「名稱服務切換(NSS)」** (在首次出現時可寫成「名稱服務切換機制(NSS)」以強調它是決定查詢順序的機制。) 為什麼這樣翻? * **Name service** 在 Unix/GLIBC 指的是 *passwd、group、hosts* 等「名稱相關資料庫/服務」。 * **Switch** 指「切換/選擇順序」的機制(由 `/etc/nsswitch.conf` 決定),不是網路設備的「交換器」。 常見相關用語(zh-TW) * NSS module → **NSS 模組/名稱服務切換模組** * nsswitch.conf → **NSS 設定檔(nsswitch.conf)** * name service database → **名稱服務資料庫** 例句 -「請在 **/etc/nsswitch.conf** 設定 **名稱服務切換** 的查詢順序(如 files、sss、ldap)。」 -「啟用 **NSS 模組** 後,帳號查詢會先走 SSSD,再回退到本機檔案。」 <br> ### `passwd: files sss ldap` 的查詢順序是:先走 ldap ,再經由 sss,最後回到本機?? - ### gpt-5 (2025/10/01) 不是喔。**`nsswitch.conf` 的查詢順序是「由左到右」**。 所以寫成: ``` passwd: files sss ldap group: files sss ldap shadow: files sss ldap ``` 代表流程是: 1. **files** → 先查本機 `/etc/passwd`、`/etc/group`(`libnss_files`) 2. **sss** → 找不到才問 **SSSD** 的 NSS 介面(`libnss_sss`)。SSSD 會先查快取,必要時再去它設定的後端(LDAP / AD / IPA)抓資料 3. **ldap** → 還是沒結果,最後才走傳統 **libnss_ldap / nslcd** 直接打 LDAP --- ### 常見誤會為何? 你看到「sss 會去 LDAP 拿資料」,就以為要先 `ldap` 再 `sss`。其實**不用** —— 只要 `sss` 已設定連到 LDAP/AD,它本身就會處理遠端查詢與快取。 > **實務建議**:同一個資料庫(`passwd/group/shadow`)**選一種遠端管道就好**: > > * 用 SSSD → `files sss`(**建議**;有快取、離線與更完整的功能) > * 不用 SSSD、用 nslcd/libnss-ldapd → `files ldap` > 把兩者都開(`files sss ldap`)容易造成**重複查詢、延遲**,甚至**重複條目**。 --- ### 進階:狀態動作(可選) `nsswitch` 允許針對結果決定是否繼續,例如: ``` passwd: files sss [UNAVAIL=return] ldap ``` 意思是:若 SSSD 不可用(UNAVAIL)就直接返回,不再嘗試 `ldap`。 --- ### 如何確認實際走哪一條? * `getent passwd <user>`、`id <user>`:看能否取到遠端帳號 * 使用 SSSD 時: * `sssctl domain-status`、`sssctl user-show <user>` * 清快取重試:`sss_cache -E` * 使用 nslcd 時:`journalctl -u nslcd -f` 或前景模式 `nslcd -d` --- ### TL;DR `files sss ldap` = **本機 → SSSD(可間接查 LDAP/AD)→ 傳統 LDAP**。 若你已用 SSSD 連 LDAP,**改成** `files sss` 就夠了。 <br> ### What’s different between nscd, nslcd and sssd > https://medium.com/jerrynotes/f07316d46e3e - ### 服務定義 - **nslcd**:Daemon for NSS and PAM lookups using LDAP(nss-pam-ldapd) - **nscd**:name service cache daemon, - Nscd provides caching for: passwd, group, hosts databases through standard libc interfaces (getpwnam, getpwuid, getgrnam, getgrgid, gethostbyname, and others). - **sssd**: System Security Services Daemon - ### 差異性 - NSCD只支持緩存4種名稱服務:passwd, group, hosts and services entries. - SSSD 只支持緩存3種名稱服務:passwd, group, and netgroup entries. <br> --- ## 討論 - NSS 錯誤排除 ### `ldapsearch` 可查到資料,但 `getent passwd tj_tsai` 沒有查到東西 - ### gpt-5 (2025/10/27) `ldapsearch` OK 但 `getent` 空白,99% 是 **NSS 沒接到 SSSD**(或快取/設定沒重載)。照這個順序做就會好: --- ### 1) 安裝 NSS 介面並確認存在 ```bash sudo apt update sudo apt install -y libnss-sss # 檢查檔案是否存在 ls -l /lib/x86_64-linux-gnu/libnss_sss.so.2 ``` --- ### 2) 編輯 `/etc/nsswitch.conf` 把 `sss` 加到 `passwd/group/shadow` 後面(順序通常建議在 `files` 之後、`systemd` 之前): ``` -passwd: files systemd -group: files systemd -shadow: files +passwd: files sss systemd +group: files sss systemd +shadow: files sss ``` - **備註**:sss 在 systemd 之後亦無妨 --- ### 3) 確認 `sssd.conf` 要點 * Base DN 與你 `ldapsearch` 一致 * `ldap_default_bind_dn = cn=admin,dc=www,dc=google,dc=com`(很重要,不能有引號) * 權限:`chmod 600 /etc/sssd/sssd.conf` --- ### 4) 清快取&重啟 SSSD ```bash sudo sssctl config-check sudo sss_cache -E sudo systemctl restart sssd ``` --- ### 5) 驗證 ```bash # 看 domain 是否被載入 sssctl domain-list # 直接查使用者(繞過 NSS,能幫忙除錯) sssctl user-show tj_tsai # NSS 查詢(應該要出現一行 passwd 格式) getent passwd tj_tsai # 再查群組/ID id tj_tsai getent group project-A ``` --- ### 若仍不行,快速定位 * `journalctl -u sssd -b -n 200 --no-pager` 看 `sssd_nss` 的錯誤訊息。 * 把 `ldap_user_search_base / ldap_group_search_base` 暫時拿掉(或設為 `dc=asus,dc=com`)避免範圍卡太小。 * 確認使用者條目真的有 `uidNumber/gidNumber/homeDirectory/loginShell`(你貼的有,OK)。 照上面做完,`getent passwd tj_tsai` 應該就會正確回傳了。 <br> --- <br> ## 討論 - 進階快取知識 ### 為何要清 SSSD 快取? - ### gpt-5 (2025/10/30) 下面把你遇到的狀況用「**NFS 權限實務 × SSSD 快取**」的角度,完整、具體化成一個可重現、可診斷、可修復的情境說明。 --- ## 為何要清 SSSD 快取?——以 NFS 權限變更延遲為例 ### 角色與資料流(精簡示意) ``` LDAP(權威名錄) │ (user/group 最新事實) ▼ SSSD(用戶端快取 + NSS 提供者) │ (把 user/group 快取在 /var/lib/sss/db/*.ldb) ▼ glibc NSS / getent / id │ (查到的群組清單 → 傳給 kernel 建立 process 的 cred) ▼ Linux Kernel cred(每個 process 有自己一份群組清單) │ NFS client → NFS server (以「數字 UID/GID 清單」做權限判定) ``` 重點:**NFS 判定的是數字 UID/GID**。而這份「使用者所屬 GID 清單」在**每個行程建立時**就固定下來(存在 kernel 的 cred 裡)。 SSSD 只是**提供/快取**「這個使用者屬於哪些群組(哪些數字 GID)」的資訊。 --- ### 事件時間軸(典型案例) **09:00**(在 LDAP 變更) * 你在 **LDAP** 把 `tj_tsai` 加入/移出群組 `project-x`(GID=10001),或直接把 **GID=10001 的成員清單**改了。 * LDAP 上的「事實」已更新。 **09:01 ~ 09:30**(用戶端觀察到不一致) * 某台用戶端 A 的 **SSSD** 仍持有舊快取(還沒到 TTL),`getent -s sss group 10001` 仍回舊名單。 * 另一台用戶端 B 剛好沒快取或 TTL 到期,`getent -s sss group 10001` 已看到新名單。 * **結果:A 可以寫/B 被拒**(或反過來),現象像是「**權限忽然不一致 / 很看運氣**」。 **09:02**(舊行程的「已載入」群組清單) * 在 A 上,`tj_tsai` 這個使用者早上 8:50 就開著一個 shell 或長駐行程(例如 Docker/Pod、Jupyter、服務程式)。 * 即使你把 SSSD 快取更新了、`getent` 也看得到新群組,**那個已存在的行程仍持有「舊的 groups」**,對 NFS 來說依舊是舊權限。 * 表面看起來像「快取清了沒效」,其實卡在「**行程自己的群組快取(kernel cred)**」。 **09:05**(你做了排錯) * 你在 A 上看到: * `getent -s sss group 10001` → 有資料(來自 SSSD) * `systemctl stop sssd` 後還是查得到(socket-activation 或離線快取復活) * 你用 `ldapsearch -H ldap://10.78.26.220:1389` 失敗,但 **SSSD 可能用的是別的 URI(例如 ldaps:636 或 DNS 名稱)**,所以 SSSD 還能回應快取或甚至取到線上資料。 **09:10**(你清快取但仍有舊現象) * 你執行 `sss_cache -E`,這只是「讓快取過期」,**不會刪掉 /var/lib/sss/db 的資料**。 LDAP 連不到時,SSSD 會啟動「**離線回覆**」:仍用本地 DB 回應查詢。 * 你重啟 SSSD 後,舊 DB 又被載入,所以 `getent` 還是查得到。 * 就算你真的把快取清掉,**既有行程**仍持有舊的 group 清單;所以「對 NFS 的實際權限」仍不變。 --- ### 你在現場會看到的「症狀」 * `id tj_tsai` / `getent group 10001` 在不同機器/同機器不同時段結果不一致。 * NFS 實際存取:有些機器(或同機器不同行程)能讀寫,有些卻被 `Permission denied`。 * `ldapsearch` 對你指定的那台 IP:Port 失敗,但 SSSD 路徑仍然能回資料(因為 **離線**或 **不同 URI**)。 --- ### 釐清與確認:快速檢查手順 1. **看現在查詢走哪個來源** ```bash getent -s files group 10001 # 只走 /etc/group getent -s sss group 10001 # 只走 SSSD getent -s ldap group 10001 # 只走 libnss-ldapd(你沒有安裝通常沒輸出) ``` 你已證明只有 `sss` 在回。 2. **看 SSSD 是否離線/用到哪個伺服器** ```bash sudo sssctl domain-list sudo sssctl domain-status <DOMAIN> -o -a -r # 線上/離線、使用中伺服器、候選清單 sudo journalctl -u sssd --since "15 min ago" | egrep -i 'offline|online|ldap|server|cache' ``` 3. **行程層級的群組清單** * 同一台主機上,**舊的 shell/服務** 可能仍持有舊群組;開新的 shell 再 `id`,兩者可能不同。 * 你可以用: * `newgrp <group>` 臨時切群組(部分情境有用); * 登出再登入、或 `exec su - $USER`、或直接重啟該服務/容器/Pod,讓行程「重新載入」群組清單。 --- ### 立即修復(你要馬上看到效果) - ### A. 只是要更新特定群組/使用者的 SSSD 快取 ```bash # 選一個就好 sudo sss_cache -g 10001 # 以 GID 清(不一定每發行版都好用) sudo sss_cache -g tj_tsai # 以群組名清 sudo sssctl cache-expire -G # 全群組過期(不刪檔) ``` 然後**為測試用戶開新的 session**(或重啟相關服務/容器),再試 NFS。 - ### B. 要「清到見骨」(不靠離線資料) ```bash sudo systemctl stop sssd sudo sssctl cache-remove # 官方建議(會備份再清) # 如版本不支援,再手動: # sudo rm -f /var/lib/sss/db/*.ldb /var/lib/sss/mc/* # 測試想完全不走 SSSD,可暫時 mask(避免 socket 喚醒) # sudo systemctl mask sssd.service sssd-nss.socket sssd-pam.socket sudo systemctl start sssd # 或 unmask 後 start # 重新登入使用者或重啟服務/容器,再試 NFS ``` > 若此時仍「查得到」,多半是 **SSSD 用了你沒測的 URI/埠**。看 `/etc/sssd/sssd.conf` 的 `ldap_uri`、`ldap_tls_*`。 --- ### 預防與最佳化(降低「變更生效延遲」) 1. **縮短快取 TTL(權衡效能)** 在 `/etc/sssd/sssd.conf`(domain 區塊): ```ini # 例:把群組與使用者快取 TTL 調短(單位:秒) entry_cache_group_timeout = 300 entry_cache_user_timeout = 300 # 過期後由背景刷新,減少查詢「空窗」 refresh_expired_interval = 60 ``` > TTL 太短會增加 LDAP 壓力;請依規模評估。 2. **一致的「清快取 + 重新載入行程」流程** * 變更關鍵群組(例如 NFS 專案群組)後,透過 Ansible/腳本在所有客戶端: * 跑 `sss_cache` / `sssctl cache-expire`; * 依服務型態「重啟 relevant 服務 / 滾動重啟容器 / 提示使用者重新登入」。 3. **避免改動「數字 GID」本身** * NFS 判定靠「數字」,當你把群組改名沒事,但**改 GID 數字**常會造成老檔案所有權對不上; * 大批檔案要改 GID,請規劃維運時窗與 `chgrp -R`/ACL 調整。 4. **測試時的幾個地雷** * **root 在 NFS(root_squash)**:root 會被映射成 nobody,測權限時請用一般使用者。 * **列舉不代表沒資料**:`getent group` 不列出 LDAP 群組是正常(預設關閉 enumeration),以「精確查詢」為準:`getent group 10001` 或 `getent group project-x`。 * **多台 LDAP / 不同 URI**:`ldapsearch` 指到的 IP:Port 失敗,不等於 SSSD 也失敗;SSSD 可能用 `ldaps://` 或 DNS SRV。 --- ### 一頁檢核清單(拿去就能貼跑) ```bash echo '--- nsswitch ---'; grep -E '^(passwd|group|shadow):' /etc/nsswitch.conf echo '--- who answers ---'; getent -s files group 10001; getent -s sss group 10001; getent -s ldap group 10001 echo '--- domains ---'; sudo sssctl domain-list echo '--- domain status ---'; sudo sssctl domain-status <DOMAIN> -o -a -r echo '--- recent sssd logs ---'; sudo journalctl -u sssd --since "15 min ago" | egrep -i 'offline|online|ldap|server|cache' | tail -n 80 echo '--- current groups (new session) ---'; id tj_tsai ``` --- ### 小結 * **問題本質**:LDAP 改了群組 → SSSD 端有 TTL/離線快取 → 行程又各自緩存群組清單 → 導致 NFS 權限生效「有延遲、且不一致」。 * **修法**:到期/清快取(`sss_cache`/`sssctl cache-expire` 或 `cache-remove`)+**讓行程重載入群組**(新登入/重啟服務或容器)。 * **預防**:合適的 `entry_cache_*_timeout` 與 `refresh_expired_interval`、一致的發佈腳本、避免變更數字 GID。 <br> ### [接續] TTL 快取是什麼? - ### gpt-5 (2025/10/30) 下面解釋「TTL 快取」是什麼、SSSD 為什麼要有它、到期時會發生什麼事,以及和 NFS/LDAP 權限更新的關係。 --- ### 什麼是 TTL 快取? **TTL (Time-To-Live)** = 一筆快取資料被視為「新鮮可用」的時間上限(以秒計)。 在 **SSSD** 裡,像使用者/群組/網域群組/網路群組/`sudo` 規則等,查到一次之後會被寫入本機快取(磁碟在 `/var/lib/sss/db/*.ldb`;同時也有記憶體快取)。在 **TTL 未到期** 之前,再查同一筆資料就直接回快取,不必每次都打 LDAP。 **目的:** * 降低 LDAP 負載與網路延遲。 * LDAP/網路短暫異常時,系統仍可運作(離線可用)。 --- ### SSSD 的 TTL 與你實際遇到的現象 你在 LDAP 改了群組(例如把 `tj_tsai` 加進/移出 `GID=10001`),但: * **在 TTL 未到期前**:用戶端的 SSSD 還會回 **舊資料**,因此 `getent group 10001` 可能仍看到舊成員。 * **即使 TTL 到期**:SSSD 多半採「**軟性過期**」策略——先回舊資料、同時背景嘗試刷新(避免前台卡住);如果此刻 **LDAP 又剛好連不到**,SSSD 會以「**離線快取**」繼續回舊資料,直到能連上為止。 * **長時間開著的行程**(shell、服務、容器)在啟動時就把「使用者所屬群組(數字 GIDs)」裝進 kernel 的 **process credentials**,**不會自動更新**;就算 SSSD 已拿到新名單,**舊行程仍用舊群組**去打 NFS,所以 NFS 權限看起來「沒變」。 > NFS 只看 **數字 UID/GID**。因此 SSSD/LDAP 的更新,要等「新 session/重啟相關服務/容器」後,行程用到**新群組清單**,NFS 的判定才會反映更新。 --- ### 到期(Expire)≠ 刪除(Remove) * `sss_cache -E` / `sssctl cache-expire`:把快取標記為**已到期**,**不刪檔**。下一次查詢會嘗試重抓;若連不到 LDAP,就繼續用舊資料(離線回覆)。 * `sssctl cache-remove`:把快取**整包移除**(會先做備份)。這時沒資料可回,只能等連上 LDAP 取得新資料。 --- ### 常見可調參數(皆為秒;不同版本預設值略有差異) 放在 `/etc/sssd/sssd.conf` 的 **domain** 區塊,例: ```ini # 身分資料快取 TTL entry_cache_user_timeout = 300 entry_cache_group_timeout = 300 # 已過期資料的背景刷新頻率(避免前台查詢被阻塞) refresh_expired_interval = 60 # 過期後,允許先回舊資料的比例(避免尖峰卡頓;可選) entry_cache_nowait_percentage = 50 # 離線認證(與身分快取不同概念;是否允許沒連 LDAP 時用本機密碼雜湊登入) cache_credentials = True ``` > 想要**更快反映 LDAP 變更**→ 縮短 `entry_cache_*_timeout`; > 想要**查詢更順**→ 設定 `refresh_expired_interval` 與 `entry_cache_nowait_percentage`,讓刷新走背景。 > 但 TTL 太短會增大 LDAP 壓力,需權衡。 --- ### 你要如何「馬上」讓 NFS 權限生效? 1. **讓 SSSD 知道資料已過期** * 針對特定群組/使用者: ```bash sudo sss_cache -g 10001 # 以 GID 清 # 或 sudo sss_cache -g project-x # 以群組名清 ``` * 或批次: ```bash sudo sssctl cache-expire -G # 所有群組到期(不刪檔) ``` * 若要**徹底**避免回舊資料(純測試): ```bash sudo systemctl stop sssd sudo sssctl cache-remove # 或手動刪 /var/lib/sss/db/*.ldb sudo systemctl start sssd ``` 2. **讓行程重新載入群組** * 使用者重新登入(開新的 shell / SSH)、或 * 重啟相關服務/容器/Pod(長駐行程一定要做這步),讓 kernel credentials 載到**新群組清單**。 --- ### 小抄:如何判斷現在回的是不是舊快取? ```bash # 看誰在回(files/sss/ldap) getent -s files group 10001 getent -s sss group 10001 # SSSD 網域狀態(把 <DOMAIN> 換成你的 domain 名) sudo sssctl domain-list sudo sssctl domain-status <DOMAIN> -o -a -r # 線上/離線、使用中/候選伺服器 # 近期日誌(是否離線/刷新失敗/改走其他 URI) sudo journalctl -u sssd --since "15 min ago" | egrep -i 'offline|online|ldap|server|cache' ``` --- ### 一句話總結 **TTL 快取**讓 SSSD 在一定時間內直接回本機資料、到期後背景刷新;**離線時仍會回舊資料**以保可用性。 要讓 **NFS 權限**立刻生效:**到期/清快取 + 讓行程重載**(新 session / 重啟服務),這兩步缺一不可。 --- <br> ### [接續] 既有行程 何時更新快取? - ### gpt-5 (2025/10/30) 結論先講:**既有行程(process)不會自動更新群組**。 Linux 把「有效 UID/GID 與補充群組」存在行程的 kernel credentials(`struct cred`)裡,**在行程啟動時就定型**。之後就算 SSSD/LDAP 的資料更新了,舊行程也不會跟著變——除非你用會呼叫 `setgroups()/initgroups()` 的方式重建或切換 session。 --- ### 什麼時候會「更新」? * **重新登入 / 新開一個 shell/SSH**:`login`/`sshd`/`su`/`sudo -i` 會透過 PAM → `initgroups()` 取最新群組。 * **重啟服務/程式**:systemd 在啟動 `User=` 的服務時會計算該使用者的群組;**需要 restart** 才會載入新群組(reload 多半不夠)。 * **重啟容器/Pod/Job**:容器與批次工作在「啟動」那刻決定群組;要 **restart/rollout**。 * **用 `newgrp`/`sg`/`su - $USER`**:會開一個新(子)shell 或取代目前 shell,並重新設定(或切換)群組。 * **程式本身主動呼叫** `setgroups()` / `initgroups()`(少數應用會這麼做)。 > 反之:**原本就開著的 shell、長駐 daemon、已在跑的容器/Job** 都不會自己更新。 --- ### 實務對照表 | 情境 | 何時拿到新群組 | 你該做什麼 | |-----|-------------|-----------| | 互動式使用者(你在打 shell) | 新開 session 時 | 登出再登入;或 `exec su - $USER`;或重開一個新的 SSH | | systemd 服務 | 服務重新啟動時 | `systemctl restart <service>`(通常 **reload 不夠**) | | Docker/K8s | 容器/Pod 啟動時 | `docker restart …`;`kubectl rollout restart …` | | Slurm 工作 | Job 派發啟動時 | 取消並重送(`scancel` → 重新 submit`sbatch/srun`) | | 單次指令想用新群組 | 指令啟動時 | `newgrp <group>` 開子 shell;或 `sg <group> -c 'cmd'`;或 `sudo -u $USER -i` 後再跑 | --- ### 為什麼你會以為「快取清了還是沒變」? 1. 你已讓 **SSSD** 拿到新資料(`getent group …` 正確),**但舊行程仍保留舊群組**。 2. 即使你用 `sss_cache -E`,这只是讓 SSSD 的快取過期,不會修改**已在跑的行程**。 3. NFS 只認「**數字 UID/GID**」。舊行程帶著舊的 GID 清單去打 NFS,結果就還是舊權限。 --- ### 如何檢查「某個行程」手上到底拿哪些群組? ```bash # 看自己這個 shell id id -G # 只列數字 GID cat /proc/$$/status | grep ^Groups # 看特定服務/程式 PID=<pid_of_service_or_app> cat /proc/$PID/status | grep ^Groups ``` 若你在同一台主機上開「一個新 shell」再跑 `id`,會看到新舊兩個 shell 的 `Groups` 不同,這就是差異來源。 --- ### 建議的「權限變更發佈」SOP(最少踩雷) 1. 在 LDAP 改群組 → **讓 SSSD 盡快看到新資料** * 單筆:`sudo sss_cache -g <groupName>` 或 `-g <gid>` * 批次:`sudo sssctl cache-expire -G`(不刪檔,僅過期) 2. **重建使用該權限的 session** * 使用者重新登入;重啟相關服務;容器/Pod rollout;批次工作重送。 3. 驗證 * 在新 session 跑 `id`;在該行程的 `proc/$PID/status` 查 `Groups`;再做 NFS 讀寫測試。 > 如果要「完全不靠舊資料」做驗證,再加: > 停 SSSD → `sssctl cache-remove`(或刪 `/var/lib/sss/db/*.ldb`)→ 啟 SSSD → 用 **新 session** 驗證。 --- **一句話版**: SSSD 只影響「之後啟動的行程」會拿到哪些群組;**已經在跑的行程不會自動更新**,要靠「重新登入 / 重啟服務或容器 / newgrp/sg/su」才會套用最新群組。 <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}