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 %}