Skip to content

Instantly share code, notes, and snippets.

@ukautz
Last active February 10, 2021 12:30
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ukautz/3e4244d301f892e8c76bc6bab40c397d to your computer and use it in GitHub Desktop.
Save ukautz/3e4244d301f892e8c76bc6bab40c397d to your computer and use it in GitHub Desktop.
Dump LDAP search result as JSON

What can you do with ldapsearch outputs? Not much. With JSON, though ..

# from LDAP search tool ...
ldapsearch -h 127.0.0.1 -p 10389 \
	-D "cn=me,ou=Acme,dc=local" -w 'mypa$$w00t" \
	-b "ou=machines,ou=Acme,dc=local" \
	-s sub "(objectclass=machine)"

# to LDAP dump tool
go run main.go -a "127.0.0.1:10389" \
	-D "cn=me,ou=Acme,dc=local" -w 'mypa$$w00t" \
	-b "ou=machines,ou=Acme,dc=local" \
	-s sub "(objectclass=machine)"
package main
import (
"crypto/tls"
"encoding/json"
"fmt"
"github.com/spf13/pflag"
"gopkg.in/ldap.v2"
"os"
"strings"
)
var (
searchBase string
bindPassword string
bindUser string
ldapAddress string
ldapTLS bool
searchScope string
searchQuery string
pageSize = uint32(500)
scopes = map[string]int{
"base": ldap.ScopeBaseObject,
"one": ldap.ScopeSingleLevel,
"sub": ldap.ScopeWholeSubtree,
}
)
func connect() (conn *ldap.Conn, err error) {
if ldapTLS {
conn, err = ldap.DialTLS("tcp", ldapAddress, &tls.Config{
InsecureSkipVerify: true,
})
} else {
conn, err = ldap.Dial("tcp", ldapAddress)
}
if err != nil {
return
}
if err = conn.Bind(bindUser, bindPassword); err != nil {
defer conn.Close()
return
}
return
}
func warn(msg string, args ...interface{}) {
if !strings.HasSuffix(msg, "\n") {
msg += "\n"
}
fmt.Fprintf(os.Stderr, msg, args...)
}
func die(msg string, args ...interface{}) {
warn(msg, args...)
os.Exit(1)
}
func transformAttribs(attribs []*ldap.EntryAttribute) map[string]interface{} {
res := make(map[string]interface{})
for _, attrib := range attribs {
switch len(attrib.Values) {
case 0:
res[attrib.Name] = nil
case 1:
res[attrib.Name] = attrib.Values[0]
default:
res[attrib.Name] = attrib.Values
}
}
return res
}
func transform(entries []*ldap.Entry) map[string]map[string]interface{} {
res := make(map[string]map[string]interface{})
for _, entry := range entries {
res[entry.DN] = transformAttribs(entry.Attributes)
}
return res
}
func main() {
pflag.StringVarP(&searchBase, "search-base", "b", "", "Use searchbase as the starting point for the search instead of the default")
pflag.StringVarP(&bindPassword, "passwd", "w", "", "Use passwd as the password for simple authentication")
pflag.StringVarP(&bindUser, "binddn", "D", "", "Use the Distinguished Name binddn to bind to the LDAP directory")
pflag.StringVarP(&ldapAddress, "address", "a", "localhost:389", "Specify an alternate address on which the ldap server is running")
pflag.StringVarP(&searchScope, "scope", "s", "one", "Specify the scope of the search to be one of base, one, sub, or children")
pflag.BoolVarP(&ldapTLS, "tls", "Z", false, "Issue StartTLS (Transport Layer Security) extended operation")
pflag.Uint32VarP(&pageSize, "page-size", "P", pageSize, "internally used page size for requests")
pflag.Parse()
if pflag.NArg() == 0 {
fmt.Printf("Missing LDAP query")
}
searchQuery = strings.Join(pflag.Args(), " ")
conn, err := connect()
if err != nil {
die("Failed to connect: %s", err)
}
defer conn.Close()
scope, ok := scopes[searchScope]
if !ok {
die("Scope \"%s\" undefined", searchScope)
}
res, err := conn.SearchWithPaging(&ldap.SearchRequest{
BaseDN: searchBase,
Scope: scope,
Filter: searchQuery,
}, pageSize)
if err != nil {
die("Failed to search: %s\n", err)
}
warn("# Found %d entries\n", len(res.Entries))
raw, err := json.MarshalIndent(transform(res.Entries), "", " ")
if err != nil {
die("Could not marshal to JSON: %s", err)
}
fmt.Println(string(raw))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment