`tj_tsai.ldif` (個人資料)
===
###### tags: `LDAP`
###### tags: `LDAP`, `ldif`, `tj_tsai`
<br>
[TOC]
<br>
## 背景知識
- [[HackMD] LDAP 相關術語](/vW9loVLbQt6R7uMMcM5zrQ)
- [LDAP schema reference](https://docs.pingidentity.com/pingds/8/schemaref/preface.html)
- [Object classes](https://docs.pingidentity.com/pingds/8/schemaref/object-classes.html)
- [Attribute types](https://docs.pingidentity.com/pingds/8/schemaref/attribute-types.html)
- [[HackMD] ldapsearch 指令與相關參數](/YWXj3DaKT3SDV4Rq2OTPrw?both)
- [[HackMD] slappasswd](/GHd7jYgZTzexmpkkxOADeA)
<br>
:::info
## 個人資料
```
- dc:com
- dc:google
- dc:www
- ou:ocis
- ou:div3
- ou:dep2
- ou:sec1
- uid:tj_tsai
```
- email: `tj_tsai@gmail.com`
- ext: 39981 (分機)
- ubuntu:
- name: `tj_tsai`
- uid: 10001
- gid: 10001
:::
<br>
## 設計 `tj_tsai` 的 LDAP 資料
> 小提醒
>
> * 一般會用 `dc=google,dc=com` 當 Base DN;是否要多一層 `dc=www` 視你的命名政策而定。下面範例已照你的結構寫好。
> * `inetOrgPerson` 需要 `sn`(姓氏);我先用 `tsai` 佔位。
> * 若 `gidNumber=10001` 的群組尚未存在,下面也一併建立一個 `posixGroup`。
---
### 步驟1:準備個人資料 `tj_tsai.ldif`
```ldif=
# 1) 階層(不存在時先建立;存在就略過)
dn: ou=ocis,dc=www,dc=google,dc=com
objectClass: organizationalUnit
ou: ocis
dn: ou=div3,ou=ocis,dc=www,dc=google,dc=com
objectClass: organizationalUnit
ou: div3
dn: ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
objectClass: organizationalUnit
ou: dep2
dn: ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
objectClass: organizationalUnit
ou: sec1
# 2) 群組(可選:若 10001 尚未存在)
dn: cn=gid10001,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
objectClass: posixGroup
cn: gid10001
gidNumber: 10001
memberUid: tj_tsai
# 3) 使用者(建議用 uid 當 RDN;若要用 cn,將 dn 第一行改成 cn=tj_tsai,...)
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
# 用 slappasswd 產生 SSHA 雜湊後,替換下一行
userPassword: {SSHA}rzc8Z7vrdQIOOiezOmmoJQxg6XnrZivI
# (userPassword 很重要,不是亂填的!)
# (-w 123456)
```
- ### userPassword 欄位填寫
- ### 產生密碼雜湊方式
> 背景知識:[[HackMD] slappasswd](/GHd7jYgZTzexmpkkxOADeA)
```bash
slappasswd -h {SSHA} -s '你的強密碼'
# 複製輸出(例如 {SSHA}8Hk...),貼到 LDIF 的 userPassword 欄位
```
- **安裝方式**
```
sudo apt install slapd -y
# 輸入 admin 密碼:123456
$ slappasswd -h {SSHA} -s 123456
{SSHA}4khG9Pq9hDOF1MjqJLaOjTApKkDSyvMQ
$ slappasswd -h {SSHA} -s 123456
{SSHA}LrvHoHNWlKHiL8Wwdha/wQcQPvGw5qYi
```
- 每次會產生不同的雜湊,不要緊,選擇任意一個雜湊來用,雜湊中會記錄 salt
- 登錄時,明碼密碼會串接所選雜湊中的 salt,再使用 SHA1 產生雜湊,雜湊相同表示密碼正確
- ### 在匯入資料後,可用底下方式驗證 tj_tsai 帳密
```
# 在任何可以連到 openldap
$ 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
# 成功回傳:dn:...
dn:uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
# 失敗回傳:
ldap_bind: Invalid credentials (49)
```
---
### 步驟2:[選用][前製作業] 建立指令測試環境
> 背景知識:
> - [[HackMD] 在 Kubernetes 上部署 OpenLDAP 的詳細教學](/4BzU5X-XQze7Z9IXg0JiAw)
> - [[HackMD] 在 Ubuntu 上安裝 OpenLDAP 的詳細教學](/JsLFQ_bvSQKvSO46ewWiyQ)
- ### 在 Kubernetes (K8s) 上
- ### 有 root 的 pod
```bash
kubectl run ububtu-2404 -it --rm --image=ubuntu:24.04 -- bash
apt update
# install ldap commands:
# - ldapadd
# - ldapcompare
# - ldapdelete
# - ldapexop
# - ldapmodify
# - ldapmodrdn
# - ldappasswd
# - ldapsearch
# - ldapurl
# - ldapwhoami
apt install ldap-utils -y
```
- ### 沒有 root 的 pod
```
kubectl run ldap-test --rm -it --image=bitnami/openldap:latest -- bash
```
- ### 先測試「管理者」的帳密可以通
```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" -LLL
```
---
### 匯入指令
> 管理者 DN 依你的資料庫 suffix 而定:
>
> * 若資料庫 suffix 是 `dc=google,dc=com`,用 `cn=admin,dc=google,dc=com`
> * 若另開一個 suffix `dc=www,dc=google,dc=com`,則用 `cn=admin,dc=www,dc=google,dc=com`
```bash
ldapadd -x -D "cn=admin,dc=google,dc=com" -W -f tj_tsai.ldif
```
- ### 執行結果
```
adding new entry "ou=ocis,dc=www,dc=google,dc=com"
adding new entry "ou=div3,ou=ocis,dc=www,dc=google,dc=com"
adding new entry "ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com"
adding new entry "ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com"
adding new entry "cn=gid10001,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com"
adding new entry "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com"
```
---
### 用指令驗證
- ### 用 uid 找人
```bash
# 預設本地端主機,會連 localhost:389
ldapsearch -x -b "dc=www,dc=google,dc=com" "(uid=tj_tsai)"
# 若是本機端無 LDAP 服務,則會有底下錯誤:
# ldap_sasl_bind(SIMPLE): Can't contact LDAP server (-1)
# 其他主機 / 容器 / Pod 上查 (假設需帳密)
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" \
"(uid=tj_tsai)" \
-LLL
```
- ### 執行結果 (攜帶 `-LLL`)
```ldif=
dn: uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
objectClass: top
cn: tj_tsai
sn: tsai
givenName: tj
displayName: tj_tsai
mail: tj_tsai@gmail.com
telephoneNumber: 29981
uid: tj_tsai
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/tj_tsai
loginShell: /bin/bash
userPassword:: e1NTSEF9UkVQTEFDRV9XSVRIX0hBU0g=
gecos: tj_tsai
```
- **`userPassword` 欄位**
- `::` 表示 base64 編碼
- base64 解碼方式
```
$ echo "e1NTSEF9UkVQTEFDRV9XSVRIX0hBU0g=" | base64 -d
{SSHA}REPLACE_WITH_HASH
```
- ### ldapsearch 不帶 `-D` & `-w` 管理者帳密
回傳結果不包含 `userPassword` 欄位
- ### 看 Unix 必備屬性(uidNumber/gidNumber/homeDirectory/loginShell)
```
# 預設本地端主機,會連 localhost:389
ldapsearch -x -b "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com"
# 其他主機 / 容器 / Pod 上查 (假設需帳密)
ldapsearch -x -H ldap://openldap.openldap:389 \
-D "cn=admin,dc=www,dc=google,dc=com" \
-w "YourSecurePassword" \
-b "uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \
-LLL
```
- ### 執行結果 (不攜帶 `-LLL` 參數)
```ldif=
# extended LDIF
#
# LDAPv3
# base <uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
# tj_tsai, sec1, dep2, div3, ocis, www.google.com
dn: uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
objectClass: top
cn: tj_tsai
sn: tsai
givenName: tj
displayName: tj_tsai
mail: tj_tsai@gmail.com
telephoneNumber: 29981
uid: tj_tsai
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/tj_tsai
loginShell: /bin/bash
userPassword:: e1NTSEF9UkVQTEFDRV9XSVRIX0hBU0g=
gecos: tj_tsai
# search result
search: 2
result: 0 Success
# numResponses: 2
# numEntries: 1
```
- ### 執行結果 (攜帶 `-LLL` 參數)
```ldif=
dn: uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
objectClass: top
cn: tj_tsai
sn: tsai
givenName: tj
displayName: tj_tsai
mail: tj_tsai@gmail.com
telephoneNumber: 29981
uid: tj_tsai
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/tj_tsai
loginShell: /bin/bash
userPassword:: e1NTSEF9UkVQTEFDRV9XSVRIX0hBU0g=
gecos: tj_tsai
```
- ### ldapsearch 不帶 `-D` & `-w` 管理者帳密
回傳結果不包含 `userPassword` 欄位
<br>
### [UI] 透過 phpLDAPadmin 匯入

<br>
### [UI] 透過 phpLDAPadmin 查看
- `cn=gid10001`

- `uid=tj_tsai`

---

<br>
---
## 常見錯誤訊息
### Object class violation
Could not add the object to the LDAP server.
- ### Error
- **LDAP said**: Object class violation
- **Error number**: 0x41 (LDAP_OBJECT_CLASS_VIOLATION)
- **Description**:
You tried to perform an operation that would cause an undefined attribute to exist or that would remove a required attribute, given the current list of ObjectClasses. This can also occur if you do not specify a structural objectClass when creating an entry, or if you specify more than one structural objectClass.
- ### gpt-5 (2025/10/15)
```ldif=
dn: uid=tj_tsai,ou=users,ou=ocis,dc=www,dc=google,dc=com
objectClass: posixAccount
cn: TJ Tsai
uid: tj_tsai
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/tj_tsai
loginShell: /bin/bash
userPassword: {SSHA}w2XNVRJUmQjanSsYMJ6cphGbw194M/k+
```
你這筆條目只有 `objectClass: posixAccount`,而你的伺服器套的是 **RFC2307bis** 架構:
`posixAccount` 在這個架構是 **AUXILIARY(輔助類別)**,不是結構類別。LDAP 每個條目**一定要有且只有一個 STRUCTURAL(結構)objectClass**,所以會報:
> `LDAP_OBJECT_CLASS_VIOLATION (0x41)`(沒有結構類別 / 或缺必要屬性)
有兩種常見修法,擇一使用即可:
---
### 方案 A(最常用):inetOrgPerson + posixAccount (+ shadowAccount)
`inetOrgPerson` 是 **STRUCTURAL**,需要 `sn`(姓氏)與 `cn`。
```ldif
dn: uid=tj_tsai,ou=users,ou=ocis,dc=www,dc=google,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: TJ Tsai
sn: Tsai
givenName: TJ
uid: tj_tsai
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/tj_tsai
loginShell: /bin/bash
userPassword: {SSHA}w2XNVRJUmQjanSsYMJ6cphGbw194M/k+
```
> 若你不需要個人資訊欄位,也可省 `givenName`、`mail` 等。
---
### 方案 B(極簡帳號):account + posixAccount (+ shadowAccount)
`account` 是 **STRUCTURAL**,必備 `uid`。
```ldif
dn: uid=tj_tsai,ou=users,ou=ocis,dc=www,dc=google,dc=com
objectClass: account
objectClass: posixAccount
objectClass: shadowAccount
uid: tj_tsai
cn: TJ Tsai
uidNumber: 10000
gidNumber: 10000
homeDirectory: /home/tj_tsai
loginShell: /bin/bash
userPassword: {SSHA}w2XNVRJUmQjanSsYMJ6cphGbw194M/k+
```
---
### 建議再補一個對應群組(可選,但實務常見)
```ldif
dn: cn=gid10000,ou=groups,ou=ocis,dc=www,dc=google,dc=com
objectClass: posixGroup
cn: gid10000
gidNumber: 10000
memberUid: tj_tsai
```
---
### 匯入與驗證
```bash
# 匯入
ldapadd -x -H ldap://openldap.openldap \
-D "cn=admin,dc=www,dc=google,dc=com" -w 'YourSecurePassword' \
-f user.ldif
# 驗證
ldapsearch -x -H ldap://openldap.openldap \
-b "dc=www,dc=google,dc=com" "(uid=tj_tsai)" -LLL
```
### 為什麼你會遇到這個錯誤?
* 你的 OpenLDAP 採用的 schema 把 `posixAccount` 定義為 **AUXILIARY**;
* 條目沒有 STRUCTURAL 類別(如 `inetOrgPerson` 或 `account`),因此違反 objectClass 規則。
照上面兩個方案其一調整後,匯入就會過。
<br>
---
## 討論
### gecos 欄位用途為何?
- ### gpt-5 (2025/09/16)
簡單說:**`gecos` 是用來放「使用者的真實姓名與聯絡備註」的欄位**。
在 Linux 的 `/etc/passwd` 第 5 欄就是 GECOS;在 LDAP 的 `posixAccount` 也常見同名屬性,用來對應這個資訊。
### 為什麼需要它?
* 讓系統或工具顯示人類可讀的名稱(例如 `finger`、顯示登入提示、GUI 使用者清單)。
* 可附帶聯絡資訊(歷史上用 **逗號 ,** 分成幾段):
1. 全名(最常用)
2. 辦公室/房號
3. 公司電話
4. 住家電話
5. 其他備註
* 不影響登入/權限,**純描述用**;安全上別放敏感資訊。
### 注意事項
* `/etc/passwd` 用 **冒號 :** 分欄,**GECOS 裡不可包含冒號或換行**;逗號可用來分子欄位。
* 現代目錄常把正式資料放在 `cn`、`displayName`、`telephoneNumber`、`roomNumber` 等屬性;`gecos` 多半只是為了與傳統 `passwd` 對應。
### 範例
**/etc/passwd** 一行(第 5 欄為 GECOS):
```
alice:x:1001:1001:Alice Lee,Room 123,02-2345-6789,,:/home/alice:/bin/bash
```
**LDAP(posixAccount)**:
```
dn: uid=alice,ou=users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
cn: Alice Lee
sn: Lee
uid: alice
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/alice
loginShell: /bin/bash
gecos: Alice Lee,Room 123,02-2345-6789,,
```
### 常用操作
* **查看(本機)**:
```bash
getent passwd alice | cut -d: -f5
# 或
finger alice # 若 finger 已安裝
```
* **設定/修改(本機)**:
```bash
sudo usermod -c "Alice Lee,Room 123,02-2345-6789,," alice
# 或互動式
sudo chfn alice
```
* **新增使用者時帶入(Debian/Ubuntu 的 adduser)**:
```bash
sudo adduser --gecos "Alice Lee,Room 123,02-2345-6789,," alice
```
* **與 LDAP 整合(nsswitch/SSSD)時**:`gecos` 會被映射成 `/etc/passwd` 的第 5 欄給應用程式讀取;正式欄位仍建議用 `cn`、`displayName`、`telephoneNumber` 等作權威資料。
<br>
### [彩蛋] GECOS 原名
- **GECOS** 的原名是 **General Electric Comprehensive Operating Supervisor**(通用電氣綜合作業監督系統)。
- 後來該系統改名為 **GCOS = General Comprehensive Operating System**。
- Linux `/etc/passwd` 裡的 `gecos` 欄位就是承襲這段歷史命名,但如今僅作「使用者資訊(全名/備註)」之用。
<br>
---
<br>
### shadowAccount 用途又是為何?
> https://docs.pingidentity.com/pingds/8/schemaref/oc-shadowAccount.html
- ### Question
```
OID:1.3.6.1.1.1.2.1
Names:shadowAccount
Description:Additional attributes for shadow passwords
Superior classes:top
Class type:AUXILIARY: for use in augmenting attributes of entries that already have a structural object class.
```
shadowAccount 用途又是為何?
- ### gpt-5 (2025/09/16)
下面這句話先記住就好:
**`shadowAccount` 是一個「輔助 (auxiliary) 物件類別」用來把 `/etc/shadow` 的「密碼到期/老化」欄位搬到 LDAP 裡集中管理**(RFC 2307,OID `1.3.6.1.1.1.2.1`)。它通常會跟 `posixAccount`、`inetOrgPerson` 一起掛在同一個使用者條目上。
### 它解決什麼?
* 讓 Linux 用戶端(NSS/SSSD/PAM)能從 LDAP 讀到與 `/etc/shadow` 相同語意的欄位,進而套用:密碼最短/最長使用天數、到期警告、到期後寬限期、帳號到期日…等規則。
* 適合要把登入帳號統一由 LDAP 發佈,又希望保留傳統 shadow 密碼老化行為的環境。
### 主要屬性與意義(單位除特別標註外皆為「天」)
| 屬性 | 用途 |
| ------------------ | ---------------------------------------------------------------- |
| `shadowLastChange` | 上次更改密碼的日期(**自 1970-01-01 起算的天數**)。 |
| `shadowMin` | 兩次變更密碼間的**最少天數**(未達則拒絕改密碼)。 |
| `shadowMax` | 密碼**最長有效天數**(超過即視為過期)。 |
| `shadowWarning` | 密碼將到期前**提前警告天數**。 |
| `shadowInactive` | 密碼過期後,**再過幾天**就鎖定帳號(未登入即失效)。 |
| `shadowExpire` | **帳號到期日**(同樣是自 1970-01-01 起算的天數;空值/未設表示不限制)。 |
| `shadowFlag` | 保留欄位,通常設 0。 |
| `userPassword` | 多數實作允許放在此(雜湊值,如 `{SSHA}`、`{SHA512-CRYPT}`);也常放在 `inetOrgPerson`。 |
> 小提醒:`shadowLastChange`/`shadowExpire` 用「天數自 Epoch 起算」,換算指令例:
> `date -d "1970-01-01 + <天數> days" +%F`
### 為什麼是 AUXILIARY?
`shadowAccount` 不是結構類別(non-structural),**不能單獨當一筆使用者條目的骨幹**,而是**用來「加掛」** 到已經有結構類別(如 `inetOrgPerson` 或 `posixAccount`)的條目上,補上 shadow 欄位。
### 範例 LDIF
```ldif
dn: uid=alice,ou=users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: Alice Lee
sn: Lee
uid: alice
uidNumber: 1001
gidNumber: 1001
homeDirectory: /home/alice
loginShell: /bin/bash
userPassword: {SSHA}A1B2C3...
shadowLastChange: 19990
shadowMin: 0
shadowMax: 90
shadowWarning: 7
shadowInactive: 14
shadowExpire: 21000
shadowFlag: 0
```
### 在用戶端如何生效?
* 使用 **SSSD** 或 **nslcd** 搭配 PAM(`pam_unix`/`pam_sss`),客戶端會讀取這些屬性來判斷密碼是否到期、是否可更改、是否應提示警告等。
* 一些指令(如 `chage -l alice`)在使用 LDAP 時也能顯示這些資訊(取決於你用的 NSS/SSSD 與 PAM 設定)。
### 常見注意事項
* **單位是「天」且自 1970-01-01 起算**,不是秒;別混用。
* 若同時採用 OpenLDAP 的 **ppolicy**(密碼政策 overlay),要決定以哪套為準,避免規則打架(SSSD 有 `ldap_pwexpire_policy = shadow` 等設定可選擇行為)。
* 未設定 `shadow*` 時,通常視為**不啟用老化/到期限制**(依各 PAM/NSS 實作預設)。
需要我幫你把你現有的 LDAP 使用者條目補上合適的 `shadow*` 參數(例如企業常見:`Max=90, Warning=7, Inactive=14`)的範例值嗎?
<br>
---
<br>
### shadowAccount 參數意義
> https://docs.pingidentity.com/pingds/8/schemaref/oc-shadowAccount.html
- ### Question
簡單說明底下參數意義:
shadowMin: 0
shadowMax: 90
shadowWarning: 7
shadowInactive: 14
shadowExpire: 21000
shadowFlag: 0
- ### gpt-5 (2025/09/16)
以下皆以「天」為單位,除 `shadowExpire` 外都以**上次改密碼日 (`shadowLastChange`)** 為基準:
* **shadowMin: 0**
上次改密碼後最少等待 0 天即可再次改密碼(= 隨時可改)。
* **shadowMax: 90**
密碼最長有效 90 天;超過即視為過期,需更改。
* **shadowWarning: 7**
密碼到期前 7 天開始提醒使用者更改。
* **shadowInactive: 14**
密碼過期後再過 14 天仍未更改,帳號即被鎖定(停用登入)。
* **shadowExpire: 21000**
帳號「絕對到期日」,自 1970-01-01 起算第 21000 天,即 **2027-07-01**,到這天起帳號失效(與密碼是否過期無關)。
* **shadowFlag: 0**
保留欄位,通常固定為 0(不使用)。
<br>
---
<br>
### `userPassword: {SSHA}REPLACE_WITH_HASH` 的疑惑
- ### Question
```
# 用 slappasswd 產生 SSHA 雜湊後,替換下一行
userPassword: {SSHA}REPLACE_WITH_HASH
```
`"{SSHA}REPLACE_WITH_HASH"` 就是我使用的密碼
一定要 slappasswd 工具產生,才能當密碼嗎?
- ### 背景知識
- [[HackMD] slappasswd](/GHd7jYgZTzexmpkkxOADeA)
- ### gpt-5 (2025/09/24)
不是一定要用 `slappasswd`,但你目前的用法有個關鍵誤會:
* `-w` **永遠填「原始明碼密碼」**,不是 `{SSHA}...`,也不是 base64。
* `userPassword` 裡的 `{SSHA}REPLACE_WITH_HASH` 在教學裡只是**佔位字串**。它不是合法雜湊,更不是可拿來登入的密碼。
要讓登入成功,你有三種做法,擇一即可:
---
## 做法 A(最省事,推薦):用 `ldappasswd` 讓伺服器自動雜湊
這樣你**只要給明碼**,伺服器會依設定(通常是 `{SSHA}` 或你設定的 `olcPasswordHash`)自動轉成雜湊存進 `userPassword`。
```bash
# 以管理者改使用者密碼(伺服器自動雜湊)
ldappasswd -x -H ldap://10.78.26.241:389 \
-D "cn=admin,dc=www,dc=google,dc=com" -w "12" \
"uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com" \
-s 'MyS3cret!'
# 立即測試(注意:-w 是明碼)
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 'MyS3cret!'
```
> 若你想指定雜湊演算法(如 `{SSHA}` 或 `{SSHA512}`),在 `cn=config` 設 `olcPasswordHash` 後再用 `ldappasswd` 設密碼即可。
---
## 做法 B:客戶端先產生合法雜湊,再用 `ldapmodify` 寫入
可以用 `slappasswd`(最簡單),或自己用任意語言算出 **SHA1(密碼+salt) 再附上 salt**,然後 base64;最後組成 `{SSHA}<base64>` 字串。
```bash
HASH=$(slappasswd -h {SSHA} -s 'MyS3cret!') # 會回類似 {SSHA}6y0... 一長串
cat > update-password.ldif <<EOF
dn: uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
changetype: modify
replace: userPassword
userPassword: $HASH
EOF
ldapmodify -x -H ldap://10.78.26.241:389 \
-D "cn=admin,dc=www,dc=google,dc=com" -w "12" \
-f update-password.ldif
```
接著用明碼 `MyS3cret!` 測試 bind(跟上面一樣)。
---
## 做法 C(僅測試用,不安全):直接存明碼
你也可以**直接存明碼**到 `userPassword`,這樣登入就用那個明碼。之後再改成雜湊。
```ldif
dn: uid=tj_tsai,ou=sec1,ou=dep2,ou=div3,ou=ocis,dc=www,dc=google,dc=com
changetype: modify
replace: userPassword
userPassword: mycleartext
```
```bash
ldapmodify -x -H ldap://10.78.26.241:389 \
-D "cn=admin,dc=www,dc=google,dc=com" -w "12" \
-f clear.ldif
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 'mycleartext'
```
---
### 你原本的狀況為什麼會失敗?
* `userPassword:: e1NTSEF9UkVQTEFDRV9XSVRIX0hBU0g=` 是 **ldapsearch 把值 base64 顯示** 的結果;解碼後是字串 `{SSHA}REPLACE_WITH_HASH`,表示你真的把**佔位字串**存進去了。
* slapd 看到字首 `{SSHA}`,會把它當成「雜湊值」,但後面不是合法的 base64(對應到雜湊+salt 的位元組),所以**比對一定失敗**。
* 你拿 `{SSHA}REPLACE_WITH_HASH` 或它的 base64 去當密碼也不會成功——**可登入的永遠是原始明碼**(例如 `MyS3cret!`),而不是雜湊字串本身。
> 進階:如果你真的「想把字面上包含 `{SSHA}` 的字串當密碼」(不建議),可存成 `{CLEARTEXT}{SSHA}REPLACE_WITH_HASH` 才會被當作純文字處理;但實務上別這麼做。
需要的話,我可以幫你檢查目前 slapd 的 `olcPasswordHash` 與(可選的)`ppolicy` 設定,順便給一份一次到位的調整指令。
<br>
---
<br>
### 如果要定義很多人,一般要如何規劃 ldif 資料?
- ### gpt-5 (2025/09/12)
下面整理一份「多使用者」LDIF 規劃做法+範本,讓你能穩定擴充、版本控管、且便於維運(大量建立/修改/加群組)。
### 規劃原則(精華版)
1. **DIT 佈局**
* 建議**扁平放人**:`ou=people,dc=google,dc=com`(或你的 `dc=www,dc=google,dc=com`),避免把部門層級寫進 DN,改用屬性紀錄(如 `departmentNumber: div3/dep2/sec1`)。
* 群組獨立:`ou=groups,...`(Unix 用 `posixGroup`,系統/應用可用 `groupOfNames`)。
2. **RDN 命名**
* 以 **`uid=<login>`** 作 RDN 最穩(例:`uid=tj_tsai,ou=people,...`),比 `cn=` 不易變動。
3. **物件類別**
* 一般:`inetOrgPerson`(人資/通訊錄屬性)。
* Unix/Linux 整合:加 `posixAccount`、`shadowAccount`。
4. **必要屬性最小集**
* `uid, cn, sn, mail, uidNumber, gidNumber, homeDirectory, loginShell, userPassword`
* 擴充:`telephoneNumber`(分機)、`departmentNumber`、`manager`(DN)、`employeeNumber` 等。
5. **UID/GID 發號策略**
* 制定規則(如從 10000 起連號),**單一來源**維護(CSV/DB),LDIF 由此產生,避免衝突。
6. **群組策略**
* Unix 權限用 `posixGroup` 的 `gidNumber`、`memberUid`;
* App 權限用 `groupOfNames` 的 `member: <使用者 DN>`;**不要以 OU 當權限依據**。
7. **檔案切割與順序**
* `00_base_ou.ldif`(基礎 OU)→ `10_groups.ldif`(群組/ gid)→ `20_people_*.ldif`(人員)→ `30_membership.ldif`(加人進群組)。
* 一次匯多檔,比一個大檔更好維運與除錯。
8. **變更管理**
* 新增:內容紀錄(預設 `add`)。
* 更新/加群組:用 **Change Record**(`changetype: modify`)。
* 調部門:若 DN 不含部門就**不需改 DN**;只改 `departmentNumber` 即可。
* DN 必須改時(真的用到多層 OU):`changetype: modrdn`。
9. **版本控管與回滾**
* LDIF 全部進 Git;重大操作前 `slapcat` 備份,可回滾。
10. **密碼與政策**
* 密碼用 `slappasswd` 產生雜湊(SSHA/BCRYPT);
* 可啟用 `ppolicy`(密碼規則、到期、鎖定)—策略由 LDIF 佈署。
---
### 檔案結構(建議)
```
ldap/
00_base_ou.ldif
10_groups.ldif
20_people_div3.ldif
20_people_div5.ldif
30_membership.ldif
```
#### 00\_base\_ou.ldif(基礎樹)
> 若你堅持 `dc=www,dc=google,dc=com`,把下方 `dc=google,dc=com` 改成你的 suffix 即可。
```ldif
dn: ou=people,dc=google,dc=com
objectClass: organizationalUnit
ou: people
dn: ou=groups,dc=google,dc=com
objectClass: organizationalUnit
ou: groups
```
#### 10\_groups.ldif(群組與 gid)
> 建立 Unix 群組(`posixGroup`)與應用群組(`groupOfNames`)。
```ldif
# Unix 群組:tn,gidNumber 10001
dn: cn=tn,ou=groups,dc=google,dc=com
objectClass: posixGroup
cn: tn
gidNumber: 10001
# App 群組(可選):data-science
dn: cn=data-science,ou=groups,dc=google,dc=com
objectClass: groupOfNames
cn: data-science
member: uid=admin1,ou=people,dc=google,dc=com # 先放管理者;之後再補成員
```
#### 20\_people\_div3.ldif(人員批次)
> 扁平放在 `ou=people`,用屬性標記部門/單位。
```ldif
# tj_tsai
dn: uid=tj_tsai,ou=people,dc=google,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: tj_tsai
cn: TJ Tsai
sn: Tsai
givenName: TJ
mail: tj_tsai@google.com
telephoneNumber: 39981
departmentNumber: ocis/div3/dep2/sec1
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/tj_tsai
loginShell: /bin/bash
gecos: TJ Tsai
userPassword: {SSHA}REPLACE_WITH_HASH
# 另一位(示例)
dn: uid=alice,ou=people,dc=google,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: alice
cn: Alice Chen
sn: Chen
givenName: Alice
mail: alice@google.com
telephoneNumber: 29888
departmentNumber: ocis/div3/dep2/sec1
uidNumber: 10002
gidNumber: 10001
homeDirectory: /home/alice
loginShell: /bin/bash
gecos: Alice Chen
userPassword: {SSHA}REPLACE_WITH_HASH
```
#### 30\_membership.ldif(把人加進群組)
> 用 **變更記錄**,可重複執行(具可重放性,維運友善)。
```ldif
# 加入 Unix 群組(posixGroup)
dn: cn=tn,ou=groups,dc=google,dc=com
changetype: modify
add: memberUid
memberUid: tj_tsai
memberUid: alice
-
#(若已存在會報錯,可改用 replace 或分行單獨 add)
# 加入應用群組(groupOfNames)
dn: cn=data-science,ou=groups,dc=google,dc=com
changetype: modify
add: member
member: uid=tj_tsai,ou=people,dc=google,dc=com
member: uid=alice,ou=people,dc=google,dc=com
```
---
### 匯入/更新流程
```bash
# 先建 OU / 群組 / 人員,再補群組成員
ldapadd -x -D "cn=admin,dc=google,dc=com" -W -f ldap/00_base_ou.ldif
ldapadd -x -D "cn=admin,dc=google,dc=com" -W -f ldap/10_groups.ldif
ldapadd -x -D "cn=admin,dc=google,dc=com" -W -f ldap/20_people_div3.ldif
# 變更(加成員)
ldapmodify -x -D "cn=admin,dc=google,dc=com" -W -f ldap/30_membership.ldif
```
---
### 批次產生 LDIF(建議流程)
1. **單一資料源**(CSV/Excel/DB):維護 `uid, cn, sn, givenName, mail, telephoneNumber, uidNumber, gidNumber, departmentNumber, loginShell` 等欄。
2. **模板化**:用 Jinja2/ERB 產生 LDIF(避免手刻)。
3. **雜湊密碼**:先批次產生 `{SSHA}` 並放入欄位或改為初次登入強制改密碼流程。
4. **CI 驗證**:匯入前跑靜態檢查(欄位完整性、id 唯一性、DN 合法)。
簡易範例(概念示意,非必要):
```jinja2
{# people.ldif.j2 #}
{% for u in users -%}
dn: uid={{u.uid}},ou=people,dc=google,dc=com
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: {{u.uid}}
cn: {{u.cn}}
sn: {{u.sn}}
givenName: {{u.givenName}}
mail: {{u.mail}}
telephoneNumber: {{u.ext}}
departmentNumber: {{u.dept}}
uidNumber: {{u.uidNumber}}
gidNumber: {{u.gidNumber}}
homeDirectory: /home/{{u.uid}}
loginShell: /bin/bash
gecos: {{u.cn}}
userPassword: {{u.password_hash}}
{% endfor -%}
```
---
### 驗證查詢(抽樣檢核)
```bash
# 抓某 OU 所有人(若扁平即抓 ou=people)
ldapsearch -x -b "ou=people,dc=google,dc=com" "(objectClass=inetOrgPerson)" uid mail uidNumber gidNumber
# 看群組成員(posixGroup)
ldapsearch -x -b "ou=groups,dc=google,dc=com" "(cn=tn)" cn gidNumber memberUid
# 看 App 群組(groupOfNames)
ldapsearch -x -b "ou=groups,dc=google,dc=com" "(cn=data-science)" cn member
```
---
### 小結
* **人放 `ou=people`、權限看群組**是大規模目錄最省事的規劃。
* **RDN 用 `uid=`**,避免人事異動牽動 DN。
* **CSV/模板→LDIF→Git 版本控管**,搭配 `ldapadd/ldapmodify`,即可安全批次上線。
* **變更用 change-record**,可重放、好追溯。
如果你決定維持目前的 `dc=www,dc=google,dc=com` 與深層部門 OU,也可以——建議仍然把人放 `ou=people`,把 `ocis/div3/dep2/sec1` 寫進 `departmentNumber` 或自定屬性即可,遷調就不會牽動 DN。需要我把上面範本換成你的 `dc=www,dc=google,dc=com` 版,我可以直接幫你改好。
<br>
---
<br>
### UPG:每位使用者各有一個群組
> i.e. `gidNumber == uidNumber`、`cn == uid`
> by gpt-5 (2025/10/18)
- ### UPG
User Private Group(使用者私有群組)
- ### LDAP 條目(每人一個 UPG)
- ### 群組(posixGroup)
```ldif=
dn: cn=<uid>,ou=groups,ou=ocis,dc=google,dc=com
objectClass: top
objectClass: posixGroup
cn: <uid>
gidNumber: <同 uidNumber>
memberUid: <uid>
```
- ### 使用者(posixAccount)
```ldif=
dn: uid=<uid>,ou=users,ou=ocis,dc=asus,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
uid: <uid>
cn: <Real Name>
sn: <Surname>
uidNumber: <唯一號, 例如 10001>
gidNumber: <同 uidNumber>
homeDirectory: /home/<uid>
loginShell: /bin/bash
userPassword: {SSHA}REPLACE_WITH_HASH
```
- ### 實際範例
```ldif=
# === UPG for tj_tsai ===
dn: cn=tj_tsai,ou=groups,ou=ocis,dc=google,dc=com
objectClass: top
objectClass: posixGroup
cn: tj_tsai
gidNumber: 10001
memberUid: tj_tsai
# === user tj_tsai ===
dn: uid=tj_tsai,ou=users,ou=ocis,dc=google,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
cn: TJ Tsai
sn: Tsai
uid: tj_tsai
uidNumber: 10001
gidNumber: 10001
homeDirectory: /home/tj_tsai
loginShell: /bin/bash
userPassword: {SSHA}REPLACE_WITH_HASH
```
<br>
{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}