How to retrieve all attributes of an LDAP entry with golang query? - go

I'm using the gopkg.in/ldap.v2 api to query an LDAP server, but was wondering how to retrieve all the attributes of an entry once it shows up in the query result? Here is the full program I have:
/*In order to use this program, the user needs to get the package by running the following command:
go get gopkg.in/ldap.v2*/
package main
import (
"fmt"
"strings"
"gopkg.in/ldap.v2"
"os"
)
//Gives constants to be used for binding to and searching the LDAP server.
const (
ldapServer = "127.0.0.1:389"
ldapBind = "cn=admin,dc=test123,dc=com"
ldapPassword = "Password"
filterDN = "(objectclass=*)"
baseDN = "dc=test123,dc=com"
loginUsername = "admin"
loginPassword = "Password"
)
//Main function, which is executed.
func main() {
conn, err := connect()
//If there is an error connecting to server, prints this
if err != nil {
fmt.Printf("Failed to connect. %s", err)
return
}
//Close the connection at a later time.
defer conn.Close()
//Declares err to be list(conn), and checks if any errors. It prints the error(s) if there are any.
if err := list(conn); err != nil {
fmt.Printf("%v", err)
return
}
/*
//Declares err to be auth(conn), and checks if any errors. It prints the error(s) if there are any.
if err := auth(conn); err != nil {
fmt.Printf("%v", err)
return
}*/
}
//This function is used to connect to the LDAP server.
func connect() (*ldap.Conn, error) {
conn, err := ldap.Dial("tcp", ldapServer)
if err != nil {
return nil, fmt.Errorf("Failed to connect. %s", err)
}
if err := conn.Bind(ldapBind, ldapPassword); err != nil {
return nil, fmt.Errorf("Failed to bind. %s", err)
}
return conn, nil
}
//This function is used to search the LDAP server as well as output the attributes of the entries.
func list(conn *ldap.Conn) error {
//This gets the command line argument and saves it in the form "(argument=*)"
arg := ""
filter := ""
if len(os.Args) > 1{
arg = os.Args[1]
fmt.Println(arg)
filter = "(" + arg + "=*)"
} else{
fmt.Println("You need to input an argument for an attribute to search. I.E. : \"go run anonymous_query.go cn\"")
}
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
//To add anymore strings to the search, you need to add it here.
[]string{"dn", "o", "cn", "ou", "uidNumber", "objectClass",
"uid", "uidNumber", "gidNumber", "homeDirectory", "loginShell", "gecos",
"shadowMax", "shadowWarning", "shadowLastChange", "dc", "description", "entryCSN"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to search users. %s", err)
}
//Prints all the attributes per entry
for _, entry := range result.Entries {
entry.Print()
fmt.Println()
}
return nil
}
//This function authorizes the user and binds to the LDAP server.
func auth(conn *ldap.Conn) error {
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter(loginUsername),
[]string{"dn"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to find user. %s", err)
}
if len(result.Entries) < 1 {
return fmt.Errorf("User does not exist")
}
if len(result.Entries) > 1 {
return fmt.Errorf("")
}
if err := conn.Bind(result.Entries[0].DN, loginPassword); err != nil {
fmt.Printf("Failed to auth. %s", err)
} else {
fmt.Printf("Authenticated successfuly!")
}
return nil
}
func filter(needle string) string {
res := strings.Replace(
filterDN,
"{username}",
needle,
-1,
)
return res
}
The issue I have is in this line:
//To add anymore strings to the search, you need to add it here.
[]string{"dn", "o", "cn", "ou", "uidNumber", "objectClass",
"uid", "uidNumber", "gidNumber", "homeDirectory", "loginShell", "gecos",
"shadowMax", "shadowWarning", "shadowLastChange", "dc", "description", "entryCSN"}
I would want to retrieve all the attributes of an LDAP entry rather than having to manually type all the attributes that I want from the query result. Another reason is because I don't know what attributes an entry may have.
Any help would be greatly appreciated. Thanks!

