Skip to content

Commit

Permalink
Add input plugin for OpenLDAP (influxdata#2612)
Browse files Browse the repository at this point in the history
  • Loading branch information
acobaugh authored and jeichorn committed Jul 24, 2017
1 parent 0f56baf commit 73e5c15
Show file tree
Hide file tree
Showing 7 changed files with 427 additions and 2 deletions.
2 changes: 2 additions & 0 deletions Godeps
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ github.com/zensqlmonitor/go-mssqldb ffe5510c6fa5e15e6d983210ab501c815b56b363
golang.org/x/crypto dc137beb6cce2043eb6b5f223ab8bf51c32459f4
golang.org/x/net f2499483f923065a842d38eb4c7f1927e6fc6e6d
golang.org/x/text 506f9d5c962f284575e88337e7d9296d27e729d3
gopkg.in/asn1-ber.v1 4e86f4367175e39f69d9358a5f17b4dda270378d
gopkg.in/fatih/pool.v2 6e328e67893eb46323ad06f0e92cb9536babbabc
gopkg.in/gorethink/gorethink.v3 7ab832f7b65573104a555d84a27992ae9ea1f659
gopkg.in/ldap.v2 8168ee085ee43257585e50c6441aadf54ecb2c9f
gopkg.in/mgo.v2 3f83fa5005286a7fe593b055f0d7771a7dce4655
gopkg.in/olivere/elastic.v5 3113f9b9ad37509fe5f8a0e5e91c96fdc4435e26
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ docker-run:
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
docker run --name riemann -p "5555:5555" -d stealthly/docker-riemann
docker run --name nats -p "4222:4222" -d nats
docker run --name openldap \
-e SLAPD_CONFIG_ROOTDN="cn=manager,cn=config" \
-e SLAPD_CONFIG_ROOTPW="secret" \
-p "389:389" -p "636:636" \
-d cobaugh/openldap-alpine

# Run docker containers necessary for CircleCI unit tests
docker-run-circle:
Expand All @@ -88,11 +93,16 @@ docker-run-circle:
docker run --name mqtt -p "1883:1883" -d ncarlier/mqtt
docker run --name riemann -p "5555:5555" -d stealthly/docker-riemann
docker run --name nats -p "4222:4222" -d nats
docker run --name openldap \
-e SLAPD_CONFIG_ROOTDN="cn=manager,cn=config" \
-e SLAPD_CONFIG_ROOTPW="secret" \
-p "389:389" -p "636:636" \
-d cobaugh/openldap-alpine

# Kill all docker containers, ignore errors
docker-kill:
-docker kill nsq aerospike redis rabbitmq postgres memcached mysql zookeeper kafka mqtt riemann nats elasticsearch
-docker rm nsq aerospike redis rabbitmq postgres memcached mysql zookeeper kafka mqtt riemann nats elasticsearch
-docker kill nsq aerospike redis rabbitmq postgres memcached mysql zookeeper kafka mqtt riemann nats elasticsearch openldap
-docker rm nsq aerospike redis rabbitmq postgres memcached mysql zookeeper kafka mqtt riemann nats elasticsearch openldap

# Run full unit tests using docker containers (includes setup and teardown)
test: vet docker-kill docker-run
Expand Down
2 changes: 2 additions & 0 deletions docs/LICENSE_OF_DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ works:
- golang.org/x/crypto [BSD](https://github.com/golang/crypto/blob/master/LICENSE)
- golang.org/x/net [BSD](https://go.googlesource.com/net/+/master/LICENSE)
- golang.org/x/text [BSD](https://go.googlesource.com/text/+/master/LICENSE)
- gopkg.in/asn1-ber.v1 [MIT](https://github.com/go-asn1-ber/asn1-ber/blob/v1.2/LICENSE)
- gopkg.in/dancannon/gorethink.v1 [APACHE](https://github.com/dancannon/gorethink/blob/v1.1.2/LICENSE)
- gopkg.in/fatih/pool.v2 [MIT](https://github.com/fatih/pool/blob/v2.0.0/LICENSE)
- gopkg.in/ldap.v2 [MIT](https://github.com/go-ldap/ldap/blob/v2.5.0/LICENSE)
- gopkg.in/mgo.v2 [BSD](https://github.com/go-mgo/mgo/blob/v2/LICENSE)
- gopkg.in/olivere/elastic.v5 [MIT](https://github.com/olivere/elastic/blob/v5.0.38/LICENSE)
- gopkg.in/yaml.v2 [APACHE](https://github.com/go-yaml/yaml/blob/v2/LICENSE)
1 change: 1 addition & 0 deletions plugins/inputs/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/nsq_consumer"
_ "github.com/influxdata/telegraf/plugins/inputs/nstat"
_ "github.com/influxdata/telegraf/plugins/inputs/ntpq"
_ "github.com/influxdata/telegraf/plugins/inputs/openldap"
_ "github.com/influxdata/telegraf/plugins/inputs/passenger"
_ "github.com/influxdata/telegraf/plugins/inputs/phpfpm"
_ "github.com/influxdata/telegraf/plugins/inputs/ping"
Expand Down
82 changes: 82 additions & 0 deletions plugins/inputs/openldap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Openldap Input Plugin

This plugin gathers metrics from OpenLDAP's cn=Monitor backend.

### Configuration:

```toml
[[inputs.openldap]]
host = "localhost"
port = 389

# ldaps, starttls, or no encryption. default is an empty string, disabling all encryption.
# note that port will likely need to be changed to 636 for ldaps
# valid options: "" | "starttls" | "ldaps"
ssl = ""

# skip peer certificate verification. Default is false.
insecure_skip_verify = false

# Path to PEM-encoded Root certificate to use to verify server certificate
ssl_ca = "/etc/ssl/certs.pem"

# dn/password to bind with. If bind_dn is empty, an anonymous bind is performed.
bind_dn = ""
bind_password = ""
```

### Measurements & Fields:

All **monitorCounter**, **monitorOpInitiated**, and **monitorOpCompleted** attributes are gathered based on this LDAP query:

```(|(objectClass=monitorCounterObject)(objectClass=monitorOperation))```

Metric names are based on their entry DN.

Metrics for the **monitorOp*** attributes have **_initiated** and **_completed** added to the base name.

An OpenLDAP 2.4 server will provide these metrics:

- openldap
- max_file_descriptors_connections
- current_connections
- total_connections
- abandon_operations_completed
- abandon_operations_initiated
- add_operations_completed
- add_operations_initiated
- bind_operations_completed
- bind_operations_initiated
- compare_operations_completed
- compare_operations_initiated
- delete_operations_completed
- delete_operations_initiated
- extended_operations_completed
- extended_operations_initiated
- modify_operations_completed
- modify_operations_initiated
- modrdn_operations_completed
- modrdn_operations_initiated
- search_operations_completed
- search_operations_initiated
- unbind_operations_completed
- unbind_operations_initiated
- bytes_statistics
- entries_statistics
- pdu_statistics
- referrals_statistics
- read_waiters
- write_waiters

### Tags:

- server= # value from config
- port= # value from config

### Example Output:

```
$ telegraf -config telegraf.conf -input-filter openldap -test --debug
* Plugin: inputs.openldap, Collection 1
> openldap,server=localhost,port=389,host=zirzla search_operations_completed=2i,delete_operations_completed=0i,read_waiters=1i,total_connections=1004i,bind_operations_completed=3i,unbind_operations_completed=3i,referrals_statistics=0i,current_connections=1i,bind_operations_initiated=3i,compare_operations_completed=0i,add_operations_completed=2i,delete_operations_initiated=0i,unbind_operations_initiated=3i,search_operations_initiated=3i,add_operations_initiated=2i,max_file_descriptors_connections=4096i,abandon_operations_initiated=0i,write_waiters=0i,modrdn_operations_completed=0i,abandon_operations_completed=0i,pdu_statistics=23i,modify_operations_initiated=0i,bytes_statistics=1660i,entries_statistics=17i,compare_operations_initiated=0i,modrdn_operations_initiated=0i,extended_operations_completed=0i,modify_operations_completed=0i,extended_operations_initiated=0i 1499990455000000000
```
178 changes: 178 additions & 0 deletions plugins/inputs/openldap/openldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package openldap

import (
"fmt"
"strconv"
"strings"

"gopkg.in/ldap.v2"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)

type Openldap struct {
Host string
Port int
Ssl string
InsecureSkipVerify bool
SslCa string
BindDn string
BindPassword string
}

const sampleConfig string = `
host = "localhost"
port = 389
# ldaps, starttls, or no encryption. default is an empty string, disabling all encryption.
# note that port will likely need to be changed to 636 for ldaps
# valid options: "" | "starttls" | "ldaps"
ssl = ""
# skip peer certificate verification. Default is false.
insecure_skip_verify = false
# Path to PEM-encoded Root certificate to use to verify server certificate
ssl_ca = "/etc/ssl/certs.pem"
# dn/password to bind with. If bind_dn is empty, an anonymous bind is performed.
bind_dn = ""
bind_password = ""
`

var searchBase = "cn=Monitor"
var searchFilter = "(|(objectClass=monitorCounterObject)(objectClass=monitorOperation))"
var searchAttrs = []string{"monitorCounter", "monitorOpInitiated", "monitorOpCompleted"}
var attrTranslate = map[string]string{
"monitorCounter": "",
"monitorOpInitiated": "_initiated",
"monitorOpCompleted": "_completed",
}

func (o *Openldap) SampleConfig() string {
return sampleConfig
}

func (o *Openldap) Description() string {
return "OpenLDAP cn=Monitor plugin"
}

// return an initialized Openldap
func NewOpenldap() *Openldap {
return &Openldap{
Host: "localhost",
Port: 389,
Ssl: "",
InsecureSkipVerify: false,
SslCa: "",
BindDn: "",
BindPassword: "",
}
}

// gather metrics
func (o *Openldap) Gather(acc telegraf.Accumulator) error {
var err error
var l *ldap.Conn
if o.Ssl != "" {
// build tls config
tlsConfig, err := internal.GetTLSConfig("", "", o.SslCa, o.InsecureSkipVerify)
if err != nil {
acc.AddError(err)
return nil
}
if o.Ssl == "ldaps" {
l, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", o.Host, o.Port), tlsConfig)
if err != nil {
acc.AddError(err)
return nil
}
} else if o.Ssl == "starttls" {
l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", o.Host, o.Port))
if err != nil {
acc.AddError(err)
return nil
}
err = l.StartTLS(tlsConfig)
} else {
acc.AddError(fmt.Errorf("Invalid setting for ssl: %s", o.Ssl))
return nil
}
} else {
l, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", o.Host, o.Port))
}

if err != nil {
acc.AddError(err)
return nil
}
defer l.Close()

// username/password bind
if o.BindDn != "" && o.BindPassword != "" {
err = l.Bind(o.BindDn, o.BindPassword)
if err != nil {
acc.AddError(err)
return nil
}
}

searchRequest := ldap.NewSearchRequest(
searchBase,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
searchFilter,
searchAttrs,
nil,
)

sr, err := l.Search(searchRequest)
if err != nil {
acc.AddError(err)
return nil
}

gatherSearchResult(sr, o, acc)

return nil
}

func gatherSearchResult(sr *ldap.SearchResult, o *Openldap, acc telegraf.Accumulator) {
fields := map[string]interface{}{}
tags := map[string]string{
"server": o.Host,
"port": strconv.Itoa(o.Port),
}
for _, entry := range sr.Entries {
metricName := dnToMetric(entry.DN, searchBase)
for _, attr := range entry.Attributes {
if len(attr.Values[0]) >= 1 {
if v, err := strconv.ParseInt(attr.Values[0], 10, 64); err == nil {
fields[metricName+attrTranslate[attr.Name]] = v
}
}
}
}
acc.AddFields("openldap", fields, tags)
return
}

// Convert a DN to metric name, eg cn=Read,cn=Waiters,cn=Monitor to read_waiters
func dnToMetric(dn, searchBase string) string {
metricName := strings.Trim(dn, " ")
metricName = strings.Replace(metricName, " ", "_", -1)
metricName = strings.ToLower(metricName)
metricName = strings.TrimPrefix(metricName, "cn=")
metricName = strings.Replace(metricName, strings.ToLower(searchBase), "", -1)
metricName = strings.Replace(metricName, "cn=", "_", -1)
return strings.Replace(metricName, ",", "", -1)
}

func init() {
inputs.Add("openldap", func() telegraf.Input { return NewOpenldap() })
}
Loading

0 comments on commit 73e5c15

Please sign in to comment.