github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-openldap/lib/openldap.go (about) 1 package mpopenldap 2 3 import ( 4 "crypto/tls" 5 "errors" 6 "flag" 7 "fmt" 8 "net/url" 9 "os" 10 "regexp" 11 "sort" 12 "strconv" 13 "strings" 14 "time" 15 16 ldap "github.com/go-ldap/ldap/v3" 17 mp "github.com/mackerelio/go-mackerel-plugin-helper" 18 "github.com/mackerelio/golib/logging" 19 "golang.org/x/text/cases" 20 "golang.org/x/text/language" 21 ) 22 23 var ( 24 logger = logging.GetLogger("metrics.plugin.openldap") 25 cnRegexp = regexp.MustCompile("^cn=([^,]+),") 26 ) 27 28 // OpenLDAPPlugin plugin for OpenLDAP 29 type OpenLDAPPlugin struct { 30 Prefix string 31 UseTLS bool 32 InsecureSkipVerify bool 33 TargetHost string 34 BindDn string 35 ReplBase string 36 ReplMasterHost string 37 ReplMasterUseTLS bool 38 ReplMasterBind string 39 ReplMasterPass string 40 ReplLocalBind string 41 ReplLocalPass string 42 BindPasswd string 43 Tempfile string 44 l *ldap.Conn 45 } 46 47 func transformKeyName(key string) string { 48 results := cnRegexp.FindStringSubmatch(key) 49 if len(results) < 2 { 50 return "" 51 } 52 return strings.ReplaceAll(strings.ToLower(strings.TrimSpace(results[1])), " ", "_") 53 } 54 55 func getStats(sr *ldap.SearchResult, prefix string) map[string]float64 { 56 stat := make(map[string]float64) 57 for _, entry := range sr.Entries { 58 for _, attr := range entry.Attributes { 59 key := prefix + transformKeyName(entry.DN) + "_" + attr.Name 60 value, err := strconv.ParseFloat(entry.GetAttributeValue(attr.Name), 64) 61 if err != nil { 62 logger.Warningf("Failed to parse value. %s", err) 63 } 64 stat[key] = value 65 } 66 } 67 return stat 68 } 69 70 func fetchOpenldapMetrics(l *ldap.Conn, base, prefix string, attrs []string) (map[string]float64, error) { 71 searchRequest := ldap.NewSearchRequest(base, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=*))", attrs, nil) 72 sr, err := l.Search(searchRequest) 73 if err != nil { 74 logger.Errorf("Failed to ldap search. %s.", err) 75 return nil, err 76 } 77 stat := getStats(sr, prefix) 78 return stat, nil 79 } 80 81 func mergeStat(dst, src map[string]float64) { 82 for k, v := range src { 83 dst[k] = v 84 } 85 } 86 87 func latestCSN(sr *ldap.SearchResult) (time.Time, error) { 88 var res time.Time 89 if len(sr.Entries) == 0 { 90 return res, errors.New("not found CSN") 91 } 92 entry := sr.Entries[0] 93 if len(entry.Attributes) == 0 { 94 return res, errors.New("not found CSN") 95 } 96 attr := entry.Attributes[0] 97 vs := entry.GetAttributeValues(attr.Name) 98 csns := make([]time.Time, len(vs)) 99 for i, v := range vs { 100 t, err := time.Parse("20060102150405.999999Z", v[0:strings.Index(v, "#")]) 101 if err != nil { 102 return res, err 103 } 104 csns[i] = t 105 } 106 sort.Slice(csns, func(i, j int) bool { 107 return csns[i].After(csns[j]) 108 }) 109 res = csns[0] 110 return res, nil 111 } 112 113 func getLatestCSN(host, base, bind, passwd string, useTLS, insecureSkipVerify bool) (time.Time, error) { 114 var l *ldap.Conn 115 var err error 116 var res time.Time 117 if useTLS { 118 u := url.URL{Scheme: "ldaps", Host: host} 119 l, err = ldap.DialURL(u.String(), ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: insecureSkipVerify})) 120 } else { 121 u := url.URL{Scheme: "ldap", Host: host} 122 l, err = ldap.DialURL(u.String()) 123 } 124 if err != nil { 125 return res, err 126 } 127 err = l.Bind(bind, passwd) 128 if err != nil { 129 logger.Errorf("Failed to Bind %s, err: %s", bind, err) 130 return res, err 131 } 132 searchRequest := ldap.NewSearchRequest(base, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, "(&(objectClass=*))", []string{"ContextCSN"}, nil) 133 sr, err := l.Search(searchRequest) 134 l.Close() 135 if err != nil { 136 logger.Errorf("Failed to ldap search. %s.", err) 137 return res, err 138 } 139 return latestCSN(sr) 140 141 } 142 143 // FetchMetrics interface for mackerelplugin 144 func (m OpenLDAPPlugin) FetchMetrics() (map[string]interface{}, error) { 145 stat := make(map[string]float64) 146 if m.ReplBase != "" { 147 masterTime, err := getLatestCSN(m.ReplMasterHost, m.ReplBase, m.ReplMasterBind, m.ReplMasterPass, m.ReplMasterUseTLS, m.InsecureSkipVerify) 148 if err != nil { 149 return nil, err 150 } 151 localTime, err := getLatestCSN(m.TargetHost, m.ReplBase, m.ReplLocalBind, m.ReplLocalPass, m.UseTLS, m.InsecureSkipVerify) 152 if err != nil { 153 return nil, err 154 } 155 stat["replication_delay"] = masterTime.Sub(localTime).Seconds() 156 } 157 158 ldapOpes, err := fetchOpenldapMetrics(m.l, "cn=Operations,cn=Monitor", "", []string{"monitorOpInitiated", "monitorOpCompleted"}) 159 if err != nil { 160 return nil, err 161 } 162 mergeStat(stat, ldapOpes) 163 ldapWaiters, err := fetchOpenldapMetrics(m.l, "cn=Waiters,cn=Monitor", "waiters_", []string{"monitorCounter"}) 164 if err != nil { 165 return nil, err 166 } 167 mergeStat(stat, ldapWaiters) 168 169 ldapMaxThreads, err := fetchOpenldapMetrics(m.l, "cn=Max,cn=Threads,cn=Monitor", "threads_", []string{"monitoredInfo"}) 170 if err != nil { 171 return nil, err 172 } 173 mergeStat(stat, ldapMaxThreads) 174 ldapOpenThreads, err := fetchOpenldapMetrics(m.l, "cn=Open,cn=Threads,cn=Monitor", "threads_", []string{"monitoredInfo"}) 175 if err != nil { 176 return nil, err 177 } 178 mergeStat(stat, ldapOpenThreads) 179 ldapActiveThreads, err := fetchOpenldapMetrics(m.l, "cn=Active,cn=Threads,cn=Monitor", "threads_", []string{"monitoredInfo"}) 180 if err != nil { 181 return nil, err 182 } 183 mergeStat(stat, ldapActiveThreads) 184 185 ldapStatistics, err := fetchOpenldapMetrics(m.l, "cn=Statistics,cn=Monitor", "statistics_", []string{"monitorCounter"}) 186 if err != nil { 187 return nil, err 188 } 189 mergeStat(stat, ldapStatistics) 190 ldapTotalConns, err := fetchOpenldapMetrics(m.l, "cn=Total,cn=Connections,cn=Monitor", "connections_", []string{"monitorCounter"}) 191 if err != nil { 192 return nil, err 193 } 194 mergeStat(stat, ldapTotalConns) 195 ldapCurrentConns, err := fetchOpenldapMetrics(m.l, "cn=Current,cn=Connections,cn=Monitor", "connections_", []string{"monitorCounter"}) 196 if err != nil { 197 return nil, err 198 } 199 mergeStat(stat, ldapCurrentConns) 200 201 result := make(map[string]interface{}) 202 for k, v := range stat { 203 result[k] = v 204 } 205 return result, nil 206 } 207 208 // MetricKeyPrefix interface for PluginWithPrefix 209 func (m OpenLDAPPlugin) MetricKeyPrefix() string { 210 if m.Prefix == "" { 211 m.Prefix = "openldap" 212 } 213 return m.Prefix 214 } 215 216 // GraphDefinition interface for mackerelplugin 217 func (m OpenLDAPPlugin) GraphDefinition() map[string]mp.Graphs { 218 labelPrefix := cases.Title(language.Und, cases.NoLower).String(m.Prefix) 219 graphs := map[string]mp.Graphs{ 220 "operations_initiated": { 221 Label: (labelPrefix + " operations Initiated"), 222 Unit: "integer", 223 Metrics: []mp.Metrics{ 224 {Name: "operations_monitorOpInitiated", Label: "All", Diff: true}, 225 {Name: "bind_monitorOpInitiated", Label: "Bind", Diff: true}, 226 {Name: "unbind_monitorOpInitiated", Label: "Unbind", Diff: true}, 227 {Name: "search_monitorOpInitiated", Label: "Search", Diff: true}, 228 {Name: "compare_monitorOpInitiated", Label: "Compare", Diff: true}, 229 {Name: "modify_monitorOpInitiated", Label: "Modify", Diff: true}, 230 {Name: "modrdn_monitorOpInitiated", Label: "Modrdn", Diff: true}, 231 {Name: "add_monitorOpInitiated", Label: "Add", Diff: true}, 232 {Name: "delete_monitorOpInitiated", Label: "Delete", Diff: true}, 233 {Name: "abandon_monitorOpInitiated", Label: "Abandon", Diff: true}, 234 {Name: "extended_monitorOpInitiated", Label: "Extended", Diff: true}, 235 }, 236 }, 237 "operations_Completed": { 238 Label: (labelPrefix + " operations Completed"), 239 Unit: "integer", 240 Metrics: []mp.Metrics{ 241 {Name: "operations_monitorOpCompleted", Label: "All", Diff: true}, 242 {Name: "bind_monitorOpCompleted", Label: "Bind", Diff: true}, 243 {Name: "unbind_monitorOpCompleted", Label: "Unbind", Diff: true}, 244 {Name: "search_monitorOpCompleted", Label: "Search", Diff: true}, 245 {Name: "compare_monitorOpCompleted", Label: "Compare", Diff: true}, 246 {Name: "modify_monitorOpCompleted", Label: "Modify", Diff: true}, 247 {Name: "modrdn_monitorOpCompleted", Label: "Modrdn", Diff: true}, 248 {Name: "add_monitorOpCompleted", Label: "Add", Diff: true}, 249 {Name: "delete_monitorOpCompleted", Label: "Delete", Diff: true}, 250 {Name: "abandon_monitorOpCompleted", Label: "Abandon", Diff: true}, 251 {Name: "extended_monitorOpCompleted", Label: "Extended", Diff: true}, 252 }, 253 }, 254 "waiters": { 255 Label: (labelPrefix + " waiters"), 256 Unit: "integer", 257 Metrics: []mp.Metrics{ 258 {Name: "waiters_read_monitorCounter", Label: "read", Diff: false}, 259 {Name: "waiters_write_monitorCounter", Label: "write", Diff: false}, 260 }, 261 }, 262 "threads": { 263 Label: (labelPrefix + " threads"), 264 Unit: "integer", 265 Metrics: []mp.Metrics{ 266 {Name: "threads_max_monitoredInfo", Label: "max", Diff: false}, 267 {Name: "threads_open_monitoredInfo", Label: "open", Diff: false}, 268 {Name: "threads_active_monitoredInfo", Label: "active", Diff: false}, 269 }, 270 }, 271 "statistics_bytes": { 272 Label: (labelPrefix + " statistics bytes"), 273 Unit: "integer", 274 Metrics: []mp.Metrics{ 275 {Name: "statistics_bytes_monitorCounter", Label: "bytes", Diff: true}, 276 }, 277 }, 278 "statistics_pdu": { 279 Label: (labelPrefix + " statistics pdu"), 280 Unit: "integer", 281 Metrics: []mp.Metrics{ 282 {Name: "statistics_pdu_monitorCounter", Label: "pdu", Diff: true}, 283 }, 284 }, 285 "statistics_entries": { 286 Label: (labelPrefix + " statistics entries"), 287 Unit: "integer", 288 Metrics: []mp.Metrics{ 289 {Name: "statistics_entries_monitorCounter", Label: "entries", Diff: true}, 290 }, 291 }, 292 "statistics_referrals": { 293 Label: (labelPrefix + " statistics referrals"), 294 Unit: "integer", 295 Metrics: []mp.Metrics{ 296 {Name: "statistics_referrals_monitorCounter", Label: "referrals", Diff: true}, 297 }, 298 }, 299 "connections": { 300 Label: (labelPrefix + " connections"), 301 Unit: "integer", 302 Metrics: []mp.Metrics{ 303 {Name: "connections_total_monitorCounter", Label: "total connection", Diff: true}, 304 {Name: "connections_current_monitorCounter", Label: "current connection", Diff: false}, 305 }, 306 }, 307 } 308 if m.ReplBase != "" { 309 graphs["replications"] = mp.Graphs{ 310 Label: (labelPrefix + " replication delay"), 311 Unit: "integer", 312 Metrics: []mp.Metrics{ 313 {Name: "replication_delay", Label: "replication delay sec", Diff: false}, 314 }, 315 } 316 } 317 return graphs 318 } 319 320 // Do the plugin 321 func Do() { 322 optHost := flag.String("host", "localhost", "Hostname") 323 optPort := flag.String("port", "389", "Port") 324 optTLS := flag.Bool("tls", false, "TLS(ldaps)") 325 optInsecureSkipVerify := flag.Bool("insecureSkipVerify", false, "TLS accepts any certificate.") 326 optReplBase := flag.String("replBase", "", "replication base dn") 327 optReplMasterBind := flag.String("replMasterBind", "", "replication master bind dn") 328 optReplMasterHost := flag.String("replMasterHost", "", "replication master hostname") 329 optReplMasterTLS := flag.Bool("replMasterTLS", false, "replication master TLS(ldaps)") 330 optReplMasterPort := flag.String("replMasterPort", "389", "replication master port") 331 optReplMasterPass := flag.String("replMasterPW", os.Getenv("OPENLDAP_REPL_MASTER_PASSWORD"), "replication master bind password") 332 optReplLocalBind := flag.String("replLocalBind", "", "replicationlocalmaster bind dn") 333 optReplLocalPass := flag.String("replLocalPW", os.Getenv("OPENLDAP_REPL_LOCAL_PASSWORD"), "replication local bind password") 334 optBindDn := flag.String("bind", "", "bind dn") 335 optBindPasswd := flag.String("pw", os.Getenv("OPENLDAP_PASSWORD"), "bind password") 336 optTempfile := flag.String("tempfile", "", "Temp file name") 337 optPrefix := flag.String("metric-key-prefix", "openldap", "Metric key prefix") 338 flag.Parse() 339 340 var m OpenLDAPPlugin 341 m.TargetHost = fmt.Sprintf("%s:%s", *optHost, *optPort) 342 m.ReplMasterHost = fmt.Sprintf("%s:%s", *optReplMasterHost, *optReplMasterPort) 343 m.UseTLS = *optTLS 344 m.InsecureSkipVerify = *optInsecureSkipVerify 345 m.ReplBase = *optReplBase 346 m.ReplMasterUseTLS = *optReplMasterTLS 347 m.ReplMasterBind = *optReplMasterBind 348 m.ReplMasterPass = *optReplMasterPass 349 m.ReplLocalBind = *optReplLocalBind 350 m.ReplLocalPass = *optReplLocalPass 351 if m.InsecureSkipVerify { 352 m.UseTLS = true 353 } 354 m.BindDn = *optBindDn 355 m.BindPasswd = *optBindPasswd 356 m.Prefix = *optPrefix 357 358 if *optBindDn == "" { 359 logger.Errorf("bind is required.") 360 flag.PrintDefaults() 361 os.Exit(1) 362 } 363 var err error 364 if m.UseTLS { 365 u := url.URL{Scheme: "ldaps", Host: m.TargetHost} 366 m.l, err = ldap.DialURL(u.String(), ldap.DialWithTLSConfig(&tls.Config{InsecureSkipVerify: m.InsecureSkipVerify})) 367 } else { 368 u := url.URL{Scheme: "ldap", Host: m.TargetHost} 369 m.l, err = ldap.DialURL(u.String()) 370 } 371 if err != nil { 372 logger.Errorf("Failed to Dial %s, err: %s", m.TargetHost, err) 373 os.Exit(1) 374 } 375 err = m.l.Bind(m.BindDn, m.BindPasswd) 376 if err != nil { 377 logger.Errorf("Failed to Bind %s, err: %s", m.BindDn, err) 378 os.Exit(1) 379 } 380 helper := mp.NewMackerelPlugin(m) 381 if *optTempfile != "" { 382 helper.Tempfile = *optTempfile 383 } else { 384 helper.SetTempfileByBasename(fmt.Sprintf("mackerel-plugin-openldap-%s", *optHost)) 385 } 386 387 helper.Run() 388 }