-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(inputs.ldap): Add LDAP input plugin supporting OpenLDAP and 389ds (
- Loading branch information
Showing
8 changed files
with
1,106 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
//go:build !custom || inputs || inputs.ldap | ||
|
||
package all | ||
|
||
import _ "github.com/influxdata/telegraf/plugins/inputs/ldap" // register plugin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package ldap | ||
|
||
import ( | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/go-ldap/ldap/v3" | ||
"github.com/influxdata/telegraf" | ||
"github.com/influxdata/telegraf/metric" | ||
) | ||
|
||
// Empty mappings are identity mappings | ||
var attrMap389ds = map[string]string{ | ||
"addentryops": "add_operations", | ||
"anonymousbinds": "anonymous_binds", | ||
"bindsecurityerrors": "bind_security_errors", | ||
"bytesrecv": "bytes_received", | ||
"bytessent": "bytes_sent", | ||
"cacheentries": "cache_entries", | ||
"cachehits": "cache_hits", | ||
"chainings": "", | ||
"compareops": "compare_operations", | ||
"connections": "", | ||
"connectionsinmaxthreads": "connections_in_max_threads", | ||
"connectionsmaxthreadscount": "connections_max_threads", | ||
"copyentries": "copy_entries", | ||
"currentconnections": "current_connections", | ||
"currentconnectionsatmaxthreads": "current_connections_at_max_threads", | ||
"dtablesize": "", | ||
"entriesreturned": "entries_returned", | ||
"entriessent": "entries_sent", | ||
"errors": "", | ||
"inops": "in_operations", | ||
"listops": "list_operations", | ||
"removeentryops": "delete_operations", | ||
"masterentries": "master_entries", | ||
"maxthreadsperconnhits": "maxthreads_per_conn_hits", | ||
"modifyentryops": "modify_operations", | ||
"modifyrdnops": "modrdn_operations", | ||
"nbackends": "backends", | ||
"onelevelsearchops": "onelevel_search_operations", | ||
"opscompleted": "operations_completed", | ||
"opsinitiated": "operations_initiated", | ||
"readops": "read_operations", | ||
"readwaiters": "read_waiters", | ||
"referrals": "referrals", | ||
"referralsreturned": "referrals_returned", | ||
"searchops": "search_operations", | ||
"securityerrors": "security_errors", | ||
"simpleauthbinds": "simpleauth_binds", | ||
"slavehits": "slave_hits", | ||
"strongauthbinds": "strongauth_binds", | ||
"threads": "", | ||
"totalconnections": "total_connections", | ||
"unauthbinds": "unauth_binds", | ||
"wholesubtreesearchops": "wholesubtree_search_operations", | ||
} | ||
|
||
func (l *LDAP) new389dsConfig() []request { | ||
attributes := make([]string, 0, len(attrMap389ds)) | ||
for k := range attrMap389ds { | ||
attributes = append(attributes, k) | ||
} | ||
|
||
req := ldap.NewSearchRequest( | ||
"cn=Monitor", | ||
ldap.ScopeWholeSubtree, | ||
ldap.NeverDerefAliases, | ||
0, | ||
0, | ||
false, | ||
"(objectClass=*)", | ||
attributes, | ||
nil, | ||
) | ||
return []request{{req, l.convert389ds}} | ||
} | ||
|
||
func (l *LDAP) convert389ds(result *ldap.SearchResult, ts time.Time) []telegraf.Metric { | ||
tags := map[string]string{ | ||
"server": l.host, | ||
"port": l.port, | ||
} | ||
fields := make(map[string]interface{}) | ||
for _, entry := range result.Entries { | ||
for _, attr := range entry.Attributes { | ||
if len(attr.Values[0]) == 0 { | ||
continue | ||
} | ||
// Map the attribute-name to the field-name | ||
name := attrMap389ds[attr.Name] | ||
if name == "" { | ||
name = attr.Name | ||
} | ||
// Reverse the name if requested | ||
if l.ReverseFieldNames { | ||
parts := strings.Split(name, "_") | ||
for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { | ||
parts[i], parts[j] = parts[j], parts[i] | ||
} | ||
name = strings.Join(parts, "_") | ||
} | ||
|
||
// Convert the number | ||
if v, err := strconv.ParseInt(attr.Values[0], 10, 64); err == nil { | ||
fields[name] = v | ||
} | ||
} | ||
} | ||
|
||
m := metric.New("389ds", tags, fields, ts) | ||
return []telegraf.Metric{m} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# LDAP Input Plugin | ||
|
||
This plugin gathers metrics from LDAP servers' monitoring (`cn=Monitor`) | ||
backend. Currently this plugin supports [OpenLDAP](https://www.openldap.org/) | ||
and [389ds](https://www.port389.org/) servers. | ||
|
||
## Global configuration options <!-- @/docs/includes/plugin_config.md --> | ||
|
||
In addition to the plugin-specific configuration settings, plugins support | ||
additional global and plugin configuration settings. These settings are used to | ||
modify metrics, tags, and field or create aliases and configure ordering, etc. | ||
See the [CONFIGURATION.md][CONFIGURATION.md] for more details. | ||
|
||
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins | ||
|
||
## Configuration | ||
|
||
```toml @sample.conf | ||
# LDAP monitoring plugin | ||
[[inputs.openldap]] | ||
## Server to monitor | ||
## The scheme determines the mode to use for connection with | ||
## ldap://... -- unencrypted (non-TLS) connection | ||
## ldaps://... -- TLS connection | ||
## starttls://... -- StartTLS connection | ||
## If no port is given, the default ports, 389 for ldap and starttls and | ||
## 636 for ldaps, are used. | ||
server = "ldap://localhost" | ||
|
||
## Server dialect, can be "openldap" or "389ds" | ||
# dialect = "openldap" | ||
|
||
# DN and password to bind with | ||
## If bind_dn is empty an anonymous bind is performed. | ||
bind_dn = "" | ||
bind_password = "" | ||
|
||
## Reverse the field names constructed from the monitoring DN | ||
# reverse_field_names = false | ||
|
||
## Optional TLS Config | ||
## Trusted root certificates for server | ||
# tls_ca = "/path/to/cafile" | ||
## Used for TLS client certificate authentication | ||
# tls_cert = "/path/to/certfile" | ||
## Used for TLS client certificate authentication | ||
# tls_key = "/path/to/keyfile" | ||
## Send the specified TLS server name via SNI | ||
# tls_server_name = "kubernetes.example.com" | ||
## Use TLS but skip chain & host verification | ||
# insecure_skip_verify = false | ||
``` | ||
|
||
To use this plugin you must enable the monitoring backend/plugin of your LDAP | ||
server. See | ||
[OpenLDAP](https://www.openldap.org/devel/admin/monitoringslapd.html) or 389ds | ||
documentation for details. | ||
|
||
## Metrics | ||
|
||
Depending on the server dialect, different metrics are produced. The metrics | ||
are usually named according to the selected dialect. | ||
|
||
### Tags | ||
|
||
- server -- Server name or IP | ||
- port -- Port used for connecting | ||
|
||
## Example Output | ||
|
||
Using the `openldap` dialect | ||
|
||
```text | ||
openldap,server=localhost,port=389 operations_bind_initiated=10i,operations_unbind_initiated=6i,operations_modrdn_completed=0i,operations_delete_initiated=0i,operations_add_completed=2i,operations_delete_completed=0i,operations_abandon_completed=0i,statistics_entries=1516i,threads_open=2i,threads_active=1i,waiters_read=1i,operations_modify_completed=0i,operations_extended_initiated=4i,threads_pending=0i,operations_search_initiated=36i,operations_compare_initiated=0i,connections_max_file_descriptors=4096i,operations_modify_initiated=0i,operations_modrdn_initiated=0i,threads_max=16i,time_uptime=6017i,connections_total=1037i,connections_current=1i,operations_add_initiated=2i,statistics_bytes=162071i,operations_unbind_completed=6i,operations_abandon_initiated=0i,statistics_pdu=1566i,threads_max_pending=0i,threads_backload=1i,waiters_write=0i,operations_bind_completed=10i,operations_search_completed=35i,operations_compare_completed=0i,operations_extended_completed=4i,statistics_referrals=0i,threads_starting=0i 1516912070000000000 | ||
``` | ||
|
||
Using the `389ds` dialect | ||
|
||
```text | ||
389ds,port=32805,server=localhost add_operations=0i,anonymous_binds=0i,backends=0i,bind_security_errors=0i,bytes_received=0i,bytes_sent=256i,cache_entries=0i,cache_hits=0i,chainings=0i,compare_operations=0i,connections=1i,connections_in_max_threads=0i,connections_max_threads=0i,copy_entries=0i,current_connections=1i,current_connections_at_max_threads=0i,delete_operations=0i,dtablesize=63936i,entries_returned=2i,entries_sent=2i,errors=2i,in_operations=11i,list_operations=0i,maxthreads_per_conn_hits=0i,modify_operations=1i,modrdn_operations=0i,onelevel_search_operations=0i,operations_completed=10i,operations_initiated=11i,read_operations=0i,read_waiters=0i,referrals=0i,referrals_returned=0i,search_operations=3i,security_errors=0i,simpleauth_binds=1i,strongauth_binds=2i,threads=17i,total_connections=4i,unauth_binds=0i,wholesubtree_search_operations=1i 1695637234047087280 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
//go:generate ../../../tools/readme_config_includer/generator | ||
package ldap | ||
|
||
import ( | ||
"crypto/tls" | ||
_ "embed" | ||
"fmt" | ||
"net/url" | ||
"time" | ||
|
||
"github.com/go-ldap/ldap/v3" | ||
|
||
"github.com/influxdata/telegraf" | ||
"github.com/influxdata/telegraf/config" | ||
commontls "github.com/influxdata/telegraf/plugins/common/tls" | ||
"github.com/influxdata/telegraf/plugins/inputs" | ||
) | ||
|
||
//go:embed sample.conf | ||
var sampleConfig string | ||
|
||
type LDAP struct { | ||
Server string `toml:"server"` | ||
Dialect string `toml:"dialect"` | ||
BindDn string `toml:"bind_dn"` | ||
BindPassword config.Secret `toml:"bind_password"` | ||
ReverseFieldNames bool `toml:"reverse_field_names"` | ||
commontls.ClientConfig | ||
|
||
tlsCfg *tls.Config | ||
requests []request | ||
mode string | ||
host string | ||
port string | ||
} | ||
|
||
type request struct { | ||
query *ldap.SearchRequest | ||
convert func(*ldap.SearchResult, time.Time) []telegraf.Metric | ||
} | ||
|
||
func (*LDAP) SampleConfig() string { | ||
return sampleConfig | ||
} | ||
|
||
func (l *LDAP) Init() error { | ||
if l.Server == "" { | ||
l.Server = "ldap://localhost:389" | ||
} | ||
|
||
u, err := url.Parse(l.Server) | ||
if err != nil { | ||
return fmt.Errorf("parsing server failed: %w", err) | ||
} | ||
|
||
// Verify the server setting and set the defaults | ||
var tlsEnable bool | ||
switch u.Scheme { | ||
case "ldap": | ||
if u.Port() == "" { | ||
u.Host = u.Host + ":389" | ||
} | ||
tlsEnable = false | ||
case "starttls": | ||
if u.Port() == "" { | ||
u.Host = u.Host + ":389" | ||
} | ||
tlsEnable = true | ||
case "ldaps": | ||
if u.Port() == "" { | ||
u.Host = u.Host + ":636" | ||
} | ||
tlsEnable = true | ||
default: | ||
return fmt.Errorf("invalid scheme: %q", u.Scheme) | ||
} | ||
l.mode = u.Scheme | ||
l.Server = u.Host | ||
l.host, l.port = u.Hostname(), u.Port() | ||
|
||
// Force TLS depending on the selected mode | ||
l.ClientConfig.Enable = &tlsEnable | ||
|
||
// Setup TLS configuration | ||
tlsCfg, err := l.ClientConfig.TLSConfig() | ||
if err != nil { | ||
return fmt.Errorf("creating TLS config failed: %w", err) | ||
} | ||
l.tlsCfg = tlsCfg | ||
|
||
// Initialize the search request(s) | ||
switch l.Dialect { | ||
case "", "openldap": | ||
l.requests = l.newOpenLDAPConfig() | ||
case "389ds": | ||
l.requests = l.new389dsConfig() | ||
default: | ||
return fmt.Errorf("invalid dialect %q", l.Dialect) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (l *LDAP) Gather(acc telegraf.Accumulator) error { | ||
// Connect | ||
conn, err := l.connect() | ||
if err != nil { | ||
return fmt.Errorf("connection failed: %w", err) | ||
} | ||
defer conn.Close() | ||
|
||
// Query the server | ||
for _, req := range l.requests { | ||
now := time.Now() | ||
result, err := conn.Search(req.query) | ||
if err != nil { | ||
acc.AddError(err) | ||
continue | ||
} | ||
|
||
// Collect metrics | ||
for _, m := range req.convert(result, now) { | ||
acc.AddMetric(m) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (l *LDAP) connect() (*ldap.Conn, error) { | ||
var conn *ldap.Conn | ||
switch l.mode { | ||
case "ldap": | ||
var err error | ||
conn, err = ldap.Dial("tcp", l.Server) | ||
if err != nil { | ||
return nil, err | ||
} | ||
case "ldaps": | ||
var err error | ||
conn, err = ldap.DialTLS("tcp", l.Server, l.tlsCfg) | ||
if err != nil { | ||
return nil, err | ||
} | ||
case "starttls": | ||
var err error | ||
conn, err = ldap.Dial("tcp", l.Server) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if err := conn.StartTLS(l.tlsCfg); err != nil { | ||
return nil, err | ||
} | ||
default: | ||
return nil, fmt.Errorf("invalid tls_mode: %s", l.mode) | ||
} | ||
|
||
if l.BindDn == "" && l.BindPassword.Empty() { | ||
return conn, nil | ||
} | ||
|
||
// Bind username and password | ||
passwd, err := l.BindPassword.Get() | ||
if err != nil { | ||
return nil, fmt.Errorf("getting password failed: %w", err) | ||
} | ||
defer passwd.Destroy() | ||
|
||
if err := conn.Bind(l.BindDn, passwd.String()); err != nil { | ||
return nil, fmt.Errorf("binding credentials failed: %w", err) | ||
} | ||
|
||
return conn, nil | ||
} | ||
|
||
func init() { | ||
inputs.Add("ldap", func() telegraf.Input { return &LDAP{} }) | ||
} |
Oops, something went wrong.