- 新增 ldap_ad.go,支持 AD 密码编码、字段映射与目录类型判断 - 重构 ldap/ldap_users/ldap_ou,兼容 OpenLDAP 与 AD 登录、导入、OU 浏览 - 前端系统设置增加目录类型选择与 AD 配置项 - Dockerfile 按 TARGETARCH 单架构编译 Agent,限制 Node 内存防 OOM - run.sh 新增 docker fast/upgrade,README 补充 LDAP/AD 与常见问题
154 lines
4.4 KiB
Go
154 lines
4.4 KiB
Go
package controller
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
"unicode/utf16"
|
||
|
||
"ezops/model"
|
||
|
||
"github.com/go-ldap/ldap/v3"
|
||
)
|
||
|
||
const (
|
||
ldapServerOpenLDAP = "openldap"
|
||
ldapServerAD = "ad"
|
||
)
|
||
|
||
// ldapIsAD 判断当前配置是否为 Active Directory // Logan.Li
|
||
func ldapIsAD(cfg *model.LDAPConfig) bool {
|
||
if cfg == nil {
|
||
return false
|
||
}
|
||
return strings.EqualFold(strings.TrimSpace(cfg.ServerType), ldapServerAD)
|
||
}
|
||
|
||
// ldapDefaultUserMapping 按目录类型返回默认字段映射 // Logan.Li
|
||
func ldapDefaultUserMapping(cfg *model.LDAPConfig) map[string]string {
|
||
if ldapIsAD(cfg) {
|
||
return map[string]string{
|
||
"username": "sAMAccountName",
|
||
"email": "mail",
|
||
"display_name": "displayName",
|
||
}
|
||
}
|
||
return map[string]string{
|
||
"username": "uid",
|
||
"email": "mail",
|
||
"display_name": "cn",
|
||
}
|
||
}
|
||
|
||
// ldapEncodeADPassword 将明文密码编码为 AD unicodePwd(UTF-16LE 带引号) // Logan.Li
|
||
func ldapEncodeADPassword(password string) []byte {
|
||
quoted := `"` + password + `"`
|
||
runes := utf16.Encode([]rune(quoted))
|
||
buf := make([]byte, len(runes)*2)
|
||
for i, r := range runes {
|
||
buf[i*2] = byte(r)
|
||
buf[i*2+1] = byte(r >> 8)
|
||
}
|
||
return buf
|
||
}
|
||
|
||
// ldapBuildUserDN 按目录类型构造用户 DN // Logan.Li
|
||
func ldapBuildUserDN(cfg *model.LDAPConfig, username, displayName, parentDN string) (string, error) {
|
||
parentDN = strings.TrimSpace(parentDN)
|
||
if parentDN == "" {
|
||
return "", fmt.Errorf("父级 DN 不能为空")
|
||
}
|
||
if ldapIsAD(cfg) {
|
||
cn := strings.TrimSpace(displayName)
|
||
if cn == "" {
|
||
cn = username
|
||
}
|
||
return fmt.Sprintf("CN=%s,%s", ldap.EscapeDN(cn), parentDN), nil
|
||
}
|
||
if strings.TrimSpace(username) == "" {
|
||
return "", fmt.Errorf("用户名不能为空")
|
||
}
|
||
return fmt.Sprintf("uid=%s,%s", ldap.EscapeFilter(username), parentDN), nil
|
||
}
|
||
|
||
// ldapOUListFilters 返回 OU/容器列表过滤器,兼容 OpenLDAP 与 AD // Logan.Li
|
||
func ldapOUListFilters(cfg *model.LDAPConfig) []string {
|
||
if ldapIsAD(cfg) {
|
||
return []string{
|
||
"(|(objectClass=organizationalUnit)(objectClass=container))",
|
||
}
|
||
}
|
||
return []string{"(objectClass=organizationalUnit)"}
|
||
}
|
||
|
||
// ldapOUEntryName 读取 OU/容器显示名 // Logan.Li
|
||
func ldapOUEntryName(entry *ldap.Entry) string {
|
||
if v := entry.GetAttributeValue("ou"); v != "" {
|
||
return v
|
||
}
|
||
return ldapEntryAttr(entry, "cn", "name")
|
||
}
|
||
|
||
// ldapAddOpenLDAPUser 在 OpenLDAP 中创建 inetOrgPerson 用户 // Logan.Li
|
||
func ldapAddOpenLDAPUser(conn *ldap.Conn, cfg *model.LDAPConfig, userDN, username, displayName, email, password string) error {
|
||
mapping := ldapUserMapping(cfg)
|
||
uidAttr := mapping["username"]
|
||
if uidAttr == "" {
|
||
uidAttr = "uid"
|
||
}
|
||
mailAttr := mapping["email"]
|
||
if mailAttr == "" {
|
||
mailAttr = "mail"
|
||
}
|
||
cn := displayName
|
||
if cn == "" {
|
||
cn = username
|
||
}
|
||
|
||
addReq := ldap.NewAddRequest(userDN, nil)
|
||
addReq.Attribute("objectClass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"})
|
||
addReq.Attribute(uidAttr, []string{username})
|
||
addReq.Attribute("cn", []string{cn})
|
||
addReq.Attribute("sn", []string{cn})
|
||
if email != "" {
|
||
addReq.Attribute(mailAttr, []string{email})
|
||
}
|
||
addReq.Attribute("userPassword", []string{password})
|
||
return conn.Add(addReq)
|
||
}
|
||
|
||
// ldapAddADUser 在 Active Directory 中创建并启用用户账号 // Logan.Li
|
||
func ldapAddADUser(conn *ldap.Conn, userDN, username, displayName, email, password string) error {
|
||
cn := strings.TrimSpace(displayName)
|
||
if cn == "" {
|
||
cn = username
|
||
}
|
||
sn := cn
|
||
if idx := strings.LastIndex(cn, " "); idx > 0 {
|
||
sn = cn[idx+1:]
|
||
}
|
||
|
||
addReq := ldap.NewAddRequest(userDN, nil)
|
||
addReq.Attribute("objectClass", []string{"top", "person", "organizationalPerson", "user"})
|
||
addReq.Attribute("cn", []string{cn})
|
||
addReq.Attribute("sAMAccountName", []string{username})
|
||
addReq.Attribute("sn", []string{sn})
|
||
if email != "" {
|
||
addReq.Attribute("mail", []string{email})
|
||
}
|
||
// 先以禁用状态创建,设置密码后再启用 // Logan.Li
|
||
addReq.Attribute("userAccountControl", []string{"514"})
|
||
if err := conn.Add(addReq); err != nil {
|
||
return err
|
||
}
|
||
|
||
modPwd := ldap.NewModifyRequest(userDN, nil)
|
||
modPwd.Replace("unicodePwd", []string{string(ldapEncodeADPassword(password))})
|
||
if err := conn.Modify(modPwd); err != nil {
|
||
_ = conn.Del(ldap.NewDelRequest(userDN, nil))
|
||
return fmt.Errorf("设置 AD 密码失败(需 LDAPS 连接): %w", err)
|
||
}
|
||
|
||
modEnable := ldap.NewModifyRequest(userDN, nil)
|
||
modEnable.Replace("userAccountControl", []string{"512"})
|
||
return conn.Modify(modEnable)
|
||
} |