In the LDAP search operation, if you don't specify attributes for the search it will return the entries with all their attributes, so this will do the job:
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
[]string{},
nil,
))

Related

Go/Ldap get primary groups of a user

I'm using go/ldap to query my active directory to get all the groups of a specific user, the function is working but is not returning the Primary Groups, like Domain Users.
Code example
package main
import (
"encoding/json"
"errors"
"fmt"
"github.com/go-ldap/ldap/v3"
"github.com/techoner/gophp"
"handlers"
"log"
"reflect"
"strconv"
"strings"
)
func main(){
conn, err := connect(bindServer,BindPort)
if err != nil {
log.Printf("Failed to connect to AD/LDAP with error: %s", err)
return nil, fmt.Errorf("Failed to connect to AD/LDAP with error: %s", err)
}
errBind := conn.Bind(bindUser, bindPWD)
if errBind != nil {
if isLdapDebug {
log.Printf("Failed to bind to AD/LDAP with error: %s", errBind)
}
return nil, fmt.Errorf("Failed to bind to AD/LDAP with error: %s", errBind)
}
searchRequest := ldap.NewSearchRequest(
DC=domain,DC=local,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=user)(sAMAccountName=%s))", administrator),
[]string{"dn"},
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
return nil, err
}
if len(sr.Entries) != 1 {
return nil, errors.New("User does not exist")
}
userdn := sr.Entries[0].DN
log.Printf("USER DN IS =%s", userdn)
searchRequest = ldap.NewSearchRequest(
DC=domain,DC=local,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectClass=group)(member=CN=Administrator,CN=Users,DC=domain,DC=local))"),
[]string{"cn"}, // can it be something else than "cn"?
nil,
)
sr, err = conn.Search(searchRequest)
if err != nil {
return nil, err
}
groups := []string{}
for _, entry := range sr.Entries {
//fmt.Printf("%s", entry)
groups = append(groups, entry.GetAttributeValue("cn"))
}
return groups, nil
}
Output
[Administrators Schema Admins Enterprise Admins Domain Admins Group Policy Creator Owners gteste1 gtest2]
The groups are correcly returned but is missing the primary groups.
Any way to return all groups of a specific user including Primary Groups?
The primary group is different. You have to look at the primaryGroupId attribute on the user, then search for the group that has that value in its primaryGroupToken attribute.
In most cases, the primaryGroupId will be 513, which corresponds to the Domain Users group.
A little more detail in an article I wrote on this: Active Directory: What makes a member a member?
To find the primary group in go using the lib
github.com/go-ldap/ldap/v3
I had to use this code sample:
userdn := sr.Entries[0].DN
groups := []string{}
searchRequest = ldap.NewSearchRequest(
data.Record.LdapSuffix,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, SearchTimelimit, false,
fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(primaryGroupID=513)(sAMAccountName=%s))", ldap.EscapeFilter(username)),
[]string{"primaryGroupID"},
nil,
)
sr, err = conn.Search(searchRequest)
if err != nil {
continue
}
if len(sr.Entries) > 0 {
primaryGroup := sr.Entries[0].GetAttributeValue("primaryGroupID")
if primaryGroup == "513" {
if searchGroup == "Domain Users" {
return true
}
}
}

Go - How can I make sure there aren't empty fields being sent in this code via gRPC?

