`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 匯入 ![](https://hackmd.io/_uploads/SkDWdHIsle.png) <br> ### [UI] 透過 phpLDAPadmin 查看 - `cn=gid10001` ![](https://hackmd.io/_uploads/ryGrhr8oee.png) - `uid=tj_tsai` ![](https://hackmd.io/_uploads/SyDmfIUolx.png) --- ![](https://hackmd.io/_uploads/HJqVfIUoeg.png) <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 %}