Backend Development 11 min read

Mastering Go-LDAP: Connect, Manage, and Secure LDAP with Go

Learn how to use the Go‑ldap library to connect to an LDAP server, set up a Docker‑based LDAP environment, and perform core operations such as creating, listing, deleting users and checking weak passwords, complete with full code examples and optimization tips.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Mastering Go-LDAP: Connect, Manage, and Secure LDAP with Go

1 Go Connect LDAP Service

Use the go-ldap package, which implements LDAP v3 functions such as connecting, adding, deleting, and modifying user entries, and supports conditional searches.

2 Download

<code>go get github.com/go-ldap/ldap/v3
go get github.com/wxnacy/wgo/arrays</code>

Documentation can be found at gopkg.in/[email protected]#section-readme.

3 Prepare LDAP Environment

Run a temporary LDAP instance with docker‑compose:

<code>version: "3"
services:
  ldap:
    image: osixia/openldap:latest
    container_name: openldap
    hostname: openldap
    restart: always
    environment:
      - "LDAP_ORGANISATION=devopsman"
      - "LDAP_DOMAIN=devopsman.cn"
      - "LDAP_BASE_DN=dc=devopsman,dc=cn"
      - "LDAP_ADMIN_PASSWORD=admin123"
    ports:
      - 389:389
      - 636:636
</code>

Modify environment variables as needed. The image can be found on hub.docker.com.

4 GO‑LDAP Example

Create User

Use Add and NewAddRequest to create a user. First bind to the server with an account that has creation rights.

<code>// LoginBind  connection ldap server and binding ldap server
func LoginBind(ldapUser, ldapPassword string) (*ldap.Conn, error) {
    l, err := ldap.DialURL(ldapURL)
    if err != nil {
        return nil, err
    }
    _, err = l.SimpleBind(&ldap.SimpleBindRequest{
        Username: fmt.Sprintf("cn=%s,dc=devopsman,dc=cn", ldapUser),
        Password: ldapPassword,
    })
    if err != nil {
        fmt.Println("ldap password is error: ", ldap.LDAPResultInvalidCredentials)
        return nil, err
    }
    fmt.Println(ldapUser, "登录成功")
    return l, nil
}
</code>

Define a User struct with required fields.

<code>type User struct {
    username    string
    password    string
    telephone   string
    emailSuffix string
    snUsername  string
    uid         string
    gid         string
}
</code>

Implement addUser using NewAddRequest.

<code>func (user *User) addUser(conn *ldap.Conn) error {
    ldaprow := ldap.NewAddRequest(fmt.Sprintf("cn=%s,dc=devopsman,dc=cn", user.username), nil)
    ldaprow.Attribute("userPassword", []string{user.password})
    ldaprow.Attribute("homeDirectory", []string{fmt.Sprintf("/home/%s", user.username)})
    ldaprow.Attribute("cn", []string{user.username})
    ldaprow.Attribute("uid", []string{user.username})
    ldaprow.Attribute("objectClass", []string{"shadowAccount", "posixAccount", "account"})
    ldaprow.Attribute("uidNumber", []string{"2201"})
    ldaprow.Attribute("gidNumber", []string{"2201"})
    ldaprow.Attribute("loginShell", []string{"/bin/bash"})
    if err := conn.Add(ldaprow); err != nil {
        return err
    }
    return nil
}
</code>

Instantiate User and call addUser.

<code>func main() {
    con, err := LoginBind("admin", "admin123")
    fmt.Println(con.IsClosing())
    if err != nil {
        fmt.Println("V")
        fmt.Println(err)
    }
    var user User
    user.username = "marionxue"
    user.password = "admin123"
    user.snUsername = "Marionxue"
    user.uid = "1000"
    user.gid = "1000"
    user.emailSuffix = "@qq.com"
    if err = user.addUser(con); err != nil {
        fmt.Println(err)
    }
    fmt.Println(user.username, "创建完成!")
}
</code>

Running the program creates the user.

List Users

Reuse LoginBind and implement GetEmployees to search the directory.

<code>func GetEmployees(con *ldap.Conn) ([]string, error) {
    var employees []string
    sql := ldap.NewSearchRequest(
        "dc=devopsman,dc=cn",
        ldap.ScopeWholeSubtree,
        ldap.NeverDerefAliases,
        0,
        0,
        false,
        "(objectClass=*)",
        []string{"dn", "cn", "objectClass"},
        nil,
    )
    cur, err := con.Search(sql)
    if err != nil {
        return nil, err
    }
    if len(cur.Entries) > 0 {
        for _, item := range cur.Entries {
            cn := item.GetAttributeValues("cn")
            for _, iCn := range cn {
                employees = append(employees, strings.Split(iCn, "[")[0])
            }
        }
        return employees, nil
    }
    return nil, nil
}
</code>

Print the retrieved usernames.

<code>func main() {
    con, err := LoginBind("admin", "admin123")
    if err != nil {
        fmt.Println("V")
        fmt.Println(err)
    }
    employees, err := GetEmployees(con)
    if err != nil {
        fmt.Println(err)
    }
    for _, employe := range employees {
        fmt.Println(employe)
    }
}
</code>

Delete User

Implement delUser using NewDelRequest.

<code>// delUser 删除用户
func (user *User) delUser(conn *ldap.Conn) error {
    ldaprow := ldap.NewDelRequest(fmt.Sprintf("cn=%s,dc=devopsman,dc=cn", user.username), nil)
    if err := conn.Del(ldaprow); err != nil {
        return err
    }
    return nil
}
</code>

Call delUser in main after binding.

<code>func main() {
    con, err := LoginBind("admin", "admin123")
    if err != nil {
        fmt.Println("V")
        fmt.Println(err)
    }
    var user User
    user.username = "marionxue"
    if err := user.delUser(con); err != nil {
        fmt.Println("用户删除失败")
    }
    fmt.Println(user.username, "用户删除成功!")
}
</code>

Weak Password Check

Because LDAP does not store passwords in plain text, a dictionary‑based login attempt can identify weak passwords.

<code>func CheckPassword(employe string) {
    // 遍历的弱密码字典
    f, err := os.Open("~/dict.txt")
    if err != nil {
        fmt.Println("reading dict.txt error: ", err)
    }
    defer f.Close()
    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        weakpassword := scanner.Text()
        _, err := LoginBind(employe, weakpassword)
        if err == nil {
            fmt.Println(employe + " 使用的密码为: " + weakpassword)
        }
    }
    if err := scanner.Err(); err != nil {
        fmt.Println(err)
    }
    fmt.Println(employe + " check have already finished. and the password is stronger well.")
}
</code>

Combine enumeration and checking, skipping whitelisted accounts.

<code>func main() {
    con, err := LoginBind("admin", "admin123")
    if err != nil {
        fmt.Println("V")
        fmt.Println(err)
    }
    employees, err := GetEmployees(con)
    if err != nil {
        fmt.Println(err)
    }
    Whitelist := []string{"zhangsan", "lisi"}
    for _, employe := range employees {
        fmt.Println("Starting check: ", employe)
        if index := arrays.ContainsString(Whitelist, employe); index == -1 {
            CheckPassword(employe)
        } else {
            fmt.Println(employe + " in whitelist. skiping...")
        }
    }
}
</code>
backendDockerGoauthenticationLDAPdirectory services
Ops Development Stories
Written by

Ops Development Stories

Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.