17
LDAP Authentication in Golang with Bind and Search
If you are familiar with the Windows Active Directory or Samba, you may have already heard about LDAP. But if you didn't, here is the description in Wikipedia.
Lightweight Directory Access Protocol (LDAP) is an open, vendor-neutral, industry standard application protocol for accessing and maintaining distributed directory information services over an Internet Protocol (IP) network.
First of all, we need to download the library:
go get github.com/go-ldap/ldap
Now, we can start using the library. Firstly, we are creating the variables to use them later(If you are going to use anonymous bind, you only need Filter):
const (
BindUsername = "[email protected]"
BindPassword = "password"
FQDN = "DC.example.com"
BaseDN = "cn=Configuration,dc=example,dc=com"
Filter = "(objectClass=*)"
)
To connect to LDAP, we can use ldap.DialURL()
func. Followed function is solely created to connect to an LDAP server:
// Ldap Connection without TLS
func Connect() (*ldap.Conn, error) {
// You can also use IP instead of FQDN
l, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", FQDN))
if err != nil {
return nil, err
}
return l, nil
}
If you need TLS Connection, you can use the function below:
// Ldap Connection with TLS
func ConnectTLS() (*ldap.Conn, error) {
// You can also use IP instead of FQDN
l, err := ldap.DialURL(fmt.Sprintf("ldaps://%s:636", FQDN))
if err != nil {
return nil, err
}
return l, nil
}
If you want anonymous bind in your project, you can use this function:
// Anonymous Bind and Search
func AnonymousBindAndSearch(l *ldap.Conn) (*ldap.SearchResult, error) {
l.UnauthenticatedBind("")
anonReq := ldap.NewSearchRequest(
"",
ldap.ScopeBaseObject, // you can also use ldap.ScopeWholeSubtree
ldap.NeverDerefAliases,
0,
0,
false,
Filter,
[]string{},
nil,
)
result, err := l.Search(anonReq)
if err != nil {
return nil, fmt.Errorf("Anonymous Bind Search Error: %s", err)
}
if len(result.Entries) > 0 {
result.Entries[0].Print()
return result, nil
} else {
return nil, fmt.Errorf("Couldn't fetch anonymous bind search entries")
}
}
If you prefer normal binding instead:
// Normal Bind and Search
func BindAndSearch(l *ldap.Conn) (*ldap.SearchResult, error) {
l.Bind(BindUsername, BindPassword)
searchReq := ldap.NewSearchRequest(
BaseDN,
ldap.ScopeBaseObject, // you can also use ldap.ScopeWholeSubtree
ldap.NeverDerefAliases,
0,
0,
false,
Filter,
[]string{},
nil,
)
result, err := l.Search(searchReq)
if err != nil {
return nil, fmt.Errorf("Search Error: %s", err)
}
if len(result.Entries) > 0 {
return result, nil
} else {
return nil, fmt.Errorf("Couldn't fetch search entries")
}
}
Last of all, we need to create a main function to use these functions:
func main() {
// TLS Connection
l, err := ConnectTLS()
if err != nil {
log.Fatal(err)
}
defer l.Close()
// Normal Bind and Search
result, err = BindAndSearch(l)
if err != nil {
log.Fatal(err)
}
result.Entries[0].Print()
}
func main() {
// Non-TLS Connection
l, err := Connect()
if err != nil {
log.Fatal(err)
}
defer l.Close()
// Anonymous Bind and Search
result, err := AnonymousBindAndSearch(l)
if err != nil {
log.Fatal(err)
}
result.Entries[0].Print()
}
After these functions, I believe you can start using LDAP authentication, bind and search. At the end, the code should look like this:
package main
import (
"fmt"
"log"
"github.com/go-ldap/ldap/v3"
)
const (
BindUsername = "[email protected]"
BindPassword = "password"
FQDN = "DC.example.com"
BaseDN = "cn=Configuration,dc=example,dc=com"
Filter = "(objectClass=*)"
)
func main() {
// TLS Connection
l, err := ConnectTLS()
if err != nil {
log.Fatal(err)
}
defer l.Close()
// Non-TLS Connection
//l, err := Connect()
//if err != nil {
// log.Fatal(err)
//}
//defer l.Close()
// Anonymous Bind and Search
result, err := AnonymousBindAndSearch(l)
if err != nil {
log.Fatal(err)
}
result.Entries[0].Print()
// Normal Bind and Search
result, err = BindAndSearch(l)
if err != nil {
log.Fatal(err)
}
result.Entries[0].Print()
}
// Ldap Connection with TLS
func ConnectTLS() (*ldap.Conn, error) {
// You can also use IP instead of FQDN
l, err := ldap.DialURL(fmt.Sprintf("ldaps://%s:636", FQDN))
if err != nil {
return nil, err
}
return l, nil
}
// Ldap Connection without TLS
func Connect() (*ldap.Conn, error) {
// You can also use IP instead of FQDN
l, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", FQDN))
if err != nil {
return nil, err
}
return l, nil
}
// Anonymous Bind and Search
func AnonymousBindAndSearch(l *ldap.Conn) (*ldap.SearchResult, error) {
l.UnauthenticatedBind("")
anonReq := ldap.NewSearchRequest(
"",
ldap.ScopeBaseObject, // you can also use ldap.ScopeWholeSubtree
ldap.NeverDerefAliases,
0,
0,
false,
Filter,
[]string{},
nil,
)
result, err := l.Search(anonReq)
if err != nil {
return nil, fmt.Errorf("Anonymous Bind Search Error: %s", err)
}
if len(result.Entries) > 0 {
result.Entries[0].Print()
return result, nil
} else {
return nil, fmt.Errorf("Couldn't fetch anonymous bind search entries")
}
}
// Normal Bind and Search
func BindAndSearch(l *ldap.Conn) (*ldap.SearchResult, error) {
l.Bind(BindUsername, BindPassword)
searchReq := ldap.NewSearchRequest(
BaseDN,
ldap.ScopeBaseObject, // you can also use ldap.ScopeWholeSubtree
ldap.NeverDerefAliases,
0,
0,
false,
Filter,
[]string{},
nil,
)
result, err := l.Search(searchReq)
if err != nil {
return nil, fmt.Errorf("Search Error: %s", err)
}
if len(result.Entries) > 0 {
return result, nil
} else {
return nil, fmt.Errorf("Couldn't fetch search entries")
}
}
You can also check out the gist I created.
Thank you for reading. I hope you found this article helpful.
17