I am writing some code to send logs with gRPC to a server. The problem I'm having is that the logs are different and not all have every field, but . I'm wondering how I can set only the fields they do have inside logpb.Log short of creating several different types of logs in the proto file?
This is all one methods, but the formatting on StackOverflow isn't with it.
The code:
func (*Client) SendWithgRPC(entry *log.Entry) error {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Could not connect: %v", err)
}
defer conn.Close()
c := logpb.NewLogServiceClient(conn)
stream, err := c.SendLogs(context.Background())
if err != nil {
log.Fatalf("Error while calling SendLogs: %v", err)
}
logFields := entry.Data
category := ""
debug_id := ""
request_id := ""
tipi := ""
uri := ""
var user_id int32 = 0
ip := ""
if logFields["category"] != nil {
category = logFields["category"].(string)
}
if logFields["debug_id"] != nil {
debug_id = logFields["debug_id"].(string)
}
if logFields["request_id"] != nil {
request_id = logFields["request_id"].(string)
}
if logFields["type"] != nil {
tipi = logFields["type"].(string)
}
if logFields["uri"] != nil {
uri = logFields["uri"].(string)
}
if logFields["user_id"] != nil {
user_id = int32(logFields["user_id"].(int))
}
if logFields["ip"] != nil {
ip = logFields["ip"].(string)
}
logEntry := &logpb.Log{
Time: entry.Time.String(),
Level: entry.Level.String(),
Msg: entry.Message,
Category: category,
DebugId: debug_id,
Ip: ip,
RequestId: request_id,
Type: tipi,
Uri: uri,
Id: user_id,
}
logs := []*logpb.Log{logEntry}
logarray := []*logpb.SendLogsRequest{
&logpb.SendLogsRequest{
Logs: logs,
},
}
fmt.Print(logarray)
for _, req := range logarray {
stream.Send(req)
}
_, err = stream.CloseAndRecv()
if err != nil {
fmt.Printf("Error with response: %v", err)
}
return nil
}
You can use something like https://github.com/go-playground/validator
It offers validation using tags. There is a lot more to this lib apart from basic validation. Do check it out.
Sample example https://github.com/go-playground/validator/blob/master/_examples/simple/main.go
As easy as it gets :
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
type Name struct {
First string `validate:"required"`
Last string
}
func main() {
validName := Name{First: "John"} // Last name is empty and is not required
invalidName := Name{Last: "Doe"} // first name is empty but is required
validate := validator.New()
err := validate.Struct(invalidName)
if err != nil {
fmt.Println("invalid name struct caused error")
}
err = validate.Struct(validName)
if err != nil {
fmt.Println("valid name struct caused error")
}
fmt.Println("GoodBye")
}
The output of above :
invalid name struct caused error
GoodBye
working code on go-playground : https://play.golang.org/p/S4Yj1yr16t2

How to bind to LDAP server with TLS using golang?

