[轉載]用 Go 寫一個輕量級的 ldap 測試工具
前言
這是一個輪子。
作為一個在高校裡混的 IT,LDAP 我們其實都蠻熟悉的,因為在高校中使用 LDAP 來做統一認證還蠻普遍的。對於 LDAP 的管理員而言,LDAP 的各種操作自然有產品對應的管理工具來處理,但對於需要整合 LDAP 的使用者而言,我們經常需要做一些 LDAP 的測試來作為整合時的對比驗證,腦補以下場景:
系統除錯ing
乙:“LDAP 認證走不通啊,你們的 LDAP 是不是有問題哦”
默默掏出測試工具
甲:“你看,毫無壓力”
乙:“我再查檢視~”
另外,高校間協作共享會比較多一些,例如通過一些聯邦式的認證聯盟來讓聯盟內的成員互相信任身份認證的結果,從而支援一些跨校協作的應用。在國外應用的比較多的是基於 Shibboleth 的聯盟。國內在上海有一個基於相同技術框架的聯盟,稱之為上海市教育認證聯盟。
我校作為上海聯盟的主要技術支援方,我經常得和各個學校的 LDAP 打交道。遠端支援當然只有 ssh 了。此時要測試 LDAP,LdapBrowser 之類的工具在純 CLI 環境下沒法用,openldap 的 client 又顯得過於麻煩,所以就造個輪子咯。
需求
這個輪子需求大概是這個樣子
- 跨平臺,木有依賴,開箱即用。用 Go 來擼一個就能很好的滿足這個需求。
- 簡單無腦一點,搞複雜了就沒意思了
- 做到 ldap 的認證和查詢就夠了。增刪改涉及 schema 以及不同 LDAP 產品實現時的標準差異,要做到相容通用會比較麻煩。反正這一塊的需求管理員用產品自帶的控制檯就好了嘛,我們的測試工具的就不折騰了
- 支援批量查詢和批量認證的測試
- 提供個簡單的 HTTP API,必要時也可以提供基於 http 的遠端測試。
- 好吧,還可以學習 Golang ~
用 Go 操作 LDAP
我們可以用 https://github.com/go-ldap/ldap 這個庫來操作 LDAP 他的 example 給的非常的詳細,基本看一遍就可以開始抄了。。。
我們拿其中 userAuthentication 的 example 來舉個例子,下為 example 中的示例程式碼,我增加了若干註釋說明
func Example_userAuthentication() {
// The username and password we want to check
// 用來認證的使用者名稱和密碼
username := "someuser"
password := "userpassword"
// 用來獲取查詢許可權的 bind 使用者。如果 ldap 禁止了匿名查詢,那我們就需要先用這個帳戶 bind 以下才能開始查詢
// bind 的賬號通常要使用完整的 DN 資訊。例如 cn=manager,dc=example,dc=org
// 在 AD 上,則可以用諸如 mananger@example.org 的方式來 bind
bindusername := "readonly"
bindpassword := "password"
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", "ldap.example.com", 389))
if err != nil {
log.Fatal(err)
}
defer l.Close()
// Reconnect with TLS
// 建立 StartTLS 連線,這是建立純文字上的 TLS 協議,允許你將非加密的通訊升級為 TLS 加密而不需要另外使用一個新的埠。
// 郵件的 POP3 ,IMAP 也有支援類似的 StartTLS,這些都是有 RFC 的
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
if err != nil {
log.Fatal(err)
}
// First bind with a read only user
// 先用我們的 bind 賬號給 bind 上去
err = l.Bind(bindusername, bindpassword)
if err != nil {
log.Fatal(err)
}
// Search for the given username
// 這樣我們就有查詢許可權了,可以構造查詢請求了
searchRequest := ldap.NewSearchRequest(
// 這裡是 basedn,我們將從這個節點開始搜尋
"dc=example,dc=com",
// 這裡幾個引數分別是 scope, derefAliases, sizeLimit, timeLimit, typesOnly
// 詳情可以參考 RFC4511 中的定義,文末有連結
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
// 這裡是 LDAP 查詢的 Filter。這個例子例子,我們通過查詢 uid=username 且 objectClass=organizationalPerson。
// username 即我們需要認證的使用者名稱
fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", username),
// 這裡是查詢返回的屬性,以陣列形式提供。如果為空則會返回所有的屬性
[]string{"dn"},
nil,
)
// 好了現在可以搜尋了,返回的是一個陣列
sr, err := l.Search(searchRequest)
if err != nil {
log.Fatal(err)
}
// 如果沒有資料返回或者超過1條資料返回,這對於使用者認證而言都是不允許的。
// 前這意味著沒有查到使用者,後者意味著存在重複資料
if len(sr.Entries) != 1 {
log.Fatal("User does not exist or too many entries returned")
}
// 如果沒有意外,那麼我們就可以獲取使用者的實際 DN 了
userdn := sr.Entries[0].DN
// Bind as the user to verify their password
// 拿這個 dn 和他的密碼去做 bind 驗證
err = l.Bind(userdn, password)
if err != nil {
log.Fatal(err)
}
// Rebind as the read only user for any further queries
// 如果後續還需要做其他操作,那麼使用最初的 bind 賬號重新 bind 回來。恢復初始許可權。
err = l.Bind(bindusername, bindpassword)
if err != nil {
log.Fatal(err)
}
}
總結:
- 建立連線
- 使用 bind 使用者先 bind 以獲取許可權
- 根據使用者名稱對應的屬性寫 searchfilter,結合 basedn 進行查詢
- 如果需要認證,用查到的 dn 進行 bind 驗證
- 如果還要繼續查詢/認證,rebind 回初始的 bind 使用者上
- 關閉連線
命令列
作為一個 cli 工具,命令列部分的設計是很重要的。考慮我們所需要實現的功能
- 使用者查詢
- 使用者認證
- 用特定的 filter 查詢
- 批量認證
- 批量查詢
比如可以按這個方式進行羅列 Go 由一個非常好的 cli 庫 cobra,我們就用它來做輪子。
cobra 用起來容易上手,我同樣貼一段他的 example 程式碼來加以註釋來說明
package main
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
func main() {
// 給後面的 Flags 用的
var echoTimes int
// cobra 以層次的方式組織命令。從 rootCmd 開始,每一個命令都通過一個 struct 來配置命令的相關資訊
// 這一行本來在 example 的最下面,我給挪上來了
var rootCmd = &cobra.Command{Use: "app"}
// 不同於 rootCmd,我們開始給出比較詳細的配置了
var cmdPrint = &cobra.Command{
// 命令的名稱,同時 [string to print] 等會在 help 時作為 usage 的內容輸出
Use: "print [string to print]",
// help 時作為 Available Commands 中,cmd 後的短描述
Short: "Print anything to the screen",
// help 時作為 cmd 的長描述
Long: `print is for printing anything back to the screen.
For many years people have printed back to the screen.`,
// 限制命令最小引數輸入為1,還有其他的引數限制,詳見 github 上的說明
Args: cobra.MinimumNArgs(1),
// 命令執行的函式,把命令要乾的事情放在這裡就好了
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdEcho = &cobra.Command{
Use: "echo [string to echo]",
Short: "Echo anything to the screen",
Long: `echo is for echoing anything back.
Echo works a lot like print, except it has a child command.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Print: " + strings.Join(args, " "))
},
}
var cmdTimes = &cobra.Command{
Use: "times [# times] [string to echo]",
Short: "Echo anything to the screen more times",
Long: `echo things multiple times back to the user by providing
a count and a string.`,
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
for i := 0; i < echoTimes; i++ {
fmt.Println("Echo: " + strings.Join(args, " "))
}
},
}
// 這裡為 cmdTimes 對應命令設定了一個 Flag 引數
// 型別為 Int,輸入方式為 `--times` 或者 `-t`,預設值時 1,繫結到最開始宣告的 `echoTimes` 上。
cmdTimes.Flags().IntVarP(&echoTimes, "times", "t", 1, "times to echo the input")
// rootCmd 後面 Add 了 cmdPrint, cmdEcho
// 也就是說初始的兩個命令是 `print` 和 `echo`
rootCmd.AddCommand(cmdPrint, cmdEcho)
// cmdEcho 後面 Add 了 cmdTimes
// 所以 `echo` 後面還有一個命令時 `times`
cmdEcho.AddCommand(cmdTimes)
rootCmd.Execute()
}
實際生產環境中,我們可以每個命令的相關程式碼單獨放在一個 .go 檔案中,這樣看起來會比較清晰一些。像這樣
├── cmd
│ ├── auth.go
│ ├── http.go
│ ├── root.go
│ ├── search.go
│ ├── utils.go
│ └── version.go
├── main.go
API
API 可以用著名的 beego 框架來搞。 beego 的文件 非常詳細,就不再贅述了。
基於 beego ,我們提供以下 API,把命令列支援的功能都搬過來。
GET /api/v1/ldap/health
ldap 健康狀態監測。請求的時候就去嘗試連線一下 ldap,用 bind 賬號 bind 測試下。成功的話就返回 ok,否則給個錯。
GET /api/v1/ldap/search/filter/:filter
根據 ldap filter 來做查詢
GET /api/v1/ldap/search/user/:username
根據使用者名稱來查詢
POST /api/v1/ldap/search/multi
根據使用者名稱同時查詢多個使用者,以 application/json 方式傳送請求資料,例:
["user1","user2","user3"]
POST /api/v1/ldap/auth/single
單個使用者的認證測試,以 application/json 方式傳送請求資料,例:
{
"username": "user",
"password": "123456"
}
POST /api/v1/ldap/auth/multi
單個使用者的認證測試,以 application/json 方式傳送請求資料,例:
[{
"username": "user1",
"password": "123456"
}, {
"username": "user2",
"password": "654321"
}]
輪子
那麼這個輪子已經造好了。ldao-test-tool
程式碼結構
# tree
.
├── cfg.json.example
├── cmd
│ ├── auth.go
│ ├── http.go
│ ├── root.go
│ ├── search.go
│ ├── utils.go
│ └── version.go
├── g
│ ├── cfg.go
│ └── const.go
├── http
│ ├── controllers
│ │ ├── authMulti.go
│ │ ├── authSingle.go
│ │ ├── default.go
│ │ ├── health.go
│ │ ├── searchFilter.go
│ │ ├── searchMulti.go
│ │ └── searchUser.go
│ ├── http.go
│ └── router.go
├── LICENSE
├── main.go
├── models
│ ├── funcs.go
│ ├── ldap.go
│ └── ldap_test.go
└── README.MD
編譯
go get ./...
go build
release
可以直接下載編譯好的 release 版本
提供 win64 和 linux64 兩個平臺的可執行檔案
https://github.com/shanghai-edu/ldap-test-tool/releases/
配置檔案
預設配置檔案為目錄下的 cfg.json
,也可以使用 -c
或 --config
來載入自定義的配置檔案。
openldap 配置示例
{
"ldap": {
"addr": "ldap.example.org:389",
"baseDn": "dc=example,dc=org",
"bindDn": "cn=manager,dc=example,dc=org",
"bindPass": "password",
"authFilter": "(&(uid=%s))",
"attributes": ["uid", "cn", "mail"],
"tls": false,
"startTLS": false
},
"http": {
"listen": "0.0.0.0:8888"
}
}
AD 配置示例
{
"ldap": {
"addr": "ad.example.org:389",
"baseDn": "dc=example,dc=org",
"bindDn": "manager@example.org",
"bindPass": "password",
"authFilter": "(&(sAMAccountName=%s))",
"attributes": ["sAMAccountName", "displayName", "mail"],
"tls": false,
"startTLS": false
},
"http": {
"listen": "0.0.0.0:8888"
}
}
命令體系
命令列部分使用 cobra 框架,可以使用 help
命令檢視命令的使用方式
# ./ldap-test-tool help
ldap-test-tool is a simple tool for ldap test
build by shanghai-edu.
Complete documentation is available at github.com/shanghai-edu/ldap-test-tool
Usage:
ldap-test-tool [flags]
ldap-test-tool [command]
Available Commands:
auth Auth Test
help Help about any command
http Enable a http server for ldap-test-tool
search Search Test
version Print the version number of ldap-test-tool
Flags:
-c, --config string load config file. default cfg.json (default "cfg.json")
-h, --help help for ldap-test-tool
Use "ldap-test-tool [command] --help" for more information about a command.
認證
./ldap-test-tool auth -h
Auth Test
Usage:
ldap-test-tool auth [flags]
ldap-test-tool auth [command]
Available Commands:
multi Multi Auth Test
single Single Auth Test
Flags:
-h, --help help for auth
Global Flags:
-c, --config string load config file. default cfg.json (default "cfg.json")
Use "ldap-test-tool auth [command] --help" for more information about a command.
單使用者測試
命令列說明
Single Auth Test
Usage:
ldap-test-tool auth single [username] [password] [flags]
Flags:
-h, --help help for single
Global Flags:
-c, --config string load config file. default cfg.json (default "cfg.json")
示例
./ldap-test-tool auth single qfeng 123456
LDAP Auth Start
==================================
qfeng auth test successed
==================================
LDAP Auth Finished, Time Usage 47.821884ms
批量測試
命令列說明
# ./ldap-test-tool auth multi -h
Multi Auth Test
Usage:
ldap-test-tool auth multi [filename] [flags]
Flags:
-h, --help help for multi
Global Flags:
-c, --config string load config file. default cfg.json (default "cfg.json")
示例
# cat authusers.txt
qfeng,123456
qfengtest,111111
使用者名稱和密碼以逗號分隔(csv風格) authusers.txt 中有兩個使用者,密碼正確的 qfeng 和密碼錯誤的 qfengtest
# ./ldap-test-tool auth multi authusers.txt
LDAP Multi Auth Start
==================================
Successed count 1
Failed count 1
Failed users:
-- User: qfengtest , Msg: Cannot find such user
==================================
LDAP Multi Auth Finished, Time Usage 49.582994ms
查詢
# ./ldap-test-tool search -h
Search Test
Usage:
ldap-test-tool search [flags]
ldap-test-tool search [command]
Available Commands:
filter Search By Filter
multi Search Multi Users
user Search Single User
Flags:
-h, --help help for search
Global Flags:
-c, --config string load config file. default cfg.json (default "cfg.json")
Use "ldap-test-tool search [command] --help" for more information about a command.
[root@wiki-qfeng ldap-test-tool]#
單使用者查詢
命令列說明
# ./ldap-test-tool search user -h
Search Single User
Usage:
ldap-test-tool search user [username] [flags]
Flags:
-h, --help help for user
Global Flags:
-c, --config string load config file. default cfg.json (default "cfg.json")
[root@wiki-qfeng ldap-test-tool]#
示例
# ./ldap-test-tool search user qfeng
LDAP Search Start
==================================
DN: uid=qfeng,ou=people,dc=example,dc=org
Attributes:
-- uid : qfeng
-- cn : 馮騏測試
-- mail : qfeng@example.org
==================================
LDAP Search Finished, Time Usage 44.711268ms
PS: 如果屬性有多值,將以 ;
分割
LDAP Filter 查詢
# ./ldap-test-tool search filter -h
Search By Filter
Usage:
ldap-test-tool search filter [searchFilter] [flags]
Flags:
-h, --help help for filter
Global Flags:
-c, --config string load config file. default cfg.json (default "cfg.json")
示例
# ./ldap-test-tool search filter "(cn=*測試)"
LDAP Search By Filter Start
==================================
DN: uid=test1,ou=people,dc=example,dc=org
Attributes:
-- uid : test1
-- cn : 一號測試
-- mail : test1@example.org
DN: uid=test2,ou=people,dc=example,dc=org
Attributes:
-- uid : test2
-- cn : 二號測試
-- mail : test2@example.org
DN: uid=test3,ou=people,dc=example,dc=org
Attributes:
-- uid : test3
-- cn : 三號測試
-- mail : test3@example.org
results count 3
==================================
LDAP Search By Filter Finished, Time Usage 46.071833ms
批量查詢測試
命令列說明
# ./ldap-test-tool search multi -h
Search Multi Users
Usage:
ldap-test-tool search multi [filename] [flags]
Flags:
-f, --file output search to users.csv, failed search to failed.csv
-h, --help help for multi
Global Flags:
-c, --config string load config file. default cfg.json (default "cfg.json")
示例
# cat searchusers.txt
qfeng
qfengtest
nofounduser
searchuser.txt 中有三個使用者,其中 nofounduser 是不存在的使用者
# ldap-test-tool.exe search multi .\searchusers.txt
LDAP Multi Search Start
==================================
Successed users:
DN: uid=qfeng,ou=people,dc=example,dc=org
Attributes:
-- uid : qfeng
-- cn : 馮騏
-- mail : qfeng@example.org
DN: uid=qfengtest,ou=people,dc=example,dc=org
Attributes:
-- uid : qfengtest
-- cn : 馮騏測試
-- mail : qfeng@example.org
nofounduser : Cannot find such user
Successed count 2
Failed count 1
==================================
LDAP Multi Search Finished, Time Usage 134.744ms
當使用 -f
選項時,查詢的結果將輸出到 csv
中。csv
將以配置檔案中 attributes
的屬性作為 title。因此當使用 -f
選項時,attributes
不得為空。
# ./ldap-test-tool search multi searchusers.txt -f
LDAP Multi Search Start
==================================
OutPut to csv successed
==================================
LDAP Multi Search Finished, Time Usage 88.756956ms
# ls | grep csv
failed.csv
users.csv
HTTP API
HTTP API 部分使用 beego 框架 使用如下命令開啟 HTTP API
# ldap-test-tool.exe http
2018/03/12 14:30:25 [I] http server Running on http://0.0.0.0:8888
健康狀態
檢測 ldap 健康狀態
# curl http://127.0.0.1:8888/api/v1/ldap/health
{
"msg": "ok",
"success": true
}
查詢使用者
查詢單個使用者資訊
# curl http://127.0.0.1:8888/api/v1/l ... qfeng
{
"user": {
"dn": "uid=qfeng,ou=people,dc=example,dc=org",
"attributes": {
"cn": [
"馮騏"
],
"mail": [
"qfeng@example.org"
],
"uid": [
"qfeng"
]
}
},
"success": true
}
Filter 查詢
根據 LDAP Filter 查詢
# curl http://127.0.0.1:8888/api/v1/ldap/search/filter/\(cn=*測試\)
{
"results": [
{
"dn": "uid=test1,ou=people,dc=example,dc=org",
"attributes": {
"cn": [
"一號測試"
],
"mail": [
"test1@example.org"
],
"uid": [
"test1"
]
}
},
{
"dn": "uid=test2,ou=people,dc=example,dc=org",
"attributes": {
"cn": [
"二號測試"
],
"mail": [
"test2@example.org"
],
"uid": [
"test2"
]
}
},
{
"dn": "uid=test3,ou=people,dc=example,dc=org",
"attributes": {
"cn": [
"三號測試"
],
"mail": [
"test3@example.org"
],
"uid": [
"test3"
]
}
},
],
"success": true
}
多使用者查詢
同時查詢多個使用者,以 application/json
方式傳送請求資料,請求資料示例
["qfeng","qfengtest","nofounduser"]
curl 示例
# curl -X POST -H 'Content-Type:application/json' -d '["qfeng","qfengtest","nofounduser"]' http://127.0.0.1:8888/api/v1/ldap/search/multi
{
"success": true,
"result": {
"successed": 2,
"failed": 1,
"users": [
{
"dn": "uid=qfeng,ou=people,dc=example,dc=org",
"attributes": {
"cn": [
"馮騏"
],
"mail": [
"qfeng@example.org"
],
"uid": [
"qfeng"
]
}
},
{
"dn": "uid=qfengtest,ou=people,dc=example,dc=org",
"attributes": {
"cn": [
"馮騏測試"
],
"mail": [
"qfeng@example.org"
],
"uid": [
"qfengtest"
]
}
}
],
"failed_messages": [
{
"username": "nofounduser",
"message": "Cannot find such user"
}
]
}
}
認證
單使用者認證
單個使用者認證測試,以 application/json
方式傳送請求資料,請求資料示例
{
"username": "qfeng",
"password": "123456"
}
curl 示例
# curl -X POST -H 'Content-Type:application/json' -d '{"username":"qfeng","password":"123456"}' http://127.0.0.1:8888/api/v1/ldap/auth/single
{
"msg": "user 20150073 Auth Successed",
"success": true
}
多使用者認證
同時發起多個使用者認證測試,以 application/json
方式傳送請求資料,請求資料示例
[{
"username": "qfeng",
"password": "123456"
}, {
"username": "qfengtest",
"password": "1111111"
}]
curl 示例
# curl -X POST -H 'Content-Type:application/json' -d '[{"username":"qfeng","password":"123456"},{"username":"qfengtest","password":"1111111"}]' http://127.0.0.1:8888/api/v1/ldap/auth/multi
{
"success": true,
"result": {
"successed": 1,
"failed": 1,
"failed_messages": [
{
"username": "qfengtest",
"message": "LDAP Result Code 49 \"Invalid Credentials\": "
}
]
}
}
參考文件
IBM Security Identity Manager V6.0.0.10 - enRoleLDAPConnection.properties
以上
轉載授權
相關文章
- 輕量級API測試工具PandariaAPI
- 仿Laravel寫了一個輕量級的框架Laravel框架
- ThinkGo:一個輕量級的 Go 語言 MVC 框架GoMVC框架
- 用go設計開發一個自己的輕量級登入庫/框架吧Go框架
- 用 Material Design 寫了一個簡單的 API 測試工具Material DesignAPI
- 為你的Go應用建立輕量級Docker映象?GoDocker
- api-hook,更輕量的介面測試工具APIHook
- Pekwm:一個輕量級的 Linux 桌面Linux
- 輕量級超級 css 工具CSS
- ddosify:用Golang編寫的高效能負載測試工具Golang負載
- 用go設計開發一個自己的輕量級登入庫/框架吧(業務篇)Go框架
- 不想寫sql?試試這款輕量級JAVA ORM框架!SQLJavaORM框架
- 嘗試用go寫一個音樂搜尋的包Go
- iOS 一個輕量級的元件化思路iOS元件化
- CherryPy :一個輕量級的 Python Web 框架PythonWeb框架
- Soa: 一個輕量級的微服務庫微服務
- 如何寫好測試用例以及go單元測試工具testify簡單介紹Go
- 使用Python編寫一個滲透測試探測工具Python
- moment太重? 那就試試miment--一個超輕量級的js時間庫JS
- Go 寫一個內網穿透工具Go內網穿透
- LogFX:JavaFX編寫一個漂亮、輕量級的日誌檢視器
- 一個輕量級react埋點元件React元件
- PHP實現一個輕量級容器PHP
- 一個更適合Java初學者的輕量級開發工具:BlueJJava
- 用 Go 寫一個簡易的 dockerGoDocker
- 用go設計開發一個自己的輕量級登入庫/框架吧(專案維護篇)Go框架
- 用Go輕鬆完成一個分散式事務TCC,保姆級教程Go分散式
- 用Go輕鬆完成一個SAGA分散式事務,保姆級教程Go分散式
- python輕量級效能工具-LocustPython
- 用Java寫一個PDF,Word檔案轉換工具Java
- 推薦一個適用於SpringBoot專案的輕量級HTTP客戶端框架,快來試試它!Spring BootHTTP客戶端框架
- 一個用於建立react+Figma外掛的輕量級的UI庫ReactUI
- GO 的鏈式呼叫寫一個轉碼庫Go
- 寫好測試,提升應用質量
- Go測試開發(一) 怎麼寫Go程式碼Go
- 支援國產ETL etl-engine 用go寫的輕量級etl引擎 方便整合到各企業中Go
- Shottr for mac(輕量級截圖工具)Mac
- 用Jmeter編寫一個較複雜的測試指令碼JMeter指令碼