ldapsearch 指令 === ###### tags: `LDAP` ###### tags: `LDAP`, `ldapsearch`, `DN`, `CN`, `Base DN`, `Bind DN` <br> [TOC] <br> ## Usage ``` usage: ldapsearch [options] [filter [attributes...]] where: filter RFC 4515 compliant LDAP search filter attributes whitespace-separated list of attribute descriptions which may include: 1.1 no attributes * all user attributes + all operational attributes Search options: -a deref one of never (default), always, search, or find -A retrieve attribute names only (no values) -b basedn base dn for search -c continuous operation mode (do not stop on errors) -E [!]<ext>[=<extparam>] search extensions (! indicates criticality) [!]accountUsability (NetScape Account usability) [!]domainScope (domain scope) !dontUseCopy (Don't Use Copy) [!]mv=<filter> (RFC 3876 matched values filter) [!]pr=<size>[/prompt|noprompt] (RFC 2696 paged results/prompt) [!]ps=<changetypes>/<changesonly>/<echg> (draft persistent search) [!]sss=[-]<attr[:OID]>[/[-]<attr[:OID]>...] (RFC 2891 server side sorting) [!]subentries[=true|false] (RFC 3672 subentries) [!]sync=ro[/<cookie>] (RFC 4533 LDAP Sync refreshOnly) rp[/<cookie>][/<slimit>] (refreshAndPersist) [!]vlv=<before>/<after>(/<offset>/<count>|:<value>) (ldapv3-vlv-09 virtual list views) [!]deref=derefAttr:attr[,...][;derefAttr:attr[,...][;...]] !dirSync=<flags>/<maxAttrCount>[/<cookie>] (MS AD DirSync) [!]extendedDn=<flag> (MS AD Extended DN [!]showDeleted (MS AD Show Deleted) [!]serverNotif (MS AD Server Notification) [!]<oid>[=:<value>|::<b64value>] (generic control; no response handling) -f file read operations from `file' -F prefix URL prefix for files (default: file:///tmp/) -l limit time limit (in seconds, or "none" or "max") for search -L print responses in LDIFv1 format -LL print responses in LDIF format without comments -LLL print responses in LDIF format without comments and version -M enable Manage DSA IT control (-MM to make critical) -P version protocol version (default: 3) -s scope one of base, one, sub or children (search scope) -S attr sort the results by attribute `attr' -t write binary values to files in temporary directory -tt write all values to files in temporary directory -T path write files to directory specified by path (default: /tmp) -u include User Friendly entry names in the output -z limit size limit (in entries, or "none" or "max") for search Common options: -d level set LDAP debugging level to `level' -D binddn bind DN -e [!]<ext>[=<extparam>] general extensions (! indicates criticality) [!]assert=<filter> (RFC 4528; a RFC 4515 Filter string) [!]authzid=<authzid> (RFC 4370; "dn:<dn>" or "u:<user>") [!]bauthzid (RFC 3829) [!]chaining[=<resolveBehavior>[/<continuationBehavior>]] one of "chainingPreferred", "chainingRequired", "referralsPreferred", "referralsRequired" [!]manageDSAit (RFC 3296) [!]noop ppolicy [!]postread[=<attrs>] (RFC 4527; comma-separated attr list) [!]preread[=<attrs>] (RFC 4527; comma-separated attr list) [!]relax [!]sessiontracking[=<username>] abandon, cancel, ignore (SIGINT sends abandon/cancel, or ignores response; if critical, doesn't wait for SIGINT. not really controls) -H URI LDAP Uniform Resource Identifier(s) -I use SASL Interactive mode -n show what would be done but don't actually do it -N do not use reverse DNS to canonicalize SASL host name -O props SASL security properties -o <opt>[=<optparam>] any libldap ldap.conf options, plus ldif_wrap=<width> (in columns, or "no" for no wrapping) nettimeout=<timeout> (in seconds, or "none" or "max") -Q use SASL Quiet mode -R realm SASL realm -U authcid SASL authentication identity -v run in verbose mode (diagnostics to standard output) -V print version info (-VV only) -w passwd bind password (for simple authentication) -W prompt for bind password -x Simple authentication -X authzid SASL authorization identity ("dn:<dn>" or "u:<user>") -y file Read password from file -Y mech SASL mechanism -Z Start TLS request (-ZZ to require successful response) ``` <br> ## 常用參數 ### `-x`, Simple authentication - 使用「簡單驗證(simple bind)」,避免走 SASL。 <br> ### `-H URI`, LDAP Uniform Resource Identifier(s) - 指定 LDAP 伺服器位址與埠(URI)。 - **URI**: Uniform Resource Identifier, 統一資源識別碼 - `ldap://…:389` 為一般 LDAP;若要加密,**再加 `-ZZ` 做 StartTLS**。 - `-ZZ`:在 389 上**升級成 TLS(StartTLS)**。若伺服器強制 TLS,沒加會被拒。 - `ldaps://…:636` 走 **LDAPS**(連上即 TLS)。 <br> ### `-D binddn`, bind DN > 講單講,就是輸入**登入帳號**的地方:`-D "你的帳號"` - `-D "..."`:Bind DN(登入身分)。 - **必須是完整 DN**,例如 `cn=admin,dc=www,dc=google,dc=com`。 - **DN**:**Distinguished Name**, 辨別名稱(完整地址) - 輔助記憶:ID Name (唯一識別碼名稱) <br> ### `-w passwd`, bind password (for simple authentication) > 講單講,就是輸入**登入密碼**的地方:`-w "你的密碼"` (把密碼寫在命令列) - `-w`:把密碼寫在命令列。 - `-W`:互動式輸入密碼(較安全,不把密碼寫在命令列)。 <br> ### `-W`, prompt for bind password > 講單講,就是輸入**登入密碼**的地方:`-W` (互動式輸入) - `-w`:把密碼寫在命令列。 - `-W`:互動式輸入密碼(較安全,不把密碼寫在命令列)。 <br> ### `-y file`, Read password from file > 講單講,就是透過檔案導入**登入密碼** <br> ### `-Y mech`, SASL mechanism > 講單講,透過外部機制(本機管理, TLS憑證)登入,免帳密 ``` # 本機免帳密 (在同一台機器上做管理) ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config dn # 遠端免帳密 + TLS憑證登入 ldapwhoami -Y EXTERNAL -H ldaps://ldap.example.com ``` <br> ### `-b basedn`, base dn for search > 講單講,就是從這個**樹根**開始找 - Base DN(搜尋起點) <br> ### filter > 講單講,就是:搜尋過濾器 - 設定方式 - 單一條件:`"(uid=tj_tsai)"` - 可用 AND/OR,例如:`"(&(objectClass=person)(uid=tj_tsai))"`。 <br> ### none vs `-L` vs `-LL` vs `-LLL` > - `-L` , print responses in LDIFv1 format > - `-LL` , print responses in LDIF format without comments > - `-LLL`, print responses in LDIF format without comments and version - ### none -> `-L` > 移除 search result ,添加版本資訊 ``` --- none +++ -L -# extended LDIF +version: 1 # search result -search: 2 -result: 0 Success ``` - ### `-L` -> `-LL` > 移除註解 ``` --- -L +++ -LL -# -# LDAPv3 -# base <uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=asus,dc=com> with scope subtree -# filter: (objectclass=*) -# requesting: ALL -# -# search result -# numResponses: 2 -# numEntries: 1 ``` - ### `-LL` -> `-LLL` > 移除版本資訊 ``` --- -LL +++ -LLL -version: 1 ``` <br> ### 其他參數 - `-s {base|one|sub}`:**搜尋範圍**,預設 `sub`(整棵子樹)。 - `-d 1`(或更高):輸出 **debug** 訊息,有助排錯。 - 在尾端加屬性名(如 `dn uid cn mail`)可**限制回傳欄位**;不加則回傳伺服器預設屬性集。 --- <br> ## ldapsearch 指令測試環境 ### 使用 K8s pod (`bitnami/openldap:latest` 已經下架) > 改用下一小節所介紹 ``` $ kubectl run ldap-test --rm -it --image=bitnami/openldap:latest -- bash ``` - `--rm`:Pod 結束後自動刪除。 - `-it`:互動式終端機。 - `--image=bitnami/openldap:latest`:使用 Bitnami 的 OpenLDAP 工具映像檔。 - **可用指令** - `ldapadd` - `ldapcompare` - `ldapdelete` - `ldapexop` - `ldapmodify` - `ldapmodrdn` - `ldappasswd` - `ldapsearch` - `ldapurl` - `ldapvc` - `ldapwhoami` - ### ImagePullBackOff ``` $ kubectl -n slurm run ldap-test -it --image=bitnami/openldap:latest Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 2m9s default-scheduler Successfully assigned slurm/ldap-test to ntuh-dev-master01 Normal Pulling 23s (x4 over 2m7s) kubelet Pulling image "bitnami/openldap:latest" Warning Failed 21s (x4 over 2m2s) kubelet Failed to pull image "bitnami/openldap:latest": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/bitnami/openldap:latest": failed to unpack image on snapshotter overlayfs: unexpected media type text/html for sha256:fec5d90e6a5d44014ec596bc6bb03e29f4f66ad9c7417e1bc388d81fb7901420: not found Warning Failed 21s (x4 over 2m2s) kubelet Error: ErrImagePull Normal BackOff 6s (x6 over 2m1s) kubelet Back-off pulling image "bitnami/openldap:latest" Warning Failed 6s (x6 over 2m1s) kubelet Error: ImagePullBackOff ``` <br> ### 使用 K8s pod (使用 `--image=ubuntu:24.04`) - ### 建立測試的 pod ``` $ kubectl -n slurm run ldap-test --image=ubuntu:24.04 -- sleep infinity $ kubectl -n slurm exec -it ldap-test -- bash $ apt update; apt install dnsutils ldap-utils -y ``` - ### 確認登錄帳密 ``` $ ldapwhoami -x \ -H ldap://openldap.openldap:389\ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" anonymous ``` - ### 查詢資料:全部的資料 ``` $ ldapsearch -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -b "ou=users,dc=www,dc=google,dc=com" ``` - ### 查詢資料:查詢 tj (LDAP 世界不區分大小寫,可以使用萬用字元`*`) ``` $ ldapsearch -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -b "ou=users,dc=www,dc=google,dc=com" \ "(uid=tj*)" ``` <br> ### 目錄伺服器的 Port: 389 (LDAP) vs 389 (LDAP + StartTLS) vs 636 (LDAPS) - ### 用 Port 389 + 明文傳輸 ```bash ldapsearch -x -H ldap://server.example.com:389 \ -b "dc=example,dc=com" "(objectClass=*)" ``` - ### 用 Port 389 並啟用加密(StartTLS)** > - 連線一開始是一般 LDAP;**用 `StartTLS` 指令升級成 TLS**。 > - 若**沒執行 StartTLS**,就是明文(不安全)。 ```bash ldapsearch -x -H ldap://server.example.com:389 \ -ZZ -b "dc=example,dc=com" "(objectClass=*)" ``` - `-ZZ` = 要求 StartTLS。沒有 `-ZZ` 就是明文,請避免。 - 先明文握手,再發 **StartTLS** 升級 - 失敗訊息 `ldap_start_tls: Connect error (-11) additional info: error:0A000086:SSL routines::certificate verify failed (self-signed certificate)` - ### 用 636(LDAPS) > - 一連上就進入 **TLS**(俗稱「LDAP over SSL/TLS」) > - **不需要再下 StartTLS**。 ```bash ldapsearch -x -H ldaps://server.example.com:636 \ -b "dc=example,dc=com" "(objectClass=*)" ``` - ### 差異總表 | 項目 | 389(LDAP 明文) | 389(LDAP + StartTLS) | 636(LDAPS) | | ---- | -------------- | --------------------- | ------------ | | **預設是否加密** | 否(明文) | 否 → 透過 StartTLS 升級為加密 | 是(連上即 TLS) | | **加密啟動方式** | 無 | 先明文握手,再發 **StartTLS** 升級 | 一連線即進入 **TLS** | | **客戶端額外參數** | 不需要 | 需要(例:`-ZZ`) | 不需要 | | **主要風險** | 密碼/資料明文外洩 | 客戶端若忘記升級→風險 | 一律加密、較直覺 | | **防火牆/埠口** | 只用 389 | 只用 389(明文/加密同埠) | 需另開 636 | | **URL Scheme** | `ldap://` | `ldap://` + StartTLS | `ldaps://` | | **伺服器端管控** | 可讀但高風險,不建議啟用 simple bind | 可**強制要求 StartTLS**、拒絕明文 bind | 天生強制加密 | | **相容性與標準傾向** | 僅測試或封閉網段 | IETF/現代實務常見 | 歷史悠久、相容性普遍 | <br> --- <br> ## 指令 - `ldapwhoami` (連線測試、帳號綁定測試) ### 匿名連線(只測通不通) > 如果有啟用匿名功能 ```bash $ ldapwhoami -x -H ldap://openldap.openldap:389 ``` - **參數說明** - `ldapwhoami`:向伺服器詢問「我(此連線)是誰?」成功就代表能連上,且(若未提供帳密)是匿名身分。 - `-x`:使用「簡單驗證(simple bind)」,避免走 SASL。對大多數測試最直覺。 - `-H ldap://openldap.openldap:389`:LDAP 伺服器位址(URI)。 - `ldap://` 走 389(明文,若要加密用 `-ZZ` 或改用 `ldaps://`)。 - `openldap.openldap` 是 DNS 名稱,請換成你的 Service 或主機名。 - **輸出** ``` # 成功 (代表連得通,但沒有帳密驗證) anonymous # 失敗 ldap_bind: Inappropriate authentication (48) additional info: anonymous bind disallowed ``` <br> --- ### 帶管理者帳密驗證(最常用) ```bash $ ldapwhoami -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" # 把密碼寫在命令列,會留在 shell 歷史,建議使用 -W 互動輸入密碼 ``` ```bash $ ldapwhoami -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -W # -W 互動式輸入密碼 ``` - **參數說明** - `-D "..."`:Bind DN(登入身分)。**必須是完整 DN**,例如 `cn=admin,dc=www,dc=google,dc=com`。 - `-w`:把密碼寫在命令列 - `-W`:互動式輸入密碼(較安全,不把密碼寫在命令列)。 - **對照** `-w YourSecurePassword`:直接在命令列帶密碼(**不建議**,會留在 shell 歷史)。 - **更安全** `-y /path/to/passfile`:密碼存在檔案,指向檔案路徑。 - **範例輸出** ``` # 成功 (代表帳密正確、成功綁定) dn: cn=admin,dc=www,dc=google,dc=com # 失敗 ldap_bind: Invalid credentials (49) ``` - **使用 curl 指令** ``` $ curl -u 'cn=admin,dc=www,dc=google,dc=com :YourSecurePassword' \ ldap://openldap.openldap:389/dc=www,dc=google,dc=com DN: dc=www,dc=google,dc=com objectClass: top objectClass: dcObject objectClass: organization o: Google dc: google ``` <br> --- ### 帶使用者帳密驗證 ```bash $ ldapwhoami -x -H ldap://openldap.openldap \ -D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -w '123456' # 把密碼寫在命令列,會留在 shell 歷史,建議使用 -W 互動輸入密碼 ``` - **範例輸出** ```bash # 成功 (代表帳密正確、成功綁定) dn:uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=asus,dc=com # 失敗 ldap_bind: Invalid credentials (49) ``` - **相似用法1** ```bash $ ldapsearch -x -H ldap://openldap.openldap \ -D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -w '123456' \ -LLL # 成功: No such object (32) # 失敗: ldap_bind: Invalid credentials (49) ``` - **相似用法2** ```bash $ ldapsearch -x -H ldap://openldap.openldap \ -D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -w '123456' \ -b "" -s base namingContexts -LLL # 成功: namingContexts: dc=www,dc=asus,dc=com # 失敗: ldap_bind: Invalid credentials (49) ``` - `-b ""` 可省略 - `-s` 預設是 `sub` - 如果回傳 `No such object (32)`, 表示 **連 RootDSE/namingContexts** 都看不到(ACL 太緊)。 <br> --- ### 安全連線 / StartTLS(在 389 埠升級成加密) > (伺服器有啟用 TLS/LDAPS 才能用) ```bash $ ldapwhoami -x -ZZ \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -W ``` - **參數說明** - `-ZZ`:要求使用 StartTLS(**強制**)。伺服器若沒開 StartTLS 會回錯誤。 - (補充:`-Z` 為「嘗試」,`-ZZ` 為「必須」。實務上建議 `-ZZ`。) - **輸出** ``` # 失敗 (未啟用 StartTLS) ldap_start_tls: Connect error (-11) additional info: error:0A000086:SSL routines::certificate verify failed (self-signed certificate) ``` <br> --- ### 安全連線 / 直接 LDAPS(636) > (伺服器有啟用 TLS/LDAPS 才能用) ```bash $ ldapwhoami -x \ -H ldaps://openldap.openldap:636 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -W ``` - **參數說明** - `ldaps://...:636`:以 LDAPS 加密通道直連。伺服器需正確安裝憑證。 > 若遇到 TLS 憑證問題:請確認客戶端有對應的 CA 憑證;**不要**直接關閉驗證,除非純測試環境且明白風險。 - **輸出** ``` # 失敗 (未啟用 TLS/LDAPS) ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1) ``` <br> --- <br> ## 指令 - `ldapsearch` (連線測試) ### 帶帳密驗證(最常用) > 同前面 `ldapwhoami` 用法 ```bash $ ldapsearch -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" # 把密碼寫在命令列 ``` ```bash $ ldapsearch -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -W # 互動式輸入密碼 ``` - **輸出** - **成功** ```ldif # extended LDIF # # LDAPv3 # base <> (default) with scope subtree # filter: (objectclass=*) # requesting: ALL # # search result search: 2 result: 32 No such object # numResponses: 1 ``` - **失敗** ``` ldap_bind: Invalid credentials (49) ``` <br> ### 帶帳密驗證(請求 namingContexts) ``` $ ldapsearch -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -s base namingContexts -LLL ``` - **輸出** - **成功** ``` dn: namingContexts: dc=www,dc=google,dc=com ``` - **失敗** ``` ldap_bind: Invalid credentials (49) ``` <br> ### 使用其他帳密驗證 帳號:一個dn 密碼: ``` $ ldapsearch -x \ -H ldap://openldap.openldap:389 \ -D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -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=google,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 userPassword:: e1NTSEF9Yi9EY1B0RDFDWDgzNkhjRjJ5TmZRbCtmd2NCZGI0R0Y= ``` - `userPassword:: e1NTSEF9Yi9EY1B0RDFDWDgzNkhjRjJ5TmZRbCtmd2NCZGI0R0Y=` ``` echo e1NTSEF9Yi9EY1B0RDFDWDgzNkhjRjJ5TmZRbCtmd2NCZGI0R0Y= | base64 -d {SSHA}b/DcPtD1CX836HcF2yNfQl+fwcBdb4GF ``` <br> --- <br> ## 指令 - `ldapsearch` (查詢測試) ### 測試有讀到任何東西 > 同前面章節的連線測試 ```bash ldapsearch -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" ``` - **執行範例** ```ldif # extended LDIF # # LDAPv3 # base <> (default) with scope subtree # filter: (objectclass=*) # requesting: ALL # # search result search: 2 result: 32 No such object # numResponses: 1 ``` <br> ### 測試有讀到任何東西 (從指定的搜尋起點開始) ```bash ldapsearch -x \ -H ldap://openldap.openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -b "dc=www,dc=google,dc=com" \ "(objectClass=*)" -LLL -z 1 ``` - **新增參數說明** - `ldapsearch`:做查詢(搜尋目錄樹)。 - `-b "..."`:Base DN(搜尋起點)。 - `"(objectClass=*)"`:搜尋條件(過濾器)。這是「匹配全部物件」的萬用測試。 - `-LLL`:輸出簡潔 LDIF(不印 version、註解等)。 - `-z 1`:最多回 1 筆(快速測權限即可)。 <br> ### 查詢自己的 posixAccount ``` ldapsearch -x \ -H ldap://openldap.openldap \ -D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -w '123456' \ -b "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -s base \ -LLL ``` - `-s base` 或 `-s sub` 都可以查到資料 - 底下查不到資料是正常的: ``` ldapsearch -x -H ldap://openldap.openldap \ -D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" -w '123456' \ -b "dc=www,dc=asus,dc=com" \ -s base \ -LLL ``` - **原因**:樹上層節點(base DN)缺少 discover/search 權限,需要調整 `olcAccess` 權限 <br> ### 怎麼知道現在有哪些編號可用? 用 root 在主機上查: ```bash # 列出所有資料庫(看現有 {N}) ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config '(olcDatabase=*)' olcDatabase olcSuffix # 只列 mdb 類型 ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config '(&(objectClass=olcMdbConfig)(olcSuffix=*))' olcDatabase olcSuffix olcDbDirectory ``` <br> ### 找出下一個可用的資料庫索引 `{N}` ```bash ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config '(olcDatabase=*)' olcDatabase # 看現有 {0},{1},{2}...,選一個未使用的號碼,以下以 {3} 為例 ``` <br> --- <br> ## 指令 - `ldapadd` (新增資料) - ### 範例1:新增資料 1. **建立 `add_group.ldif`**: ```ldif dn: cn=gid10001,ou=groups,dc=www,dc=google,dc=com objectClass: posixGroup cn: gid10001 gidNumber: 10001 memberUid: tj_tsai ``` 2. **執行**: ```bash # 匯入 ldapadd -x \ -H ldap://openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -f add_group.ldif # 驗證 ldapsearch -x \ -H ldap://openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -b "dc=www,dc=google,dc=com" \ "(gidNumber=10001)" -LLL ``` <br> --- <br> ## 指令 - `ldapmodify` (修改資料) - ### 範例1:刪除資料 1. **建立 `delete_user.ldif`**: ```ldif dn: cn=tj_tsai,ou=users,dc=www,dc=google,dc=com changetype: delete ``` 2. **執行**: ```bash ldapmodify -x \ -H ldap://openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -f delete_user.ldif ``` - ### 範例2:更新密碼 1. **建立 `update-password.ldif`**: ```ldif dn: uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com changetype: modify replace: userPassword userPassword: <userPassword> ``` - **`<userPassword>` 產生方式** - 登入密碼:`123456` - 產生雜湊: ``` $ slappasswd -h {SSHA} New password: 123456 Re-enter new password: 123456 {SSHA}1Y3Fw3kdU7f9KBwnBIN9Qp+v4VW7Vhlt ``` - **最終檔案內容** ```ldif dn: uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=asus,dc=com changetype: modify replace: userPassword userPassword: {SSHA}1Y3Fw3kdU7f9KBwnBIN9Qp+v4VW7Vhlt ``` 2. **套用設定**: ```bash $ ldapmodify -x \ -H ldap://openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ -f delete_user.ldif # 執行結果 modifying entry "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" ``` 3. **登入驗證**: ```bash $ ldapwhoami -x -H ldap://openldap: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=google,dc=com ``` <br> --- <br> ## 指令 - `ldapdelete` (刪除資料) - ### 範例1:刪除使用者 ``` ldapdelete -x -H ldap://openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ "cn=tj_tsai,ou=users,dc=www,dc=google,dc=com" ``` - **刪除目標**:特定 dn - ### 範例2:遞迴刪除整個子樹(若該帳號條目有子節點) ```bash ldapdelete -x -r \ -H ldap://openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" -W \ "cn=tj_tsai,ou=users,dc=www,dc=google,dc=com" ``` - `-r` 會逐一刪除該 DN 下的所有子條目(OpenLDAP 支援)。請務必小心使用。 <br> --- <br> ## 指令 - `ldappasswd` (修改密碼) ### 重設密碼 - ### 不知道原密碼就**重設**: ```bash # 以管理者改使用者密碼(伺服器自動雜湊) ldappasswd -x \ -H ldap://openldap:389 \ -D "cn=admin,dc=www,dc=google,dc=com" \ -w "YourSecurePassword" \ "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -s 'your_new_strong_password!' ``` - ### 使用新密碼測試登入 ``` # 立即測試(注意:-w 是明碼) ldapwhoami -x \ -H ldap://openldap:389 \ -D "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \ -w 'your_new_strong_password!' ``` - ### 亦可透過 `ldapmodify` 指令變更密碼 - [ldapmodify / 範例2:更新密碼](#範例2:更新密碼) <br> --- <br> ## 透過 `curl` 指令查詢 ### 安裝 `curl` 指令 ``` apt update apt install curl -y ``` <br> ### 檢查 `curl` 指令是否支援 ldap / ldaps ```bash curl -V # 看 Protocols 列表是否含 ldap / ldaps ``` - ### 執行範例 ``` $ curl -V curl 8.5.0 (x86_64-pc-linux-gnu) libcurl/8.5.0 OpenSSL/3.0.13 zlib/1.3 brotli/1.1.0 zstd/1.5.5 libidn2/2.3.7 libpsl/0.21.2 (+libidn2/2.3.7) libssh/0.10.6/openssl/zlib nghttp2/1.59.0 librtmp/2.3 OpenLDAP/2.6.7 Release-Date: 2023-12-06, security patched: 8.5.0-2ubuntu10.6 Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM PSL SPNEGO SSL threadsafe TLS-SRP UnixSockets zstd ``` <br> ### 基本語法 LDAP URL 由下列片段組成: ``` ldap[s]://主機:埠/基底DN?屬性清單?範圍?過濾器 ``` - 基底DN,屬性清單,範圍,過濾器 -> 內容皆不分大小寫(大小寫不敏感) - **範圍**:`base`(預設) | `one` | `sub` - **過濾器/操作範例**: - `uid=tj_tsai` - `(UID=TJ_Tsai)` - `uid=tj*` - `(objectClass=posixGroup)` <br> ### 常用查詢範例 > 假設你的目錄根為 `dc=www,dc=google,dc=com`,LDAP在 10.78.26.241 - ### (1) 匿名查詢(sub 範圍,取 cn,mail) ```bash curl 'ldap://10.78.26.241:389/dc=www,dc=google,dc=com?cn,mail?sub?(uid=tj_tsai)' ``` - **錯誤訊息**: - `curl: (38) LDAP: cannot bind` - 不允許匿名,需要帳密 - ### (2) 帶 Bind DN/密碼(簡單繫結) ```bash curl -u 'cn=admin,dc=www,dc=google,dc=com:YourSecurePassword' \ 'ldap://10.78.26.241/dc=www,dc=google,dc=com??sub?(objectClass=posixAccount)' ``` - **範例執行**: ``` $ curl -u 'cn=admin,dc=asus,dc=com:YourSecurePassword' \ ldap://10.96.42.255:389 DN: objectClass: top objectClass: OpenLDAProotDSE ``` ``` $ curl -u 'cn=admin,dc=asus,dc=com:YourSecurePassword' \ ldap://10.96.42.255:389/dc=asus,dc=com DN: dc=asus,dc=com objectClass: top objectClass: dcObject objectClass: organization o: ASUS dc: asus ``` - ### (3) LDAPS(636/TLS 直連) ```bash curl --cacert /path/ca.crt \ -u 'cn=admin,dc=www,dc=google,dc=com:YourSecurePassword' \ 'ldaps://10.78.26.241:636/dc=www,dc=google,dc=com?uid,cn?sub?(uid=tj_tsai)' ``` - ### (4) STARTTLS(389,升級為 TLS) ```bash curl --starttls ldap --cacert /path/ca.crt \ -u 'cn=admin,dc=www,dc=google,dc=com:YourSecurePassword' \ 'ldap://10.78.26.241/dc=www,dc=google,dc=com??sub?(uid=tj_tsai)' ``` - ### (5) 只取 base 物件 ```bash curl 'ldap://10.78.26.241/dc=www,dc=google,dc=com??base?(objectClass=*)' ``` > 小撇步:URL 內若有空白或特殊字元請使用引號,必要時做 URL 編碼(如空白 `%20`)。 <br> ### 與 `ldapsearch` 的差異 * `curl` 的 LDAP支援**偏基本、以查詢為主**;進階操作(如分頁控制、特殊控制項、SASL/機制選擇、修改/新增/刪除)建議用 `ldapsearch/ldapmodify/ldapadd`。 * `curl` 輸出為「接近 LDIF」的文字;`ldapsearch` 在格式與選項上更完整、好用。 <br> ### 常見錯誤與排解 * `Protocol "ldap" not supported`:當前 `curl` 未啟用 LDAP;改裝有支援的套件或改用 `ldapsearch`。 * `Can't contact LDAP server`:主機/埠、網路或防火牆問題。 * `Invalid credentials`:Bind DN 或密碼錯。 * 憑證錯誤:提供 `--cacert /path/ca.crt`(不建議用 `-k` 降低驗證)。 需要我把你現有的 `ldapsearch` 查詢,等價改寫成 `curl` 範例嗎? <br> --- <br> ## 常見錯誤與判讀 | 錯誤/輸出 | 意義與排除 | | -------- | -------- | | `ldap_bind: Invalid credentials (49)` | 帳密錯(DN 不對、密碼錯、或該 DN 不允許 simple bind)。先確認 DN 拼法與管理者 DN 真的存在。 | | `Can't contact LDAP server (-1)` 或 `(81)` | 連不通:DNS、Service 名稱、Port、NetworkPolicy / 防火牆、Service/Pod 未啟動。 | | `Protocol error (2)`(配 `-ZZ`) | 伺服器沒啟用 StartTLS(或 TLS 協商問題)。改用 `ldaps://` 或在伺服器啟用 StartTLS。 | | `Invalid DN syntax (34)` | `-D` 或 `-b` 的 DN 格式不正確(少逗號、屬性名打錯)。 | | TLS/憑證錯誤(如 `unable to get local issuer certificate`) | 客戶端沒有信任的 CA。請把 CA 憑證配到容器 `/etc/openldap/certs/` 或透過 `LDAPTLS_CACERT`/`TLS_CACERT` 指定。 | - **除錯小技巧** - 加上 `-d 1`(或更高,如 `-d 2`)看除錯訊息。 - 檢查環境變數或 `/etc/openldap/ldap.conf` 中的 `TLS_CACERT` 設定。 - 用 `$?` 看退出碼:`0` 成功;非 0 代表失敗。 ```bash ldapwhoami -x -H ldap://... -D "..." -W ; echo $? ``` <br> ### additional info: anonymous bind disallowed ``` $ ldapsearch -x -H ldap://openldap.openldap \ -b "" -s base namingContexts -LLL ldap_bind: Inappropriate authentication (48) additional info: anonymous bind disallowed ``` - **原因**:不允許匿名查詢 <br> --- --- <br> ## 討論 ### [無法連線] `ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)` - ### Quesstion ``` $ ldapsearch -x -b "dc=asus,dc=com" "(uid=tj_tsai)" ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1) ``` - ### gpt-5 (2025/09/19) 下面這個錯誤訊息代表「**客戶端連不到任何 LDAP 伺服器**」,不是帳密或 Base DN 錯誤: ``` ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1) ``` 最常見原因:沒有指定伺服器 URI(預設會連 localhost:389)、伺服器沒啟動、連錯主機/埠、或被防火牆/TLS 擋下來。照這份「**最短查核清單**」一步做,通常就能定位問題。 --- > ### 兩種情況: > (A) 你在 **同一台裝有 slapd 的主機** 上(apt 安裝) > (B) 你在 **其他主機 / 容器 / Pod** 上查(最常見) --- ### (A) 你在 **同一台裝有 slapd 的主機** 上(apt 安裝) 1. 先確認服務有跑 ```bash sudo systemctl status slapd --no-pager sudo ss -ltnp | grep -E ':389|:636' ``` 2. 直接用 Unix socket(ldapi)查設定,驗證 **Naming Context(suffix)** 與 **管理者 DN** ```bash # 以 root 權限用本機 socket 連線,不走網路 sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b cn=config -LLL olcSuffix olcRootDN ``` 看輸出中的: * `olcSuffix`: 例如 `dc=google,dc=com` * `olcRootDN`: 例如 `cn=admin,dc=google,dc=com`(這就是你要用的 -D) 3. 用 ldapi 直接找資料(先不管網路問題) ```bash sudo ldapsearch -H ldapi:/// -Y EXTERNAL -b "" -s base -LLL namingContexts sudo ldapsearch -H ldapi:/// -x -b 'dc=google,dc=com' '(uid=tj_tsai)' -LLL dn ``` - 這兩條成功就表示 **資料已存在 + 伺服器正常運作**。 --- ### (B) 你在 **其他主機 / 容器 / Pod** 上查(最常見) > 關鍵:**一定要加 `-H ldap://<host>:<port>`** 指向正確的服務位置與埠。 1. 先確認你能連到 LDAP 埠 ```bash # 換成你的主機名或 Service 名稱與埠(常見 389 / 1389;LDAPS 常見 636 / 1636) nc -vz <ldap-host> 389 ``` 2. 用匿名 bind 讀 namingContexts(找出正確 Base DN) ```bash ldapsearch -H ldap://<ldap-host>:389 -x -b "" -s base -LLL namingContexts ``` 看到像 `namingContexts: dc=google,dc=com` 就代表伺服器可連,且 Base DN 是那個字串。 3. 搜尋你的使用者 (**建議用整體樹的 Base DN**,讓伺服器自己往下找,不要一開始就填整個使用者 DN 當 base) ```bash ldapsearch -H ldap://<ldap-host>:389 -x \ -b 'dc=google,dc=com' '(uid=tj_tsai)' -LLL dn uidNumber gidNumber homeDirectory loginShell ``` 4. 需要驗證帳密時(以管理者 DN 連線): ```bash ldapwhoami -H ldap://<ldap-host>:389 -x -D 'cn=admin,dc=google,dc=com' -W # 或搜尋: ldapsearch -H ldap://<ldap-host>:389 -x \ -D 'cn=admin,dc=google,dc=com' -W \ -b 'dc=google,dc=com' '(uid=tj_tsai)' -LLL dn ``` > 你的 LDIF 把條目放在 `dc=www,dc=google,dc=com` 底下。這 **仍屬於** `dc=google,dc=com` 這棵樹之內(`dc=www` 只是中間節點),**只要伺服器的 suffix 是 `dc=google,dc=com`,就用它當 -b**,搜尋會自己往下遞迴找到 `uid=tj_tsai`。 --- ### C)(選用)TLS/StartTLS 檢查 * LDAPS(636):`-H ldaps://<host>:636 -x ...` * StartTLS(389):`-H ldap://<host>:389 -ZZ -x ...` 若遇到憑證錯誤,可暫時用 `-d 1` 觀察,或把 CA 加到 `/etc/ldap/ldap.conf` 的 `TLS_CACERT`。 --- ### D) 常見踩雷速解 * **沒加 `-H`** → 預設連 localhost:389;在容器裡通常沒有 slapd,所以報 `Can't contact LDAP server (-1)`。 * **連錯埠** → 有些映像(或 K8s Service)用 **1389/1636**。請以實際 Service/Port 為準。 * **Base DN 用錯** → 先用 `-b "" -s base namingContexts` 查真正的 suffix。 * **admin DN 用錯** → 以 `ldapi:///` 查 `olcRootDN` 最準。 * **防火牆** → 伺服器要開放 389/636(或 1389/1636);K8s 要確認 Service/NetworkPolicy。 * **`I have no name!@...`** → 只是容器內沒有對應的 /etc/passwd 項,與 LDAP 連線無關。 --- ### E) 你這份資料的「最小可驗證」指令範例 > 假設伺服器 suffix 是 `dc=google,dc=com`,且用 389: 1. 匯入 ```bash ldapadd -H ldap://<ldap-host>:389 -x \ -D 'cn=admin,dc=google,dc=com' -W -f tj_tsai.ldif ``` 2. 搜尋(匿名或管理者都可) ```bash ldapsearch -H ldap://<ldap-host>:389 -x \ -b 'dc=google,dc=com' '(uid=tj_tsai)' -LLL dn uidNumber gidNumber homeDirectory loginShell ``` 如果以上第 2 步還是回 `Can't contact LDAP server (-1)`,那就回到 **A-1/B-1** 檢查服務是否在正確的主機與埠上對外監聽,以及你 `-H` 的主機與埠是否填對。 <br> --- ### [參數指令] SASL/EXTERNAL 這兩個參數差在哪裡? - ### gpt-5 (2025/09/21) 簡短說:**`SASL/EXTERNAL` 不是兩個獨立參數**。 * **SASL** 是一個「認證框架」(Simple Authentication and Security Layer)。 * **EXTERNAL** 是 **SASL 的其中一種機制**(mechanism),意思是「用外部安全層提供的身份」來認證。 在指令上你會看到:`-Y EXTERNAL` * `-Y`:選擇 **SASL 機制** * `EXTERNAL`:指定用 **EXTERNAL 機制** --- ### 它跟一般 `-x`(簡單綁定)差在哪? | 比較項 | `-Y EXTERNAL`(SASL/EXTERNAL) | `-x -D ... -W`(Simple Bind) | | ----- | ----- | ----- | | 身份來源 | 來自「外部安全層」 | 明文帳號密碼(或互動輸入) | | 常見外部安全層 | 1) **ldapi:/// 本機 Socket**:用作業系統的 **UID/GID**(root 最常見)<br>2) **TLS/SSL**:用 **用戶端憑證** 的主題資訊 | 無 | | 用途 | 本機管理(改 `cn=config`、載入 schema、ACL)、或憑證式無密碼登入 | 一般遠端用戶端登入、應用程式連線 | | 安全性 | 很高:不須密碼;依賴 OS/憑證信任鏈 | 視密碼強度與傳輸保護(要搭配 StartTLS/LDAPS) | | 需要先有 | 1) ldapi:`olcAuthzRegexp` 對應 OS 身份 → LDAP DN<br>2) TLS:CA 與客戶端憑證對應 DN | 建好帳號密碼即可 | | 指令例子 | `ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config dn`(本機免密)<br>`ldapwhoami -Y EXTERNAL -H ldaps://ldap.example.com`(憑證) | `ldapsearch -x -H ldap://host -D "cn=admin,dc=example,dc=com" -W ...` | --- ### 兩種「EXTERNAL」最常見情境 #### 1) 本機 `ldapi:///`(用 OS 身份) * 依靠 Unix Domain Socket 的 **peercred**(你的程序 UID/GID),常見是 **root**。 * 伺服器用 `olcAuthzRegexp` 把 OS 身份**映射**成可管理的 LDAP DN(例如 `cn=admin,cn=config`)。 **測試:** ```bash ldapwhoami -Y EXTERNAL -H ldapi:/// # 期望輸出:dn:cn=admin,cn=config 或 dn:gidNumber=...,+uidNumber=...,cn=peercred,cn=external,cn=auth ``` #### 2) `ldaps://` 或 `ldap://`+StartTLS(用客戶端憑證) * 你的客戶端握有 **x509 憑證**;伺服器信任該 CA,並將憑證的 Subject(或 SAN)**映射**到 LDAP DN。 **測試(憑證已配置):** ```bash ldapwhoami -Y EXTERNAL -H ldaps://ldap.example.com # 期望輸出:對應的 DN(例如:uid=tj,ou=users,dc=example,dc=com) ``` --- ### 快速判斷何時用哪個 * **在同一台機器上做管理**(改 `cn=config`、載 schema/ACL)→ 用 `ldapi:///` + `-Y EXTERNAL`(免密、最安全)。 * **遠端且要免密**(CI/CD、自動化)→ 配憑證,用 `ldaps://` + `-Y EXTERNAL`。 * **一般應用/帳號密碼** → `-x -D ... -W`,並搭配 StartTLS(`-ZZ`)或直接 `ldaps://`。 如果你告訴我現在是 **ldapi 本機管理** 還是 **TLS 憑證登入**,我可以給你對應的 `olcAuthzRegexp` 映射範例與測試指令。 <br> --- ### [參數指令] `ldap://` vs `ldaps://` vs `ldapi:///` - ### Question `ldap://` vs `ldaps://` vs `ldapi:///` 又是什麼縮寫?用途為何? - ### gpt-5 (2025/09/21) 好!你問的是這三個 URI scheme:`ldap://`、`ldaps://`、`ldapi:///`——各自是什麼縮寫、用途是什麼。快速對照在這裡: ### 1) `ldap://` * **全名**:**L**ightweight **D**irectory **A**ccess **P**rotocol(LDAP) * **傳輸**:TCP(預設埠 **389**),連線本身**不加密**,但可再「升級為 TLS」(StartTLS) * **用途**:一般網路用戶端連到 LDAP 伺服器;建議**搭配 StartTLS** 變成加密連線 * **何時用**:你要走 389 埠、需要防火牆相容性好、而且想用 **StartTLS(-ZZ)** 升級加密時 * **例**: ```bash ldapsearch -x -H ldap://ldap.example.com -b dc=example,dc=com -ZZ # -ZZ = 要求在 ldap:// 上啟動 StartTLS ``` ### 2) `ldaps://` > `http://` & `https://` <--> `ldap://` & `ldaps://` * **全名**:**LDAP over SSL/TLS**(俗稱「LDAPS」) * **傳輸**:TLS 直連(預設埠 **636**),一開始就加密 * **用途**:歷史上常見的「固定 636、永遠加密」作法;現在仍然廣泛使用 * **何時用**:你的環境或既有設備只接受 636/TLS 直連,或你想**強制所有連線都加密**(不走 StartTLS) * **例**: ```bash ldapsearch -x -H ldaps://ldap.example.com:636 -b dc=example,dc=com ``` ### 3) `ldapi:///` * **全名**:**LDAP over IPC**(以本機 **Unix Domain Socket** 行程間通訊) * **傳輸**:走本機 socket(預設路徑常是 `/run/slapd/ldapi`),**不經網路、不用埠號** * **用途**:**伺服器本機管理**、自動化腳本、套件後設設定;可用 **SASL/EXTERNAL** 做「免密、以作業系統身份」的管理(很方便改 `cn=config`) * **何時用**:你在**同一台主機**上維護 slapd;要改核心設定、灌 schema、調 ACL * **例**: ```bash ldapsearch -Y EXTERNAL -H ldapi:/// -b cn=config dn # 以作業系統帳號身份(root)做本機管理,不需密碼 ``` --- ### 小結(怎麼選) * 想走 **389** 又要加密 → `ldap://` + **StartTLS(-ZZ)** * 想一律加密、或相容既有 636 → `ldaps://` * 在**本機**做管理、改 `cn=config`、不用密碼 → `ldapi:///` + **SASL/EXTERNAL** 需要我用你的實際網域/Base DN,幫你寫三種連線的「可直接貼上執行」的測試指令嗎? <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}