I have the following code that works perfectly when binding to an LDAP server without TLS/SSL but when I try to bind to a LDAP server that has TLS setup, it doesn't bind.
/*In order to use this program, the user needs to get the package by running the following command:
go get gopkg.in/ldap.v2*/
package main
import (
"fmt"
"strings"
"gopkg.in/ldap.v2"
"os"
)
//Gives constants to be used for binding to and searching the LDAP server.
const (
ldapServer = "ldaps://test.com:636"
ldapBind = "CN=ad_binder,CN=Users,DC=dev,DC=test,DC=com"
ldapPassword = "Password"
filterDN = "(objectclass=*)"
baseDN = "dc=dev,dc=test,dc=com"
loginUsername = "ad_binder"
loginPassword = "Password"
)
//Main function, which is executed.
func main() {
conn, err := connect()
//If there is an error connecting to server, prints this
if err != nil {
fmt.Printf("Failed to connect. %s", err)
return
}
//Close the connection at a later time.
defer conn.Close()
//Declares err to be list(conn), and checks if any errors. It prints the error(s) if there are any.
if err := list(conn); err != nil {
fmt.Printf("%v", err)
return
}
//Declares err to be auth(conn), and checks if any errors. It prints the error(s) if there are any.
if err := auth(conn); err != nil {
fmt.Printf("%v", err)
return
}
}
//This function is used to connect to the LDAP server.
func connect() (*ldap.Conn, error) {
conn, err := ldap.Dial("tcp", ldapServer)
if err != nil {
return nil, fmt.Errorf("Failed to connect. %s", err)
}
if err := conn.Bind(ldapBind, ldapPassword); err != nil {
return nil, fmt.Errorf("Failed to bind. %s", err)
}
return conn, nil
}
//This function is used to search the LDAP server as well as output the attributes of the entries.
func list(conn *ldap.Conn) error {
//This gets the command line argument and saves it in the form "(argument=*)"
arg := ""
filter := ""
if len(os.Args) > 1{
arg = os.Args[1]
fmt.Println(arg)
filter = "(" + arg + "=*)"
} else{
fmt.Println("You need to input an argument for an attribute to search. I.E. : \"go run anonymous_query.go cn\"")
}
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
fmt.Sprintf(filter),
//To add anymore strings to the search, you need to add it here.
[]string{},
nil,
))
if err != nil {
return fmt.Errorf("Failed to search users. %s", err)
}
//Prints all the attributes per entry
for _, entry := range result.Entries {
entry.Print()
fmt.Println()
}
return nil
}
//This function authorizes the user and binds to the LDAP server.
func auth(conn *ldap.Conn) error {
result, err := conn.Search(ldap.NewSearchRequest(
baseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter(loginUsername),
[]string{"dn"},
nil,
))
if err != nil {
return fmt.Errorf("Failed to find user. %s", err)
}
if len(result.Entries) < 1 {
return fmt.Errorf("User does not exist")
}
if len(result.Entries) > 1 {
return fmt.Errorf("")
}
if err := conn.Bind(result.Entries[0].DN, loginPassword); err != nil {
fmt.Printf("Failed to auth. %s", err)
} else {
fmt.Printf("Authenticated successfuly!")
}
return nil
}
func filter(needle string) string {
res := strings.Replace(
filterDN,
"{username}",
needle,
-1,
)
return res
}
Here is the error I get when I try to run it:
Failed to connect. Failed to connect. LDAP Result Code 200 "Network Error": dial tcp: address ldaps://test.com:636: too many colons in address
Which is weird since it works with a different LDAP server just fine but with the following:
ldapServer = "10.1.30.47:389"
instead of
ldapServer = "ldaps://test.com:636"
So any help would be greatly appreciated, thanks!
gopkg.in/ldap.v2 does not support URI addressing (i.e. you cannot put the scheme ldap:// or ldaps:// before the network address).
Note: gopkg.in/ldap.v3 does support URI dialing (ldaps://test.com) via DialURL.
If you are using gopkg.in/ldap.v2, you can still establish a direct TLS connection, but you must use the function DialTLS, and use a network address (not a URI):
// addr = "10.1.30.47:389"
// tlsAddr = "10.1.30.47:636"
// conn, err = ldap.Dial("tcp", addr) // non-TLS
// serverName = "test.com" TLS cert name will be verified
// serverName = "" TLS verify will be disabled (DON'T DO THIS IN PRODUCTION)
tlsConf, err := getTLSconfig(serverName)
if err != nil { /* */ }
conn, err = ldap.DialTLS("tcp", tlsAddr, tlsConf)
the above needs a *tls.Config, so use a helper function like:
func getTLSconfig(tlsName string) (tlsC *tls.Config, err error) {
if tlsName != "" {
tlsC = &tls.Config{
ServerName: tlsName,
}
return
}
log.Println("No TLS verification enabled! ***STRONGLY*** recommend adding a trust file to the config.")
tlsC = &tls.Config{
InsecureSkipVerify: true,
}
return
}

How should I switch based on the URL

This is my URL in Postman: http://localhost:8000/filter?Name=Srinu
I am passing a query string to fetch records on the database based on a query.
In my database having Name, Gender, and Age, how should I write the switch statement based on the below code snippet, that I have to get data based on my query?
switch ? {
case "Name":
fiName := bson.D{{"Name", name}}
err = uc.session.DB(DB_NAME).C(DB_COLLECTION).Find(fiName).All(&json1)
if err == nil {
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, &json1)
} else {
c.JSON(500, gin.H{"result": "An error occured"})
}
case "Gender":
fiGender := bson.D{{"Gender", gender}}
err = uc.session.DB(DB_NAME).C(DB_COLLECTION).Find(fiGender).All(&json1)
if err == nil {
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, &json1)
} else {
c.JSON(500, gin.H{"result": "An error occured"})
}
case "Age":
fiAge := bson.D{{"Age", age}}
err = uc.session.DB(DB_NAME).C(DB_COLLECTION).Find(fiAge).All(&json1)
if err == nil {
c.Writer.Header().Set("Content-Type", "application/json")
c.JSON(201, &json1)
} else {
c.JSON(500, gin.H{"result": "An error occured"})
}
}
Use the following code:
r := ctx.Request
r.ParseForm()
switch {
case len(r.Form["Name"]) > 0:
name := r.Form["Name"][0]
fiName := bson.D{{"Name", name}}
err = uc.session.DB(DB_NAME).C(DB_COLLECTION).Find(fiName).All(&json1)
...
}
The call r.ParseForm() sets r.Form using data parsed from the request.
The field r.Form is a map of string slices. If a parameter is present in the query, then the length of the string slice for the parameter is greater than zero.
The statement name := r.Form["Name"][0] gets the first parameter value for Name. If request is http://localhost:8000/filter?Name=Srinu&Name=Reflect, then the second value Reflect is silently ignored.
You can use URL to see the results you can check
package main
import (
"fmt"
"net/url"
)
func main() {
u, err := url.Parse("http://localhost:8000/filter?Name=Srinu")
if err != nil {
fmt.Println(err)
}
query :=u.RawQuery
parsed, err := url.ParseQuery(query)
if err != nil {
fmt.Println(err)
}
switch true {
case parsed.Get("Name") != "" :
fmt.Println(parsed["Name"])
case parsed.Get("Age") != "":
fmt.Println(parsed["Age"])
default:
return
}
}

