Skip to content

Commit

Permalink
Changes to query the AD service interface (ADSI)
Browse files Browse the repository at this point in the history
  • Loading branch information
rnishtala-sumo committed Oct 13, 2023
1 parent beb71a9 commit ac7b2cb
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 159 deletions.
34 changes: 9 additions & 25 deletions pkg/receiver/activedirectoryinvreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,15 @@ Supported pipeline types: logs
```yaml
receivers:
activedirectoryinv:
# Common name
# default: "test user"
CN: "test user"
# Base DN
base_dn: "CN=Users,DC=exampledomain,DC=com"

# Organizational Unit
# default: "test"
OU: "test"

# Password for authenticating with the AD server
# default: "test"
Password: "test"

# Domain name
# default: "exampledomain.com"
DC: "exampledomain.com"

# The fully qualified domain name (FQDN).
# default: "examplehost"
Host: "hostname.exampledomain.com"
# User attributes
attributes: [name, mail, department, manager, memberOf]

# The polling interval.
# default = 60
PollInterval: 60
poll_interval: 60
```
The full list of settings exposed for this receiver are documented in
Expand All @@ -44,12 +30,10 @@ Example configuration:
```yaml
receivers:
## All my example logs
activedirectoryinvreceiver:
cn: "test user"
ou: "test"
password: "Exampledomain@123"
domain: "exampledomain"
host: "EC2AMAZ.exampledomain.com"
activedirectoryinv:
base_dn: "CN=Users,DC=exampledomain,DC=com"
attributes: [name, mail, department, manager, memberOf]
poll_interval: 60

exporters:
logging:
Expand Down
97 changes: 52 additions & 45 deletions pkg/receiver/activedirectoryinvreceiver/adinvreceiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@ import (
"context"
"fmt"
"log"
"strings"
"sync"
"time"

"github.com/go-ldap/ldap/v3"
adsi "github.com/go-adsi/adsi"
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
Expand Down Expand Up @@ -82,61 +81,69 @@ func (l *ADReceiver) startPolling(ctx context.Context) {

func (r *ADReceiver) poll(ctx context.Context) error {
go func() {
l, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", r.config.Host, 389))
client, err := adsi.NewClient()
if err != nil {
log.Fatal(err)
r.logger.Error("Failed to create client:", zap.Error(err))
return
}
defer l.Close()

fqdn := strings.Split(r.config.DC, ".")
domain, hld := fqdn[0], fqdn[1]

err = l.Bind(fmt.Sprintf("cn=%s,ou=%s,dc=%s, dc=%s", r.config.CN, r.config.OU, domain, hld), r.config.Password)
ldapPath := fmt.Sprintf("LDAP://%s", r.config.DN)
root, err := client.Open(ldapPath)
if err != nil {
log.Fatal(err)
r.logger.Error("Failed to open root object:", zap.Error(err))
return
}

searchRequest := ldap.NewSearchRequest(
"dc=exampledomain,dc=com",
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"(&(objectClass=organizationalPerson))",
[]string{"dn", "cn", "sAMAccountName", "mail", "department", "manager", "memberOf"},
nil,
)

sr, err := l.Search(searchRequest)
rootContainer, err := root.ToContainer()
if err != nil {
log.Fatal(err)
r.logger.Error("Failed to open root object:", zap.Error(err))
return
}

defer rootContainer.Close()
logs := plog.NewLogs()
rl := logs.ResourceLogs().AppendEmpty()
resourceLogs := &rl
resourceAttributes := resourceLogs.Resource().Attributes()
resourceAttributes.PutStr("host.name", r.config.Host)
_ = resourceLogs.ScopeLogs().AppendEmpty()

for _, entry := range sr.Entries {
if ctx.Err() != nil {
// If the collector has been shutdown
break
}
var attributes string
attributes += fmt.Sprintf("CN: %s, ", entry.GetAttributeValue("cn"))
attributes += fmt.Sprintf("sAMAccountName: %s, ", entry.GetAttributeValue("sAMAccountName"))
attributes += fmt.Sprintf("mail: %s, ", entry.GetAttributeValue("mail"))
attributes += fmt.Sprintf("department: %s, ", entry.GetAttributeValue("department"))
attributes += fmt.Sprintf("manager: %s, ", entry.GetAttributeValue("manager"))
attributes += fmt.Sprintf("memberOf: %v, ", entry.GetAttributeValues("memberOf"))
fmt.Println(attributes)
logRecord := resourceLogs.ScopeLogs().At(0).LogRecords().AppendEmpty()
logRecord.Body().SetStr(attributes)
err := r.consumer.ConsumeLogs(ctx, logs)
if err != nil {
r.logger.Error("Error consuming log", zap.Error(err))
}
r.traverse(rootContainer, resourceLogs)
err = r.consumer.ConsumeLogs(ctx, logs)
if err != nil {
r.logger.Error("Error consuming log", zap.Error(err))
}
}()

return nil
}

func (l *ADReceiver) printAttrs(user *adsi.Object, resourceLogs *plog.ResourceLogs) {
attrs := l.config.Attributes
attributes := ""
for _, attr := range attrs {
values, err := user.Attr(attr)
if err == nil && len(values) > 0 {
attributes += fmt.Sprintf("%s: %v\n", attr, values)
}
}
logRecord := resourceLogs.ScopeLogs().At(0).LogRecords().AppendEmpty()
logRecord.Body().SetStr(attributes)
}

func (l *ADReceiver) traverse(node *adsi.Container, resourceLogs *plog.ResourceLogs) {
nodeObject, err := node.ToObject()
if err != nil {
log.Printf("Error creating objects: %v\n", err)
return
}
l.printAttrs(nodeObject, resourceLogs)
children, err := node.Children()
if err != nil {
log.Printf("Error retrieving children: %v\n", err)
return
}
for child, err := children.Next(); err == nil; child, err = children.Next() {
childContainer, err := child.ToContainer()
if err != nil {
log.Println("Failed to traverse child object:", err)
return
}
l.traverse(childContainer, resourceLogs)
}
children.Close()
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import (

func TestStart(t *testing.T) {
cfg := CreateDefaultConfig().(*ADConfig)
cfg.CN = "test user"
cfg.DN = "CN=Guest,CN=Users,DC=exampledomain,DC=com"

sink := &consumertest.LogsSink{}
logsRcvr := newLogsReceiver(cfg, zap.NewNop(), sink)
Expand Down
36 changes: 11 additions & 25 deletions pkg/receiver/activedirectoryinvreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,49 +16,35 @@ package activedirectoryinvreceiver

import (
"errors"
"regexp"
"time"
)

// ADConfig defines configuration for Active Directory Inventory receiver.

type ADConfig struct {
CN string `mapstructure:"cn"`
OU string `mapstructure:"ou"`
Password string `mapstructure:"password"`
DC string `mapstructure:"domain"`
Host string `mapstructure:"host"`
DN string `mapstructure:"base_dn"` // DN is the base distinguished name to search from
Attributes []string `mapstructure:"attributes"`
PollInterval time.Duration `mapstructure:"poll_interval"`
}

var (
errNoCN = errors.New("no common name configured")
errNoOU = errors.New("no organizational unit configured")
errNoPassword = errors.New("no password configured")
errNoDC = errors.New("no domain configured")
errNoHost = errors.New("no host configured")
errInvalidDN = errors.New("Base DN is incorrect, it must be in the format of CN=Users,DC=exampledomain,DC=com")
errInvalidPollInterval = errors.New("poll interval is incorrect, it must be a duration greater than one second")
)

// Validate validates all portions of the relevant config
func (c *ADConfig) Validate() error {
if c.CN == "" {
return errNoCN
}

if c.OU == "" {
return errNoOU
}
// Define the regular expression pattern for a valid Base DN
pattern := `^((CN|OU)=[^,]+(,|$))*((DC=[^,]+),?)+$`

if c.Password == "" {
return errNoPassword
}

if c.DC == "" {
return errNoDC
}
// Compile the regular expression pattern
regex := regexp.MustCompile(pattern)

if c.Host == "" {
return errNoHost
// Check if the Base DN is valid
if !regex.MatchString(c.DN) {
return errInvalidDN
}

if c.PollInterval < 0 {
Expand Down
36 changes: 23 additions & 13 deletions pkg/receiver/activedirectoryinvreceiver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,41 @@ func TestValidate(t *testing.T) {
expectedErr error
}{
{
name: "Valid Config",
name: "Valid Config CN",
config: ADConfig{
CN: "test user",
OU: "test",
Password: "test",
DC: "exampledomain.com",
Host: "hostname.exampledomain.com",
DN: "CN=Guest,CN=Users,DC=exampledomain,DC=com",
Attributes: []string{"name"},
PollInterval: 60,
},
},
{
name: "Valid Config DC",
config: ADConfig{
DN: "DC=exampledomain,DC=com",
Attributes: []string{"name"},
PollInterval: 60,
},
},
{
name: "Valid Config OU",
config: ADConfig{
DN: "CN=Guest,OU=Users,DC=exampledomain,DC=com",
Attributes: []string{"name"},
PollInterval: 60,
},
},
{
name: "Invalid No CN",
config: ADConfig{
CN: "",
DN: "",
},
expectedErr: errNoCN,
expectedErr: errInvalidDN,
},
{
name: "Invalid Poll Interval",
config: ADConfig{
CN: "test user",
OU: "test",
Password: "test",
DC: "exampledomain.com",
Host: "hostname.exampledomain.com",
DN: "CN=Users,DC=exampledomain,DC=com",
Attributes: []string{"name"},
PollInterval: -1,
},
expectedErr: errInvalidPollInterval,
Expand Down
7 changes: 2 additions & 5 deletions pkg/receiver/activedirectoryinvreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,8 @@ func NewFactory() receiver.Factory {
// CreateDefaultConfig creates the default configuration for the receiver
func CreateDefaultConfig() component.Config {
return &ADConfig{
CN: "test user",
OU: "test",
Password: "test",
DC: "exampledomain.com",
Host: "hostname.exampledomain.com",
DN: "CN=Guest,CN=Users,DC=exampledomain,DC=com",
Attributes: []string{"name"},
PollInterval: 60,
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/receiver/activedirectoryinvreceiver/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestType(t *testing.T) {

func TestCreateLogsReceiver(t *testing.T) {
cfg := CreateDefaultConfig().(*ADConfig)
cfg.CN = "test user"
cfg.DN = "CN=Guest,CN=Users,DC=exampledomain,DC=com" // valid DN
_, err := NewFactory().CreateLogsReceiver(
context.Background(),
receivertest.NewNopCreateSettings(),
Expand Down
8 changes: 4 additions & 4 deletions pkg/receiver/activedirectoryinvreceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/SumoLogic/sumologic-otel-collector/pkg/receiver/activedirector
go 1.20

require (
github.com/go-ldap/ldap/v3 v3.4.6
github.com/go-adsi/adsi v0.0.0-20230315214257-4ff46d735e52
github.com/stretchr/testify v1.8.4
go.opentelemetry.io/collector/component v0.86.0
go.opentelemetry.io/collector/consumer v0.86.0
Expand All @@ -13,9 +13,8 @@ require (
)

require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.3.1 // indirect
Expand All @@ -29,14 +28,15 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/scjalliance/comshim v0.0.0-20230315213746-5e51f40bd3b9 // indirect
github.com/scjalliance/comutil v0.0.0-20230315211610-645474dab300 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.86.0 // indirect
go.opentelemetry.io/collector/confmap v0.86.0 // indirect
go.opentelemetry.io/collector/featuregate v1.0.0-rcv0015 // indirect
go.opentelemetry.io/otel v1.18.0 // indirect
go.opentelemetry.io/otel/metric v1.18.0 // indirect
go.opentelemetry.io/otel/trace v1.18.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.13.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
Expand Down
Loading

0 comments on commit ac7b2cb

Please sign in to comment.