Error Handling within a for loop in Go results probably in a next iteration

I struggle with a specific Go implementation for sending log files to different locations:
package main
func isDestinationSIEM(json_msg string, json_obj *jason.Object, siem_keys []string) (bool) {
if json_obj != nil {
dest, err := json_obj.GetString("destination")
if err == nil {
if strings.Contains(dest,"SIEM") {
return true
}
}
for _, key := range siem_keys {
if strings.Contains(json_msg, key) {
return true
}
}
}
return false
}
func sendToSIEM(siem_dst string, json_msg string) (error) {
// Create connection to syslog server
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
fmt.Println("failed to parse root certificate")
}
config := &tls.Config{RootCAs: roots, InsecureSkipVerify: true}
conn, err := tls.Dial("tcp", siem_dst, config)
if err != nil {
fmt.Println("Error connecting SIEM")
fmt.Println(err.Error())
} else {
// Send log message
_, err = fmt.Fprintf(conn, json_msg)
if err != nil {
fmt.Println("Error sending SIEM message: ", json_msg)
fmt.Println(err.Error())
}
}
defer conn.Close()
return err
}
func main() {
// simplified code otherwise there would have been too much
// but the 'devil' is this for loop
for _, obj := range objects {
// first check
isSIEM := isDestinationSIEM(obj, siem_keys)
if isSIEM {
err := sendToSIEM(obj)
if err != nil {
// print error
}
isAUDIT:= isDestinationSIEM(obj)
if isAUDIT {
err := sendToAUDIT(obj)
if err != nil {
// print error
}
} // end of for
}
When the 'if isSIEM' returns an error, the second check 'if isAUDIT' is not conducted.
Why is this? If an error is returned, does the loop start with the next iteration?
The error looks like this:
runtime error: invalid memory address or nil pointer dereference: errorString (which lists a couple of go packages)
The error looks like this: runtime error: invalid memory address or nil pointer dereference: errorString (which lists a couple of go packages)
It means you catch the panic() and your program has been stopped that means your circle for is stopped too.
Here details how works with panic https://blog.golang.org/defer-panic-and-recover

Resources