First commit
This commit is contained in:
		
						commit
						245788f3f8
					
				
							
								
								
									
										20
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/release.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					on:
 | 
				
			||||||
 | 
					  release:
 | 
				
			||||||
 | 
					    types: [created]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					permissions:
 | 
				
			||||||
 | 
					    contents: write
 | 
				
			||||||
 | 
					    packages: write
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  release-linux-amd64:
 | 
				
			||||||
 | 
					    name: release linux/amd64
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					    - uses: actions/checkout@v3
 | 
				
			||||||
 | 
					    - uses: wangyoucao577/go-release-action@v1
 | 
				
			||||||
 | 
					      with:
 | 
				
			||||||
 | 
					        github_token: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
 | 
					        goos: linux
 | 
				
			||||||
 | 
					        goarch: amd64
 | 
				
			||||||
 | 
					        goversion: '1.21'
 | 
				
			||||||
							
								
								
									
										21
									
								
								.github/workflows/test_golang.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/test_golang.yaml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					name: Go package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					on: [push]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					jobs:
 | 
				
			||||||
 | 
					  build:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
 | 
					    steps:
 | 
				
			||||||
 | 
					      - uses: actions/checkout@v3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Set up Go
 | 
				
			||||||
 | 
					        uses: actions/setup-go@v4
 | 
				
			||||||
 | 
					        with:
 | 
				
			||||||
 | 
					          go-version: '1.21'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Build
 | 
				
			||||||
 | 
					        run: go build -v ./...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      - name: Test
 | 
				
			||||||
 | 
					        run: go test -v ./...
 | 
				
			||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					config.yaml
 | 
				
			||||||
 | 
					freeipa-health-metrics
 | 
				
			||||||
							
								
								
									
										7
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    // Use IntelliSense to learn about possible attributes.
 | 
				
			||||||
 | 
					    // Hover to view descriptions of existing attributes.
 | 
				
			||||||
 | 
					    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 | 
				
			||||||
 | 
					    "version": "0.2.0",
 | 
				
			||||||
 | 
					    "configurations": []
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								License.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								License.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					Copyright (c) 2023 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
							
								
								
									
										227
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								config.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,227 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"os/user"
 | 
				
			||||||
 | 
						"path"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kkyr/fig"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Main configuration structure.
 | 
				
			||||||
 | 
					type Config struct {
 | 
				
			||||||
 | 
						// Used as the FreeIPA server hostname in multiple checks.
 | 
				
			||||||
 | 
						// If no address configurations for metric exporters are defined,
 | 
				
			||||||
 | 
						// the hostname will be used to set defaults.
 | 
				
			||||||
 | 
						Hostname string `fig:"hostname"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Metric exporters configurations.
 | 
				
			||||||
 | 
						LDAP    LDAPConfig    `fig:"ldap"`
 | 
				
			||||||
 | 
						FreeIPA FreeIPAConfig `fig:"freeipa"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Metric outputs configurations.
 | 
				
			||||||
 | 
						HTTP   HTTPOutputConfig   `fig:"http_output"`
 | 
				
			||||||
 | 
						Influx InfluxOutputConfig `fig:"influx_output"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// File path configurations for binaries and config files.
 | 
				
			||||||
 | 
						Krb5SysConfigPath  string `fig:"krb5_sysconfig_path"`
 | 
				
			||||||
 | 
						Krb5KeytabPath     string `fig:"krb5_keytab_path"`
 | 
				
			||||||
 | 
						Krb5ConfigPath     string `fig:"krb5_config_path"`
 | 
				
			||||||
 | 
						PKITomcatServerXML string `fig:"pki_tomcat_server_xml"`
 | 
				
			||||||
 | 
						HTTPDPKIProxyConf  string `fig:"httpd_pki_proxy_conf"`
 | 
				
			||||||
 | 
						KInitBin           string `fig:"kinit_bin"`
 | 
				
			||||||
 | 
						KListBin           string `fig:"klist_bin"`
 | 
				
			||||||
 | 
						IPAGetCertBIN      string `fig:"ipa_getcert_bin"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						// Use standard LDAP connection.
 | 
				
			||||||
 | 
						LDAPMethodUnsecure = "Unsecure"
 | 
				
			||||||
 | 
						// Use LDAP over TLS.
 | 
				
			||||||
 | 
						LDAPMethodSecure = "Secure"
 | 
				
			||||||
 | 
						// Use StartTLS over standard LDAP connection.
 | 
				
			||||||
 | 
						LDAPMethodStartTLS = "StartTLS"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Configurations relating to LDAP.
 | 
				
			||||||
 | 
					type LDAPConfig struct {
 | 
				
			||||||
 | 
						Address            string `fig:"address"`
 | 
				
			||||||
 | 
						CACertificate      string `fig:"ca_certificate"`
 | 
				
			||||||
 | 
						InsecureSkipVerify bool   `fig:"insecure_skip_verify"`
 | 
				
			||||||
 | 
						ConnectMethod      string `fig:"connect_method"`
 | 
				
			||||||
 | 
						BaseDN             string `fig:"base_dn"`
 | 
				
			||||||
 | 
						BindDN             string `fig:"bind_dn"`
 | 
				
			||||||
 | 
						BindPassword       string `fig:"bind_password"`
 | 
				
			||||||
 | 
						SearchSizeLimit    int    `fig:"search_size_limit"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DisabledMetrics []string `fig:"disabled_metrics"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UNIX system group members for the FreeIPA configuration check of file system state.
 | 
				
			||||||
 | 
					type GroupMembers struct {
 | 
				
			||||||
 | 
						Name    string   `fig:"name"`
 | 
				
			||||||
 | 
						Members []string `fig:"members"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Configurations relating to FreeIPA API and configuration testing.
 | 
				
			||||||
 | 
					type FreeIPAConfig struct {
 | 
				
			||||||
 | 
						// Kerberos config can be used for both API authentication and for kinit test.
 | 
				
			||||||
 | 
						// To use for API authentication, simply do not supply an username/password.
 | 
				
			||||||
 | 
						// It is recommended to have it configured for the kinit test to function.
 | 
				
			||||||
 | 
						Krb5Realm     string `fig:"krb5_realm"`
 | 
				
			||||||
 | 
						Krb5Principal string `fig:"krb5_principal"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Host               string `fig:"host"`
 | 
				
			||||||
 | 
						CACertificate      string `fig:"ca_certificate"`
 | 
				
			||||||
 | 
						InsecureSkipVerify bool   `fig:"insecure_skip_verify"`
 | 
				
			||||||
 | 
						Username           string `fig:"username"`
 | 
				
			||||||
 | 
						Password           string `fig:"password"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GroupMembers []GroupMembers `fig:"group_mebers"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DisabledMetrics []string `fig:"disabled_metrics"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Configurations relating to HTTP server.
 | 
				
			||||||
 | 
					type HTTPOutputConfig struct {
 | 
				
			||||||
 | 
						Enabled     bool   `fig:"enabled"`
 | 
				
			||||||
 | 
						BindAddr    string `fig:"bind_addr"`
 | 
				
			||||||
 | 
						Port        uint   `fig:"port"`
 | 
				
			||||||
 | 
						MetricsPath string `fig:"metrics_path"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// If you want to output to InfluxDB either via Kafka or to InfluxDB API directly,
 | 
				
			||||||
 | 
					// these configurations allow you to set output to occur at a specified frequency.
 | 
				
			||||||
 | 
					type InfluxOutputConfig struct {
 | 
				
			||||||
 | 
						Frequency time.Duration `fig:"frequency"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						KafkaBrokers            []string `fig:"kafka_brokers"`
 | 
				
			||||||
 | 
						KafkaTopic              string   `fig:"kafka_topic"`
 | 
				
			||||||
 | 
						KafkaUsername           string   `fig:"kafka_usernamne"`
 | 
				
			||||||
 | 
						KafkaPassword           string   `fig:"kafka_password"`
 | 
				
			||||||
 | 
						KafkaInsecureSkipVerify bool     `fig:"kafka_insecure_skip_verify"`
 | 
				
			||||||
 | 
						KafkaOutputFormat       string   `fig:"kafka_output_format"` // Either lineprotocol or json. Default: lineprotocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						InfluxServer string `fig:"influx_server"`
 | 
				
			||||||
 | 
						Token        string `fig:"token"`
 | 
				
			||||||
 | 
						Org          string `fig:"org"`
 | 
				
			||||||
 | 
						Bucket       string `fig:"bucket"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read the configuration file.
 | 
				
			||||||
 | 
					func (a *App) ReadConfig() {
 | 
				
			||||||
 | 
						// Gets the current user for getting the home directory.
 | 
				
			||||||
 | 
						usr, err := user.Current()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Configuration paths.
 | 
				
			||||||
 | 
						localConfig, _ := filepath.Abs("./config.yaml")
 | 
				
			||||||
 | 
						homeDirConfig := usr.HomeDir + "/.config/freeipa-health-metrics/config.yaml"
 | 
				
			||||||
 | 
						etcConfig := "/etc/ipa/freeipa-health-metrics.yaml"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Determine which configuration to use.
 | 
				
			||||||
 | 
						var configFile string
 | 
				
			||||||
 | 
						if _, err := os.Stat(app.flags.ConfigPath); err == nil && app.flags.ConfigPath != "" {
 | 
				
			||||||
 | 
							configFile = app.flags.ConfigPath
 | 
				
			||||||
 | 
						} else if _, err := os.Stat(localConfig); err == nil {
 | 
				
			||||||
 | 
							configFile = localConfig
 | 
				
			||||||
 | 
						} else if _, err := os.Stat(homeDirConfig); err == nil {
 | 
				
			||||||
 | 
							configFile = homeDirConfig
 | 
				
			||||||
 | 
						} else if _, err := os.Stat(etcConfig); err == nil {
 | 
				
			||||||
 | 
							configFile = etcConfig
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							log.Fatal("Unable to find a configuration file.")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set defaults.
 | 
				
			||||||
 | 
						config := &Config{
 | 
				
			||||||
 | 
							HTTP: HTTPOutputConfig{
 | 
				
			||||||
 | 
								Enabled:     true,
 | 
				
			||||||
 | 
								Port:        9101,
 | 
				
			||||||
 | 
								MetricsPath: "/metrics",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							LDAP: LDAPConfig{
 | 
				
			||||||
 | 
								SearchSizeLimit: 100,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							FreeIPA: FreeIPAConfig{
 | 
				
			||||||
 | 
								GroupMembers: []GroupMembers{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										Name:    "apache",
 | 
				
			||||||
 | 
										Members: []string{"ipaapi"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Krb5SysConfigPath:  "/etc/sysconfig/krb5kdc",
 | 
				
			||||||
 | 
							Krb5KeytabPath:     "/etc/krb5.keytab",
 | 
				
			||||||
 | 
							Krb5ConfigPath:     "/etc/krb5.conf",
 | 
				
			||||||
 | 
							PKITomcatServerXML: "/etc/pki/pki-tomcat/server.xml",
 | 
				
			||||||
 | 
							HTTPDPKIProxyConf:  "/etc/httpd/conf.d/ipa-pki-proxy.conf",
 | 
				
			||||||
 | 
							KInitBin:           "/usr/bin/kinit",
 | 
				
			||||||
 | 
							KListBin:           "/usr/bin/klist",
 | 
				
			||||||
 | 
							IPAGetCertBIN:      "/usr/bin/ipa-getcert",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load configuration.
 | 
				
			||||||
 | 
						filePath, fileName := path.Split(configFile)
 | 
				
			||||||
 | 
						err = fig.Load(config,
 | 
				
			||||||
 | 
							fig.File(fileName),
 | 
				
			||||||
 | 
							fig.Dirs(filePath),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("Error parsing configuration: %s\n", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If no hostname is defined in config, pull system hostname.
 | 
				
			||||||
 | 
						if config.Hostname == "" {
 | 
				
			||||||
 | 
							cmd := exec.Command("/bin/hostname", "-f")
 | 
				
			||||||
 | 
							var out bytes.Buffer
 | 
				
			||||||
 | 
							cmd.Stdout = &out
 | 
				
			||||||
 | 
							err = cmd.Run()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println("Error getting hostname:", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							config.Hostname = strings.TrimRight(out.String(), "\n")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Use configured hostname as defaults for host related configs.
 | 
				
			||||||
 | 
						if config.FreeIPA.Krb5Principal == "" {
 | 
				
			||||||
 | 
							config.FreeIPA.Krb5Principal = "host/" + config.Hostname
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if config.LDAP.Address == "" {
 | 
				
			||||||
 | 
							config.LDAP.Address = "ldaps://" + config.Hostname + ":636"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if config.FreeIPA.Host == "" {
 | 
				
			||||||
 | 
							config.FreeIPA.Host = config.Hostname
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Flag Overrides.
 | 
				
			||||||
 | 
						if app.flags.HTTPBind != "" {
 | 
				
			||||||
 | 
							config.HTTP.BindAddr = app.flags.HTTPBind
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if app.flags.HTTPPort != 0 {
 | 
				
			||||||
 | 
							config.HTTP.Port = app.flags.HTTPPort
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if app.flags.HTTPMetricsPath != "" {
 | 
				
			||||||
 | 
							config.HTTP.MetricsPath = app.flags.HTTPMetricsPath
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Verify at least one output is enabled.
 | 
				
			||||||
 | 
						if !config.HTTP.Enabled && (len(app.config.Influx.KafkaBrokers) == 0 || app.config.Influx.KafkaTopic == "") && (config.Influx.InfluxServer == "" && config.Influx.Token == "" && config.Influx.Org == "" && config.Influx.Bucket == "") {
 | 
				
			||||||
 | 
							log.Println("No output services are configured.")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set global config structure.
 | 
				
			||||||
 | 
						app.config = config
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										54
									
								
								flags.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								flags.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Flags supplied to cli.
 | 
				
			||||||
 | 
					type Flags struct {
 | 
				
			||||||
 | 
						ConfigPath      string
 | 
				
			||||||
 | 
						HTTPBind        string
 | 
				
			||||||
 | 
						HTTPPort        uint
 | 
				
			||||||
 | 
						HTTPMetricsPath string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TelegrafOutput bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parse the supplied flags.
 | 
				
			||||||
 | 
					func (a *App) ParseFlags() {
 | 
				
			||||||
 | 
						app.flags = new(Flags)
 | 
				
			||||||
 | 
						flag.Usage = func() {
 | 
				
			||||||
 | 
							fmt.Printf(serviceName + ": " + serviceDescription + ".\n\nUsage:\n")
 | 
				
			||||||
 | 
							flag.PrintDefaults()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If version is requested.
 | 
				
			||||||
 | 
						var printVersion bool
 | 
				
			||||||
 | 
						flag.BoolVar(&printVersion, "v", false, "Print version")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Override configuration path.
 | 
				
			||||||
 | 
						usage := "Load configuration from `FILE`"
 | 
				
			||||||
 | 
						flag.StringVar(&app.flags.ConfigPath, "config", "", usage)
 | 
				
			||||||
 | 
						flag.StringVar(&app.flags.ConfigPath, "c", "", usage+" (shorthand)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Config overrides for http output.
 | 
				
			||||||
 | 
						flag.StringVar(&app.flags.HTTPBind, "http-bind", "", "Bind address for http server")
 | 
				
			||||||
 | 
						flag.UintVar(&app.flags.HTTPPort, "http-port", 0, "Bind port for http server")
 | 
				
			||||||
 | 
						flag.StringVar(&app.flags.HTTPMetricsPath, "http-metrics-path", "", "Path for pulling prometheus metrics")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Rather or not we should output lineprotocol data and exit for telegraf.
 | 
				
			||||||
 | 
						usage = "Output for telegraf execution."
 | 
				
			||||||
 | 
						flag.BoolVar(&app.flags.TelegrafOutput, "telegraf", false, usage)
 | 
				
			||||||
 | 
						flag.BoolVar(&app.flags.TelegrafOutput, "t", false, usage+" (shorthand)")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse the flags.
 | 
				
			||||||
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Print version and exit if requested.
 | 
				
			||||||
 | 
						if printVersion {
 | 
				
			||||||
 | 
							fmt.Println(serviceName + ": " + serviceVersion)
 | 
				
			||||||
 | 
							os.Exit(0)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										379
									
								
								freeipa.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										379
									
								
								freeipa.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,379 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
 | 
						"crypto/x509"
 | 
				
			||||||
 | 
						"encoding/pem"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/grmrgecko/go-freeipa"
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The prometheus exporter for FreeIPA configurations.
 | 
				
			||||||
 | 
					type FreeIPAExporter struct {
 | 
				
			||||||
 | 
						config  *FreeIPAConfig
 | 
				
			||||||
 | 
						conn    *freeipa.Client
 | 
				
			||||||
 | 
						mutex   sync.RWMutex
 | 
				
			||||||
 | 
						metrics []metricInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Basic metrics.
 | 
				
			||||||
 | 
						up                          prometheus.Gauge
 | 
				
			||||||
 | 
						totalScrapes, totalFailures prometheus.Counter
 | 
				
			||||||
 | 
						failedTests                 prometheus.Gauge
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Caches so we do not have more load than neccessary.
 | 
				
			||||||
 | 
						apiConfigCache       *freeipa.Response
 | 
				
			||||||
 | 
						caCertificateCache   *x509.Certificate
 | 
				
			||||||
 | 
						ipaCertificateCache  []*x509.Certificate
 | 
				
			||||||
 | 
						ldapCertificateCache []*x509.Certificate
 | 
				
			||||||
 | 
						certMongerCertsCache []*CertMongerCerts
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Make the FreeIPA exporter.
 | 
				
			||||||
 | 
					func NewFreeIPAExporter() *FreeIPAExporter {
 | 
				
			||||||
 | 
						e := new(FreeIPAExporter)
 | 
				
			||||||
 | 
						e.Reload()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return e
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reload the configurations.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) Reload() {
 | 
				
			||||||
 | 
						e.config = &app.config.FreeIPA
 | 
				
			||||||
 | 
						e.metrics = nil
 | 
				
			||||||
 | 
						e.setupMetrics()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get the API configuration from FreeIPA.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) apiConfig() (*freeipa.Response, error) {
 | 
				
			||||||
 | 
						// If not cached, pull from the API.
 | 
				
			||||||
 | 
						if e.apiConfigCache == nil {
 | 
				
			||||||
 | 
							// Get the configuration from the API.
 | 
				
			||||||
 | 
							params := make(map[string]interface{})
 | 
				
			||||||
 | 
							req := freeipa.NewRequest(
 | 
				
			||||||
 | 
								"config_show",
 | 
				
			||||||
 | 
								[]interface{}{},
 | 
				
			||||||
 | 
								params,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							res, err := e.conn.Do(req)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							e.apiConfigCache = res
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Return the cache.
 | 
				
			||||||
 | 
						return e.apiConfigCache, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get the FreeIPA CA Certificate.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) caCert() (*x509.Certificate, error) {
 | 
				
			||||||
 | 
						// If not cached, pull it.
 | 
				
			||||||
 | 
						if e.caCertificateCache == nil {
 | 
				
			||||||
 | 
							// Find the CA certificate in the API.
 | 
				
			||||||
 | 
							params := make(map[string]interface{})
 | 
				
			||||||
 | 
							req := freeipa.NewRequest(
 | 
				
			||||||
 | 
								"ca_show",
 | 
				
			||||||
 | 
								[]interface{}{"ipa"},
 | 
				
			||||||
 | 
								params,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							res, err := e.conn.Do(req)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check if the certificate was returned.
 | 
				
			||||||
 | 
							certS, ok := res.GetString("certificate")
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("unable to get certificate")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Parse the x509 certificate.
 | 
				
			||||||
 | 
							caPEM := "-----BEGIN CERTIFICATE-----\n" + certS + "\n-----END CERTIFICATE-----"
 | 
				
			||||||
 | 
							block, _ := pem.Decode([]byte(caPEM))
 | 
				
			||||||
 | 
							cert, err := x509.ParseCertificate(block.Bytes)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If we found the certificate, cache it.
 | 
				
			||||||
 | 
							e.caCertificateCache = cert
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Return the cache.
 | 
				
			||||||
 | 
						return e.caCertificateCache, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get the active certificate on the FreeIPA API http server.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) ipaCerts() ([]*x509.Certificate, error) {
 | 
				
			||||||
 | 
						// If no cache, pull the certificates.
 | 
				
			||||||
 | 
						if len(e.ipaCertificateCache) == 0 {
 | 
				
			||||||
 | 
							// Determine the proper host name.
 | 
				
			||||||
 | 
							host := e.config.Host
 | 
				
			||||||
 | 
							if !strings.Contains(host, ":") {
 | 
				
			||||||
 | 
								host = host + ":443"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// In this case, we don't care about security as we're just grabbing certificates.
 | 
				
			||||||
 | 
							tlsConf := &tls.Config{
 | 
				
			||||||
 | 
								InsecureSkipVerify: true,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Dial the host.
 | 
				
			||||||
 | 
							conn, err := tls.Dial("tcp", host, tlsConf)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Close the connection when done.
 | 
				
			||||||
 | 
							defer conn.Close()
 | 
				
			||||||
 | 
							// Get the certificates and cache them.
 | 
				
			||||||
 | 
							certs := conn.ConnectionState().PeerCertificates
 | 
				
			||||||
 | 
							e.ipaCertificateCache = append(e.ipaCertificateCache, certs...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Return cached certificates.
 | 
				
			||||||
 | 
						return e.ipaCertificateCache, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get the active LDAP server certificates.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) ldapCerts() ([]*x509.Certificate, error) {
 | 
				
			||||||
 | 
						// If not cached, pull them.
 | 
				
			||||||
 | 
						if len(e.ldapCertificateCache) == 0 {
 | 
				
			||||||
 | 
							// Determine the host from the LDAP config.
 | 
				
			||||||
 | 
							addr, err := url.Parse(app.config.LDAP.Address)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							port := addr.Port()
 | 
				
			||||||
 | 
							host := addr.Hostname() + ":" + port
 | 
				
			||||||
 | 
							// If no port defined in the URL, or if the ldaps protocol isn't defined. Use the default port.
 | 
				
			||||||
 | 
							if addr.Scheme != "ldaps" || port == "" {
 | 
				
			||||||
 | 
								host = addr.Hostname() + ":636"
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// In this case, we don't care about security as we're just grabbing certificates.
 | 
				
			||||||
 | 
							tlsConf := &tls.Config{
 | 
				
			||||||
 | 
								InsecureSkipVerify: true,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Dial the host.
 | 
				
			||||||
 | 
							conn, err := tls.Dial("tcp", host, tlsConf)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Close the connection when done.
 | 
				
			||||||
 | 
							defer conn.Close()
 | 
				
			||||||
 | 
							// Get the certificates and cache them.
 | 
				
			||||||
 | 
							certs := conn.ConnectionState().PeerCertificates
 | 
				
			||||||
 | 
							e.ldapCertificateCache = append(e.ldapCertificateCache, certs...)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Return cached certificates.
 | 
				
			||||||
 | 
						return e.ldapCertificateCache, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Information on the certificates managed by cert monger.
 | 
				
			||||||
 | 
					type CertMongerCerts struct {
 | 
				
			||||||
 | 
						RequestID      string
 | 
				
			||||||
 | 
						Status         string
 | 
				
			||||||
 | 
						Stuck          bool
 | 
				
			||||||
 | 
						KeyPairStorage string
 | 
				
			||||||
 | 
						Issuer         string
 | 
				
			||||||
 | 
						Subject        string
 | 
				
			||||||
 | 
						Expires        time.Time
 | 
				
			||||||
 | 
						DNSNames       []string
 | 
				
			||||||
 | 
						Track          bool
 | 
				
			||||||
 | 
						AutoRenew      bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Pull certificates managed by cert monger.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) certMongerCerts() ([]*CertMongerCerts, error) {
 | 
				
			||||||
 | 
						// If not cached, pull them.
 | 
				
			||||||
 | 
						if e.certMongerCertsCache == nil {
 | 
				
			||||||
 | 
							var cert *CertMongerCerts
 | 
				
			||||||
 | 
							// Parsing regex for cert monger.
 | 
				
			||||||
 | 
							requestRX := regexp.MustCompile(`Request ID '([A-Za-z0-9]+)':`)
 | 
				
			||||||
 | 
							keyValueRX := regexp.MustCompile(`\s([A-Za-z][A-Za-z- ]+): (.*)$`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Setup the ipa-getcert list command.
 | 
				
			||||||
 | 
							cmd := exec.Command(app.config.IPAGetCertBIN, "list")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the pipes.
 | 
				
			||||||
 | 
							stdout, err := cmd.StdoutPipe()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							stderr, err := cmd.StderrPipe()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Start the command.
 | 
				
			||||||
 | 
							err = cmd.Start()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Setup wait group to avoid race condition.
 | 
				
			||||||
 | 
							var wg sync.WaitGroup
 | 
				
			||||||
 | 
							wg.Add(2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Scan the standard output for certificates.
 | 
				
			||||||
 | 
							stdoutScanner := bufio.NewScanner(stdout)
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								// Scan each line of the output.
 | 
				
			||||||
 | 
								for stdoutScanner.Scan() {
 | 
				
			||||||
 | 
									line := stdoutScanner.Text()
 | 
				
			||||||
 | 
									// If this is a request line, setup a new certificate.
 | 
				
			||||||
 | 
									if requestRX.MatchString(line) {
 | 
				
			||||||
 | 
										match := requestRX.FindStringSubmatch(line)
 | 
				
			||||||
 | 
										// If match doesn't return expected count, continue.
 | 
				
			||||||
 | 
										if len(match) != 2 {
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// If the certificate was previously parsed, add it to the cache.
 | 
				
			||||||
 | 
										if cert != nil {
 | 
				
			||||||
 | 
											e.certMongerCertsCache = append(e.certMongerCertsCache, cert)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// Start a new certificate.
 | 
				
			||||||
 | 
										cert = &CertMongerCerts{
 | 
				
			||||||
 | 
											RequestID: match[1],
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else if keyValueRX.MatchString(line) {
 | 
				
			||||||
 | 
										// Parse key value entry.
 | 
				
			||||||
 | 
										match := keyValueRX.FindStringSubmatch(line)
 | 
				
			||||||
 | 
										// If match doesn't return expected count, continue.
 | 
				
			||||||
 | 
										if len(match) != 3 {
 | 
				
			||||||
 | 
											continue
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										// Check if key is one we're parsing and store the parsed info.
 | 
				
			||||||
 | 
										switch match[1] {
 | 
				
			||||||
 | 
										case "status":
 | 
				
			||||||
 | 
											cert.Status = match[2]
 | 
				
			||||||
 | 
										case "stuck":
 | 
				
			||||||
 | 
											cert.Stuck = match[2] == "yes"
 | 
				
			||||||
 | 
										case "key pair storage":
 | 
				
			||||||
 | 
											cert.KeyPairStorage = match[2]
 | 
				
			||||||
 | 
										case "issuer":
 | 
				
			||||||
 | 
											cert.Issuer = match[2]
 | 
				
			||||||
 | 
										case "subject":
 | 
				
			||||||
 | 
											cert.Subject = match[2]
 | 
				
			||||||
 | 
										case "expires":
 | 
				
			||||||
 | 
											cert.Expires, _ = time.Parse("2006-01-02 15:04:05 MST", match[2])
 | 
				
			||||||
 | 
										case "dns":
 | 
				
			||||||
 | 
											cert.DNSNames = strings.Split(match[2], ",")
 | 
				
			||||||
 | 
										case "track":
 | 
				
			||||||
 | 
											cert.Track = match[2] == "yes"
 | 
				
			||||||
 | 
										case "auto-renew":
 | 
				
			||||||
 | 
											cert.AutoRenew = match[2] == "yes"
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// We're done parsing the standard output.
 | 
				
			||||||
 | 
								wg.Done()
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Scan the standard error output and pass to our standard error.
 | 
				
			||||||
 | 
							stderrScanner := bufio.NewScanner(stderr)
 | 
				
			||||||
 | 
							go func() {
 | 
				
			||||||
 | 
								for stderrScanner.Scan() {
 | 
				
			||||||
 | 
									line := stderrScanner.Text()
 | 
				
			||||||
 | 
									fmt.Fprintln(os.Stderr, line)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								wg.Done()
 | 
				
			||||||
 | 
							}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Wait for file reads to finish before waiting on command to finnish to avoid a race condition.
 | 
				
			||||||
 | 
							wg.Wait()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Wait for the command to and check if error returned.
 | 
				
			||||||
 | 
							err = cmd.Wait()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If a certificate was parsed, add it to the cache.
 | 
				
			||||||
 | 
							if cert != nil {
 | 
				
			||||||
 | 
								e.certMongerCertsCache = append(e.certMongerCertsCache, cert)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Return cached list.
 | 
				
			||||||
 | 
						return e.certMongerCertsCache, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Connect to the FreeIPA API.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) connect() error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup TLS configurations.
 | 
				
			||||||
 | 
						tlsConifg := tls.Config{InsecureSkipVerify: e.config.InsecureSkipVerify}
 | 
				
			||||||
 | 
						// Load CA certificates if configured.
 | 
				
			||||||
 | 
						if e.config.CACertificate != "" {
 | 
				
			||||||
 | 
							caCert, err := os.ReadFile(e.config.CACertificate)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println("Error reading CA certificate:", err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								caCertPool := x509.NewCertPool()
 | 
				
			||||||
 | 
								caCertPool.AppendCertsFromPEM(caCert)
 | 
				
			||||||
 | 
								tlsConifg.RootCAs = caCertPool
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update the transport config with TLS config.
 | 
				
			||||||
 | 
						transportConfig := &http.Transport{
 | 
				
			||||||
 | 
							TLSClientConfig: &tlsConifg,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If we're logging in with plain authentication, do so.
 | 
				
			||||||
 | 
						if e.config.Username != "" && e.config.Password != "" {
 | 
				
			||||||
 | 
							e.conn, err = freeipa.Connect(e.config.Host, transportConfig, e.config.Username, e.config.Password)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Plain authentication wasn't used, so now we try logging in with Kerberos authentication.
 | 
				
			||||||
 | 
						// Read the keytab for kerberos.
 | 
				
			||||||
 | 
						krb5KtFd, err := os.Open(app.config.Krb5KeytabPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Close keytab after we're done.
 | 
				
			||||||
 | 
						defer krb5KtFd.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Open the kerberos config file.
 | 
				
			||||||
 | 
						krb5Fd, err := os.Open(app.config.Krb5ConfigPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Close the config file afte we're done.
 | 
				
			||||||
 | 
						defer krb5Fd.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup the kerberos connection options.
 | 
				
			||||||
 | 
						krb5ConnectOption := &freeipa.KerberosConnectOptions{
 | 
				
			||||||
 | 
							Krb5ConfigReader: krb5Fd,
 | 
				
			||||||
 | 
							KeytabReader:     krb5KtFd,
 | 
				
			||||||
 | 
							User:             e.config.Krb5Principal,
 | 
				
			||||||
 | 
							Realm:            e.config.Krb5Realm,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Attempt to connect with kerberos.
 | 
				
			||||||
 | 
						e.conn, err = freeipa.ConnectWithKerberos(e.config.Host, transportConfig, krb5ConnectOption)
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Disconnect from the API.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) disconnect() {
 | 
				
			||||||
 | 
						if e.conn != nil {
 | 
				
			||||||
 | 
							e.conn = nil
 | 
				
			||||||
 | 
							// Clear caches.
 | 
				
			||||||
 | 
							e.apiConfigCache = nil
 | 
				
			||||||
 | 
							e.caCertificateCache = nil
 | 
				
			||||||
 | 
							e.ipaCertificateCache = nil
 | 
				
			||||||
 | 
							e.ldapCertificateCache = nil
 | 
				
			||||||
 | 
							e.certMongerCertsCache = nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										663
									
								
								freeipa_metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										663
									
								
								freeipa_metrics.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,663 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"math/rand"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/exec"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"runtime"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/antchfx/xmlquery"
 | 
				
			||||||
 | 
						"github.com/grmrgecko/go-freeipa"
 | 
				
			||||||
 | 
						UNIXAccounts "github.com/grmrgecko/go-unixaccounts"
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Creates a metric and appends it to the to the available metrics if enabled.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) NewMetric(metricName string, docString string, t prometheus.ValueType, value func() (float64, error)) {
 | 
				
			||||||
 | 
						// If metric is disabled, stop here.
 | 
				
			||||||
 | 
						for _, metric := range e.config.DisabledMetrics {
 | 
				
			||||||
 | 
							if metric == metricName {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Create info for this metric.
 | 
				
			||||||
 | 
						info := metricInfo{
 | 
				
			||||||
 | 
							Desc: prometheus.NewDesc(
 | 
				
			||||||
 | 
								prometheus.BuildFQName(namespace, "config", metricName),
 | 
				
			||||||
 | 
								docString,
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							Type:  t,
 | 
				
			||||||
 | 
							Value: value,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Add metric to list.
 | 
				
			||||||
 | 
						e.metrics = append(e.metrics, info)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sets up the exporter with all needed metrics for FreeIPA.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) setupMetrics() {
 | 
				
			||||||
 | 
						// Setup basic metrics.
 | 
				
			||||||
 | 
						e.up = prometheus.NewGauge(prometheus.GaugeOpts{
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Name:      "up",
 | 
				
			||||||
 | 
							Help:      "Was the last scrape of FreeIPA successful.",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						e.totalScrapes = prometheus.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Name:      "scrapes_total",
 | 
				
			||||||
 | 
							Help:      "Current total HAProxy scrapes.",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						e.totalFailures = prometheus.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Name:      "failures_total",
 | 
				
			||||||
 | 
							Help:      "Number of errors while scapping metrics.",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						e.failedTests = prometheus.NewGauge(prometheus.GaugeOpts{
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Name:      "freeipa_failed_tests",
 | 
				
			||||||
 | 
							Help:      "Number of failed tests in the most recent scrape.",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: Ensure that kerberos authentication works with kinit and klist.
 | 
				
			||||||
 | 
						e.NewMetric("krb5_auth", "Kerberos can authenticate.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Specific cache file for tokens to ensure we're not succeeding due to previous check.
 | 
				
			||||||
 | 
							krb5CacheFile := fmt.Sprintf("/tmp/krb5_cache_%d", rand.Int())
 | 
				
			||||||
 | 
							// Remove file when test is done.
 | 
				
			||||||
 | 
							defer os.Remove(krb5CacheFile)
 | 
				
			||||||
 | 
							// Get pricipal with realm for authentication.
 | 
				
			||||||
 | 
							krb5Principal := e.config.Krb5Principal + "@" + e.config.Krb5Realm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Authenticate with kinit.
 | 
				
			||||||
 | 
							cmd := exec.Command(app.config.KInitBin, "-kt", app.config.Krb5KeytabPath, "-c", krb5CacheFile, krb5Principal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Run kinit, which will error if exit code is not 0 as expected.
 | 
				
			||||||
 | 
							_, err := cmd.Output()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Verify the cache file contains the token with klist.
 | 
				
			||||||
 | 
							cmd = exec.Command(app.config.KListBin, "-c", krb5CacheFile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Run klist, which will error if exit code is not 0 as expected.
 | 
				
			||||||
 | 
							_, err = cmd.Output()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Both tests suceeded, return 1.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: The number of workers configured for the directory server should equal number of cores.
 | 
				
			||||||
 | 
						e.NewMetric("krb5_workers", "Workers match processors.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Open the kerberos server configuration file.
 | 
				
			||||||
 | 
							f, err := os.Open(app.config.Krb5SysConfigPath)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Close file after test is done.
 | 
				
			||||||
 | 
							defer f.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Scan the file for each line.
 | 
				
			||||||
 | 
							scanner := bufio.NewScanner(f)
 | 
				
			||||||
 | 
							scanner.Split(bufio.ScanLines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Default to zero workers.
 | 
				
			||||||
 | 
							workers := 0
 | 
				
			||||||
 | 
							// The workers are defined as `-w WORKERS` in the cli arguments.
 | 
				
			||||||
 | 
							rxWorkers := regexp.MustCompile(`=.*-w\s*([0-9]+)`)
 | 
				
			||||||
 | 
							// Scan each line for the number of workers.
 | 
				
			||||||
 | 
							for scanner.Scan() {
 | 
				
			||||||
 | 
								line := scanner.Text()
 | 
				
			||||||
 | 
								// If line is the arguments config, parse it.
 | 
				
			||||||
 | 
								if strings.HasPrefix(line, "KRB5KDC_ARGS") {
 | 
				
			||||||
 | 
									// Parse line for number of workers.
 | 
				
			||||||
 | 
									match := rxWorkers.FindStringSubmatch(line)
 | 
				
			||||||
 | 
									if len(match) == 2 {
 | 
				
			||||||
 | 
										workers, _ = strconv.Atoi(match[1])
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check number of workers configured against number of CPU cores.
 | 
				
			||||||
 | 
							if workers != runtime.NumCPU() {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("number of workers does not match CPU cores")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If successful, return 1.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
							Test: The DNA range specify the starting user ID,
 | 
				
			||||||
 | 
							there needs to be at least one master with a range set.
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						e.NewMetric("dna_range", "DNA range is defined.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Pull from FreeIPA API all DNA ranges found.
 | 
				
			||||||
 | 
							params := make(map[string]interface{})
 | 
				
			||||||
 | 
							req := freeipa.NewRequest(
 | 
				
			||||||
 | 
								"idrange_find",
 | 
				
			||||||
 | 
								[]interface{}{""},
 | 
				
			||||||
 | 
								params,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							res, err := e.conn.Do(req)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If no DNA ranges found, fail.
 | 
				
			||||||
 | 
							if res.Result.Count == 0 {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("no DNA ID range found")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Success, return 1.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/*
 | 
				
			||||||
 | 
						   Test: This ensures that groups specified in the config have specific members.
 | 
				
			||||||
 | 
						   By default, we define this check to verify the ipaapi user is a member
 | 
				
			||||||
 | 
						   of the apache group. This is critical for security and access to cache.
 | 
				
			||||||
 | 
						*/
 | 
				
			||||||
 | 
						e.NewMetric("group_members", "Group members are as expected.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Get UNIX account list.
 | 
				
			||||||
 | 
							accounts, err := UNIXAccounts.NewUNIXAccounts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check each ground member configuration.
 | 
				
			||||||
 | 
							for _, check := range e.config.GroupMembers {
 | 
				
			||||||
 | 
								// Find the group that matches the name configured.
 | 
				
			||||||
 | 
								group := accounts.GroupWithName(check.Name)
 | 
				
			||||||
 | 
								// If group not found, fail.
 | 
				
			||||||
 | 
								if group == nil {
 | 
				
			||||||
 | 
									// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
									e.failedTests.Inc()
 | 
				
			||||||
 | 
									return 0, fmt.Errorf("unable to find group with name: %s", check.Name)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Get all users in this group.
 | 
				
			||||||
 | 
								users := accounts.UsersInGroup(group)
 | 
				
			||||||
 | 
								// Check each member configured to verify they are a group memebr.
 | 
				
			||||||
 | 
								for _, member := range check.Members {
 | 
				
			||||||
 | 
									// Check all users in this group to see if they are this member.
 | 
				
			||||||
 | 
									userIsMember := false
 | 
				
			||||||
 | 
									for _, user := range users {
 | 
				
			||||||
 | 
										if user.Name == member {
 | 
				
			||||||
 | 
											userIsMember = true
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									// If member isn't a user in the group, fail.
 | 
				
			||||||
 | 
									if !userIsMember {
 | 
				
			||||||
 | 
										// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
										e.failedTests.Inc()
 | 
				
			||||||
 | 
										return 0, fmt.Errorf("user %s should be a member of %s", member, check.Name)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If we reached this point, no test failed.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test: Confirm the shared secret between tomcat and Apache match.
 | 
				
			||||||
 | 
						e.NewMetric("proxy_secret", "Proxy secret is configured.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Open the tomcat server configuration file.
 | 
				
			||||||
 | 
							xmlF, err := os.Open(app.config.PKITomcatServerXML)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Close config at end of test.
 | 
				
			||||||
 | 
							defer xmlF.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Query XML for the AJP connector configuration.
 | 
				
			||||||
 | 
							p, err := xmlquery.CreateStreamParser(xmlF, `//Connector[@protocol="AJP/1.3"]`)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Variable to store found secrets.
 | 
				
			||||||
 | 
							var foundSecrets []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Pull the first connector match if possible.
 | 
				
			||||||
 | 
							n, err := p.Read()
 | 
				
			||||||
 | 
							// If EOF reached, no connectors are defined.
 | 
				
			||||||
 | 
							if err == io.EOF {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("no AJP/1.3 connectors defined")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Other erros are general.
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the secret attribute from the connector. This only may be configured on older installs.
 | 
				
			||||||
 | 
							secret := n.SelectAttr("secret")
 | 
				
			||||||
 | 
							if secret != "" {
 | 
				
			||||||
 | 
								foundSecrets = append(foundSecrets, secret)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Get the required secret which is the newer attribute name.
 | 
				
			||||||
 | 
							secret = n.SelectAttr("requiredSecret")
 | 
				
			||||||
 | 
							if secret != "" {
 | 
				
			||||||
 | 
								foundSecrets = append(foundSecrets, secret)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If there are more than one secret, check if they both match.
 | 
				
			||||||
 | 
							if len(foundSecrets) > 1 {
 | 
				
			||||||
 | 
								if foundSecrets[0] != foundSecrets[1] {
 | 
				
			||||||
 | 
									// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
									e.failedTests.Inc()
 | 
				
			||||||
 | 
									return 0, fmt.Errorf("the AJP secrets do not match")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// If no secrets were found, fail.
 | 
				
			||||||
 | 
							if len(foundSecrets) == 0 {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("no AJP secrets found")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Open the Apache HTTPD proxy configuration file.
 | 
				
			||||||
 | 
							f, err := os.Open(app.config.HTTPDPKIProxyConf)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Close this config file at end of test.
 | 
				
			||||||
 | 
							defer f.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Create a new line scanner for the httpd configuration file.
 | 
				
			||||||
 | 
							scanner := bufio.NewScanner(f)
 | 
				
			||||||
 | 
							scanner.Split(bufio.ScanLines)
 | 
				
			||||||
 | 
							// Parse regex for expected configuration.
 | 
				
			||||||
 | 
							proxyRx := regexp.MustCompile(`\s+ProxyPassMatch ajp://localhost:8009 secret=(\w+)$`)
 | 
				
			||||||
 | 
							// List of found secrets.
 | 
				
			||||||
 | 
							var foundProxySecrets []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Scan each line of the config for secrets.
 | 
				
			||||||
 | 
							for scanner.Scan() {
 | 
				
			||||||
 | 
								line := scanner.Text()
 | 
				
			||||||
 | 
								// If line matches a secret, get the secret.
 | 
				
			||||||
 | 
								if proxyRx.MatchString(line) {
 | 
				
			||||||
 | 
									match := proxyRx.FindStringSubmatch(line)
 | 
				
			||||||
 | 
									if len(match) == 2 {
 | 
				
			||||||
 | 
										// Add secret to the list.
 | 
				
			||||||
 | 
										foundProxySecrets = append(foundProxySecrets, match[1])
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If no secrets found in HTTPD config, fail.
 | 
				
			||||||
 | 
							if len(foundProxySecrets) == 0 {
 | 
				
			||||||
 | 
								// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
								e.failedTests.Inc()
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("no AJP proxy secrets found")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check each found proxy secret against the tomcat secrets.
 | 
				
			||||||
 | 
							for _, secret := range foundProxySecrets {
 | 
				
			||||||
 | 
								foundMatch := false
 | 
				
			||||||
 | 
								for _, xmlSecret := range foundSecrets {
 | 
				
			||||||
 | 
									if secret == xmlSecret {
 | 
				
			||||||
 | 
										foundMatch = true
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// If no match found, fail.
 | 
				
			||||||
 | 
								if !foundMatch {
 | 
				
			||||||
 | 
									// As this is a test, add to the tests failed counter.
 | 
				
			||||||
 | 
									e.failedTests.Inc()
 | 
				
			||||||
 | 
									return 0, fmt.Errorf("the AJP secrets configured do not match between tomcat and apache")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// At this point, the test succeeded.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Is this server the renewal master?
 | 
				
			||||||
 | 
						e.NewMetric("renewal_master", "This server is the renewal master.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Get the FreeIPA config.
 | 
				
			||||||
 | 
							config, err := e.apiConfig()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the configured renewal master.
 | 
				
			||||||
 | 
							masterServer, _ := config.GetString("ca_renewal_master_server")
 | 
				
			||||||
 | 
							// This is a renewal master if the configured hostname matches.
 | 
				
			||||||
 | 
							if masterServer != app.config.Hostname {
 | 
				
			||||||
 | 
								return 0, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Did the IPA CA sign the FreeIPA API certificate?
 | 
				
			||||||
 | 
						e.NewMetric("ipa_ca_issued_cert", "The FreeIPA API was issued a certificate by the CA cert.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Get the CA certificate.
 | 
				
			||||||
 | 
							caCert, err := e.caCert()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the FreeIPA API certificate.
 | 
				
			||||||
 | 
							ipaCerts, err := e.ipaCerts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check each certificate returned by the API against the CA certificate.
 | 
				
			||||||
 | 
							countSuccess := 0
 | 
				
			||||||
 | 
							for _, cert := range ipaCerts {
 | 
				
			||||||
 | 
								// Ignore the CA certificates.
 | 
				
			||||||
 | 
								if cert.IsCA {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// If the signature is signed by the CA certificate, add to the successes.
 | 
				
			||||||
 | 
								err := cert.CheckSignatureFrom(caCert)
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									countSuccess++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// If no successful signature checks, there are no certificates signed by the CA certificate.
 | 
				
			||||||
 | 
							if countSuccess == 0 {
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("certificates are not issued by ca certificate")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// At this point, a certificate was found to be signed by the CA certificate.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Is the FreeIPA API certificate in certmonger for autorenew?
 | 
				
			||||||
 | 
						e.NewMetric("ipa_cert_auto_renew", "The FreeIPA API certificate is managed and set to auto renew.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Get certificates managed by cert monger.
 | 
				
			||||||
 | 
							certsFound, err := e.certMongerCerts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get info about the httpd certificate.
 | 
				
			||||||
 | 
							var httpSubject string
 | 
				
			||||||
 | 
							autoRenew := false
 | 
				
			||||||
 | 
							for _, cert := range certsFound {
 | 
				
			||||||
 | 
								// If certificate storage path is in the httpd path, this is the httpd certificate.
 | 
				
			||||||
 | 
								if strings.Contains(cert.KeyPairStorage, "httpd") {
 | 
				
			||||||
 | 
									httpSubject = cert.Subject
 | 
				
			||||||
 | 
									autoRenew = cert.Status == "MONITORING" && cert.AutoRenew && !cert.Stuck && cert.Track
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the FreeIPA API certificates.
 | 
				
			||||||
 | 
							ipaCerts, err := e.ipaCerts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check if the certificate currently returned by FreeIPA is the one found in cert monger.
 | 
				
			||||||
 | 
							foundCerts := 0
 | 
				
			||||||
 | 
							for _, cert := range ipaCerts {
 | 
				
			||||||
 | 
								// If is an CA certificate, skip.
 | 
				
			||||||
 | 
								if cert.IsCA {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								thisSubject := cert.Subject.String()
 | 
				
			||||||
 | 
								// OpenSSL seems to escape the comma.
 | 
				
			||||||
 | 
								thisSubject = strings.ReplaceAll(thisSubject, "\\,", ",")
 | 
				
			||||||
 | 
								// If subject matches, this is the certificate in certmonger..
 | 
				
			||||||
 | 
								if thisSubject == httpSubject {
 | 
				
			||||||
 | 
									foundCerts++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If no certificates found, return error.
 | 
				
			||||||
 | 
							if foundCerts == 0 {
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("unable to determine if http cert is auto renew")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If not auto renew, return 0.
 | 
				
			||||||
 | 
							if !autoRenew {
 | 
				
			||||||
 | 
								return 0, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// At this point, the certificate was found and has been determined to be autorenew.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: The unix timestamp of the earliest FreeIPA API certificate expiry date.
 | 
				
			||||||
 | 
						e.NewMetric("ipa_earliest_cert_expiry", "The earliest certificate expiry date for FreeIPA API.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Get FreeIPA API certificates.
 | 
				
			||||||
 | 
							ipaCerts, err := e.ipaCerts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Find the earliest expiry date.
 | 
				
			||||||
 | 
							earliest := time.Time{}
 | 
				
			||||||
 | 
							for _, cert := range ipaCerts {
 | 
				
			||||||
 | 
								// If this is before the previously found earliest, update.
 | 
				
			||||||
 | 
								if earliest.IsZero() || (cert.NotAfter.Before(earliest) && !cert.NotAfter.IsZero()) {
 | 
				
			||||||
 | 
									earliest = cert.NotAfter
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// If the earliest date found is zero, we did not find any expiry dates.
 | 
				
			||||||
 | 
							if earliest.IsZero() {
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("unable to find earliest cert")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Return the earliest date in unix time format.
 | 
				
			||||||
 | 
							return float64(earliest.Unix()), nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Did the IPA CA sign the LDAP certificate?
 | 
				
			||||||
 | 
						e.NewMetric("ipa_ca_issued_ldap_cert", "The LDAP cert was issued a certificate by the CA cert.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Get the CA certificate.
 | 
				
			||||||
 | 
							caCert, err := e.caCert()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the LDAP certificates.
 | 
				
			||||||
 | 
							ldapCerts, err := e.ldapCerts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check each certificate.
 | 
				
			||||||
 | 
							countSuccess := 0
 | 
				
			||||||
 | 
							for _, cert := range ldapCerts {
 | 
				
			||||||
 | 
								// If this is a CA certificate, ignore.
 | 
				
			||||||
 | 
								if cert.IsCA {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// If the signature was signed by the CA certificate, add to the count.
 | 
				
			||||||
 | 
								err := cert.CheckSignatureFrom(caCert)
 | 
				
			||||||
 | 
								if err == nil {
 | 
				
			||||||
 | 
									countSuccess++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// If no successful signature checks, there are no certificates signed by the CA certificate.
 | 
				
			||||||
 | 
							if countSuccess == 0 {
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("certificates are not issued by ca certificate")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// At this point, a certificate was found to be signed by the CA certificate.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Is the LDAP certificate in certmonger for autorenew?
 | 
				
			||||||
 | 
						e.NewMetric("ldap_cert_auto_renew", "The LDAP certificate is managed and set to auto renew.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Get the certificates managed by cert monger.
 | 
				
			||||||
 | 
							certsFound, err := e.certMongerCerts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Find info about the LDAP certificate in cert monger.
 | 
				
			||||||
 | 
							var httpSubject string
 | 
				
			||||||
 | 
							autoRenew := false
 | 
				
			||||||
 | 
							for _, cert := range certsFound {
 | 
				
			||||||
 | 
								// If the storage path is in the dirsrv folder, this is an LDAP certificate.
 | 
				
			||||||
 | 
								if strings.Contains(cert.KeyPairStorage, "dirsrv") {
 | 
				
			||||||
 | 
									httpSubject = cert.Subject
 | 
				
			||||||
 | 
									autoRenew = cert.Status == "MONITORING" && cert.AutoRenew && !cert.Stuck && cert.Track
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the LDAP certificates.
 | 
				
			||||||
 | 
							ldapCerts, err := e.ldapCerts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check each LDAP certificate to see if it matches the one in cert monger.
 | 
				
			||||||
 | 
							foundCerts := 0
 | 
				
			||||||
 | 
							for _, cert := range ldapCerts {
 | 
				
			||||||
 | 
								// If CA certificate, ignore.
 | 
				
			||||||
 | 
								if cert.IsCA {
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								thisSubject := cert.Subject.String()
 | 
				
			||||||
 | 
								// OpenSSL seems to escape the comma.
 | 
				
			||||||
 | 
								thisSubject = strings.ReplaceAll(thisSubject, "\\,", ",")
 | 
				
			||||||
 | 
								// If this certificate matches the cert monger certificate.
 | 
				
			||||||
 | 
								if thisSubject == httpSubject {
 | 
				
			||||||
 | 
									foundCerts++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If no certificates found, return error.
 | 
				
			||||||
 | 
							if foundCerts == 0 {
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("unable to determine if LDAP cert is auto renew")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If not auto renew, return 0.
 | 
				
			||||||
 | 
							if !autoRenew {
 | 
				
			||||||
 | 
								return 0, nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// At this point, the certificate was found and has been determined to be autorenew.
 | 
				
			||||||
 | 
							return 1, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: The unix timestamp of the earliest LDAP certificate expiry date.
 | 
				
			||||||
 | 
						e.NewMetric("ldap_earliest_cert_expiry", "The earliest certificate expiry date for LDAP.", prometheus.GaugeValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Get the LDAP certificates.
 | 
				
			||||||
 | 
							ldapCerts, err := e.ldapCerts()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Find the earliest expiry date.
 | 
				
			||||||
 | 
							earliest := time.Time{}
 | 
				
			||||||
 | 
							for _, cert := range ldapCerts {
 | 
				
			||||||
 | 
								// If this is before the previously found earliest, update.
 | 
				
			||||||
 | 
								if earliest.IsZero() || (cert.NotAfter.Before(earliest) && !cert.NotAfter.IsZero()) {
 | 
				
			||||||
 | 
									earliest = cert.NotAfter
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If the earliest date found is zero, we did not find any expiry dates.
 | 
				
			||||||
 | 
							if earliest.IsZero() {
 | 
				
			||||||
 | 
								return 0, fmt.Errorf("unable to find earliest cert")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Return the earliest date in unix time format.
 | 
				
			||||||
 | 
							return float64(earliest.Unix()), nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Provide Promethues all descriptions of metrics exported.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) Describe(ch chan<- *prometheus.Desc) {
 | 
				
			||||||
 | 
						for _, m := range e.metrics {
 | 
				
			||||||
 | 
							ch <- m.Desc
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ch <- e.up.Desc()
 | 
				
			||||||
 | 
						ch <- e.totalScrapes.Desc()
 | 
				
			||||||
 | 
						ch <- e.totalFailures.Desc()
 | 
				
			||||||
 | 
						ch <- e.failedTests.Desc()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Collects metrics exported and provide values to Prometheus.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) Collect(ch chan<- prometheus.Metric) {
 | 
				
			||||||
 | 
						// Protect metrics from concurrent collects.
 | 
				
			||||||
 | 
						e.mutex.Lock()
 | 
				
			||||||
 | 
						defer e.mutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Scrape FreeIPA metrics.
 | 
				
			||||||
 | 
						up := e.scrape(ch)
 | 
				
			||||||
 | 
						// Update the up status.
 | 
				
			||||||
 | 
						e.up.Set(up)
 | 
				
			||||||
 | 
						// If not up, count as a failed scrape.
 | 
				
			||||||
 | 
						if up == 0 {
 | 
				
			||||||
 | 
							e.totalFailures.Inc()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send basic metrics.
 | 
				
			||||||
 | 
						ch <- e.up
 | 
				
			||||||
 | 
						ch <- e.totalScrapes
 | 
				
			||||||
 | 
						ch <- e.totalFailures
 | 
				
			||||||
 | 
						ch <- e.failedTests
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test FreeIPA and pull metrics.
 | 
				
			||||||
 | 
					func (e *FreeIPAExporter) scrape(ch chan<- prometheus.Metric) float64 {
 | 
				
			||||||
 | 
						// Reset the number of failed tests.
 | 
				
			||||||
 | 
						e.failedTests.Set(0)
 | 
				
			||||||
 | 
						// Increment the total number of scrapes.
 | 
				
			||||||
 | 
						e.totalScrapes.Inc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Attempt to connect to the FreeIPA API.
 | 
				
			||||||
 | 
						err := e.connect()
 | 
				
			||||||
 | 
						// If failure connecting, FreeIPA API is not up.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Error connecting to FreeIPA API:", err)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Disconnect after we're done scrapping information.
 | 
				
			||||||
 | 
						defer e.disconnect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update data for each metric.
 | 
				
			||||||
 | 
						for _, m := range e.metrics {
 | 
				
			||||||
 | 
							// Get the value of the metric.
 | 
				
			||||||
 | 
							value, err := m.Value()
 | 
				
			||||||
 | 
							// If an error occurred getting the value, log it for debug.
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Printf("Error retrieving value for metric %s: %s\n", m.Desc.String(), err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Update the value.
 | 
				
			||||||
 | 
							ch <- prometheus.MustNewConstMetric(m.Desc, m.Type, value)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The FreeIPA API server is up.
 | 
				
			||||||
 | 
						return 1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										243
									
								
								freeipa_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										243
									
								
								freeipa_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,243 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/grmrgecko/go-freeipa"
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus/testutil"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Setup global app variable with test config for tests.
 | 
				
			||||||
 | 
					func setupFreeIPATestApp() {
 | 
				
			||||||
 | 
						app = new(App)
 | 
				
			||||||
 | 
						app.flags = new(Flags)
 | 
				
			||||||
 | 
						app.flags.ConfigPath = "test/test_config.yaml"
 | 
				
			||||||
 | 
						app.ReadConfig()
 | 
				
			||||||
 | 
						app.freeIPAExporter = NewFreeIPAExporter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set ldap address to https port for certificate tests.
 | 
				
			||||||
 | 
						app.config.LDAP.Address = fmt.Sprintf("ldaps://127.0.0.1:%d", httpsPort)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Unused port for testing.
 | 
				
			||||||
 | 
					const httpsPort = 8831
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type FreeIPATestServer struct {
 | 
				
			||||||
 | 
						server    *http.Server
 | 
				
			||||||
 | 
						mux       *http.ServeMux
 | 
				
			||||||
 | 
						responses map[string]string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewFreeIPATestServer() *FreeIPATestServer {
 | 
				
			||||||
 | 
						s := new(FreeIPATestServer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup handlers.
 | 
				
			||||||
 | 
						mux := http.NewServeMux()
 | 
				
			||||||
 | 
						mux.HandleFunc("/ipa/session/login_password", s.handleLogin)
 | 
				
			||||||
 | 
						mux.HandleFunc("/ipa/session/json", s.handleJSON)
 | 
				
			||||||
 | 
						s.mux = mux
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup server config.
 | 
				
			||||||
 | 
						srvAddr := fmt.Sprintf("127.0.0.1:%d", httpsPort)
 | 
				
			||||||
 | 
						s.server = &http.Server{
 | 
				
			||||||
 | 
							Addr:    srvAddr,
 | 
				
			||||||
 | 
							Handler: mux,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Method to response file map.
 | 
				
			||||||
 | 
						s.responses = map[string]string{
 | 
				
			||||||
 | 
							"ca_is_enabled": "test/freeipa_ca_is_enabled.json",
 | 
				
			||||||
 | 
							"idrange_find":  "test/freeipa_idrange_find.json",
 | 
				
			||||||
 | 
							"config_show":   "test/freeipa_config_show.json",
 | 
				
			||||||
 | 
							"ca_show":       "test/freeipa_ca_show.json",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test login handler.
 | 
				
			||||||
 | 
					func (s *FreeIPATestServer) handleLogin(w http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
						// Logins are form data posts.
 | 
				
			||||||
 | 
						req.ParseForm()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check username/password equals test credentials.
 | 
				
			||||||
 | 
						user := req.Form.Get("user")
 | 
				
			||||||
 | 
						password := req.Form.Get("password")
 | 
				
			||||||
 | 
						if user == app.config.FreeIPA.Username && password == app.config.FreeIPA.Password {
 | 
				
			||||||
 | 
							// Successful login send session cookie.
 | 
				
			||||||
 | 
							cookie := http.Cookie{}
 | 
				
			||||||
 | 
							cookie.Name = "ipa_session"
 | 
				
			||||||
 | 
							cookie.Value = "correct-session-secret"
 | 
				
			||||||
 | 
							cookie.Expires = time.Now().Add(30 * time.Minute)
 | 
				
			||||||
 | 
							cookie.Secure = true
 | 
				
			||||||
 | 
							cookie.HttpOnly = true
 | 
				
			||||||
 | 
							cookie.Path = "/ipa"
 | 
				
			||||||
 | 
							http.SetCookie(w, &cookie)
 | 
				
			||||||
 | 
							w.Header().Set("IPASESSION", "correct-session-secret")
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Invalid login, send rejection.
 | 
				
			||||||
 | 
							w.Header().Set("X-IPA-Rejection-Reason", "invalid-password")
 | 
				
			||||||
 | 
							http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
 | 
				
			||||||
 | 
							fmt.Fprintf(w, `<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					<title>401 Unauthorized</title>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					<h1>Invalid Authentication</h1>
 | 
				
			||||||
 | 
					<p>
 | 
				
			||||||
 | 
					<strong>kinit: Password incorrect while getting initial credentials
 | 
				
			||||||
 | 
					</strong>
 | 
				
			||||||
 | 
					</p>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>`)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Send JSON file to HTTP request.
 | 
				
			||||||
 | 
					func (s *FreeIPATestServer) sendJSONFile(w http.ResponseWriter, filePath string) {
 | 
				
			||||||
 | 
						f, err := os.Open(filePath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer f.Close()
 | 
				
			||||||
 | 
						io.Copy(w, f)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// General invalid json error response for testing error handling.
 | 
				
			||||||
 | 
					func (s *FreeIPATestServer) sendInvalidJSON(w http.ResponseWriter) {
 | 
				
			||||||
 | 
						s.sendJSONFile(w, "test/freeipa_invalid_json.json")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Handle the json session test request.
 | 
				
			||||||
 | 
					func (s *FreeIPATestServer) handleJSON(w http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
						// If session cookie doesn't exist, something is wrong. Send unauthenticated response.
 | 
				
			||||||
 | 
						cookie, err := req.Cookie("ipa_session")
 | 
				
			||||||
 | 
						if err != nil || cookie.Value != "correct-session-secret" {
 | 
				
			||||||
 | 
							http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generally json response from here.
 | 
				
			||||||
 | 
						w.Header().Set("Content-Type", "application/json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get the request body and parse it out.
 | 
				
			||||||
 | 
						res := new(freeipa.Request)
 | 
				
			||||||
 | 
						err = json.NewDecoder(req.Body).Decode(res)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// If the json decode fails, send the error.
 | 
				
			||||||
 | 
							s.sendInvalidJSON(w)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// For testing, we'll consider user_add/user_find as an accepted method, all others will error.
 | 
				
			||||||
 | 
						resFile, ok := s.responses[res.Method]
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							s.sendJSONFile(w, resFile)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Debug output.
 | 
				
			||||||
 | 
							// jsonD, _ := json.Marshal(res)
 | 
				
			||||||
 | 
							// fmt.Println(string(jsonD))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// An unexpected method received for testing, send error message.
 | 
				
			||||||
 | 
							s.sendInvalidJSON(w)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Run the http server.
 | 
				
			||||||
 | 
					func (s *FreeIPATestServer) Run() {
 | 
				
			||||||
 | 
						isListening := make(chan bool)
 | 
				
			||||||
 | 
						// Start server.
 | 
				
			||||||
 | 
						go s.Start(isListening)
 | 
				
			||||||
 | 
						// Allow the http server to initialize.
 | 
				
			||||||
 | 
						<-isListening
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Stop the HTTP server.
 | 
				
			||||||
 | 
					func (s *FreeIPATestServer) Stop() {
 | 
				
			||||||
 | 
						s.server.Shutdown(context.Background())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start the HTTP server with a notification channel
 | 
				
			||||||
 | 
					// for when the server is listening.
 | 
				
			||||||
 | 
					func (s *FreeIPATestServer) Start(isListening chan bool) {
 | 
				
			||||||
 | 
						// Start server.
 | 
				
			||||||
 | 
						l, err := net.Listen("tcp", s.server.Addr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Listen: ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Now notify we are listening.
 | 
				
			||||||
 | 
						isListening <- true
 | 
				
			||||||
 | 
						// Serve http server on the listening port.
 | 
				
			||||||
 | 
						err = s.server.ServeTLS(l, "test/cert.pem", "test/key.pem")
 | 
				
			||||||
 | 
						if err != nil && err != http.ErrServerClosed {
 | 
				
			||||||
 | 
							log.Fatal("Serve: ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Main FreeIPA test function that verifies metrics for FreeIPA and its configurations works.
 | 
				
			||||||
 | 
					func TestFreeIPA(t *testing.T) {
 | 
				
			||||||
 | 
						// Setup configs.
 | 
				
			||||||
 | 
						setupFreeIPATestApp()
 | 
				
			||||||
 | 
						// Start http server.
 | 
				
			||||||
 | 
						server := NewFreeIPATestServer()
 | 
				
			||||||
 | 
						server.Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Open the expected prometheus metrics.
 | 
				
			||||||
 | 
						expected, err := os.Open("test/freeipa.metrics")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Error opening tests:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer expected.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test the LDAP exporter and verify metrics match what's expected.
 | 
				
			||||||
 | 
						err = testutil.CollectAndCompare(app.freeIPAExporter, expected)
 | 
				
			||||||
 | 
						// If results are not as expected, fail test with the error.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Unexpected metrics returned:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Remove all responses to test failures.
 | 
				
			||||||
 | 
						server.responses = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Open the expected prometheus metrics.
 | 
				
			||||||
 | 
						expected, err = os.Open("test/freeipa_fail.metrics")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Error opening tests:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer expected.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test the LDAP exporter and verify metrics match what's expected.
 | 
				
			||||||
 | 
						err = testutil.CollectAndCompare(app.freeIPAExporter, expected)
 | 
				
			||||||
 | 
						// If results are not as expected, fail test with the error.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Unexpected metrics returned:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set server to an bad address to test failure to connect.
 | 
				
			||||||
 | 
						app.config.FreeIPA.Host = "bad-address"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Open the expected prometheus metrics.
 | 
				
			||||||
 | 
						expected, err = os.Open("test/freeipa_fail_connect.metrics")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Error opening tests:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer expected.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test the LDAP exporter and verify metrics match what's expected.
 | 
				
			||||||
 | 
						err = testutil.CollectAndCompare(app.freeIPAExporter, expected)
 | 
				
			||||||
 | 
						// If results are not as expected, fail test with the error.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Unexpected metrics returned:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Stop as we're done.
 | 
				
			||||||
 | 
						server.Stop()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					module github.com/GRMrGecko/freeipa-health-metrics
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					go 1.20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/antchfx/xmlquery v1.3.17
 | 
				
			||||||
 | 
						github.com/go-ldap/ldap/v3 v3.4.5
 | 
				
			||||||
 | 
						github.com/gorilla/handlers v1.5.1
 | 
				
			||||||
 | 
						github.com/grmrgecko/go-freeipa v0.0.0-20230814003934-9662b716120c
 | 
				
			||||||
 | 
						github.com/grmrgecko/go-unixaccounts v0.0.0-20230814023229-86c46cf9fa3b
 | 
				
			||||||
 | 
						github.com/influxdata/influxdb-client-go/v2 v2.12.3
 | 
				
			||||||
 | 
						github.com/influxdata/line-protocol/v2 v2.2.1
 | 
				
			||||||
 | 
						github.com/jimlambrt/gldap v0.1.7
 | 
				
			||||||
 | 
						github.com/kkyr/fig v0.3.2
 | 
				
			||||||
 | 
						github.com/kylelemons/godebug v1.1.0
 | 
				
			||||||
 | 
						github.com/prometheus/client_golang v1.16.0
 | 
				
			||||||
 | 
						github.com/prometheus/client_model v0.4.0
 | 
				
			||||||
 | 
						github.com/segmentio/kafka-go v0.4.42
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require (
 | 
				
			||||||
 | 
						github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
 | 
				
			||||||
 | 
						github.com/antchfx/xpath v1.2.4 // indirect
 | 
				
			||||||
 | 
						github.com/beorn7/perks v1.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/cespare/xxhash/v2 v2.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
 | 
						github.com/deepmap/oapi-codegen v1.8.2 // indirect
 | 
				
			||||||
 | 
						github.com/fatih/color v1.14.1 // indirect
 | 
				
			||||||
 | 
						github.com/felixge/httpsnoop v1.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
 | 
				
			||||||
 | 
						github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
 | 
				
			||||||
 | 
						github.com/golang/protobuf v1.5.3 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-hclog v1.4.0 // indirect
 | 
				
			||||||
 | 
						github.com/hashicorp/go-uuid v1.0.3 // indirect
 | 
				
			||||||
 | 
						github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
 | 
				
			||||||
 | 
						github.com/jcmturner/aescts/v2 v2.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/jcmturner/gofork v1.7.6 // indirect
 | 
				
			||||||
 | 
						github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
 | 
				
			||||||
 | 
						github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
 | 
				
			||||||
 | 
						github.com/jcmturner/rpc/v2 v2.0.3 // indirect
 | 
				
			||||||
 | 
						github.com/klauspost/compress v1.15.9 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-colorable v0.1.13 // indirect
 | 
				
			||||||
 | 
						github.com/mattn/go-isatty v0.0.17 // indirect
 | 
				
			||||||
 | 
						github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 | 
				
			||||||
 | 
						github.com/mitchellh/mapstructure v1.4.1 // indirect
 | 
				
			||||||
 | 
						github.com/pelletier/go-toml v1.9.3 // indirect
 | 
				
			||||||
 | 
						github.com/pierrec/lz4/v4 v4.1.15 // indirect
 | 
				
			||||||
 | 
						github.com/pkg/errors v0.9.1 // indirect
 | 
				
			||||||
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/prometheus/common v0.42.0 // indirect
 | 
				
			||||||
 | 
						github.com/prometheus/procfs v0.10.1 // indirect
 | 
				
			||||||
 | 
						github.com/rogpeppe/go-internal v1.11.0 // indirect
 | 
				
			||||||
 | 
						github.com/stretchr/testify v1.8.1 // indirect
 | 
				
			||||||
 | 
						golang.org/x/crypto v0.7.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/net v0.8.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sys v0.8.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/text v0.8.0 // indirect
 | 
				
			||||||
 | 
						google.golang.org/protobuf v1.30.0 // indirect
 | 
				
			||||||
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										253
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										253
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,253 @@
 | 
				
			|||||||
 | 
					github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
 | 
				
			||||||
 | 
					github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
 | 
				
			||||||
 | 
					github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA=
 | 
				
			||||||
 | 
					github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
 | 
				
			||||||
 | 
					github.com/antchfx/xmlquery v1.3.17 h1:d0qWjPp/D+vtRw7ivCwT5ApH/3CkQU8JOeo3245PpTk=
 | 
				
			||||||
 | 
					github.com/antchfx/xmlquery v1.3.17/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA=
 | 
				
			||||||
 | 
					github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY=
 | 
				
			||||||
 | 
					github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
 | 
				
			||||||
 | 
					github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 | 
				
			||||||
 | 
					github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 | 
				
			||||||
 | 
					github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
 | 
				
			||||||
 | 
					github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 | 
				
			||||||
 | 
					github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 | 
				
			||||||
 | 
					github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
				
			||||||
 | 
					github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU=
 | 
				
			||||||
 | 
					github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw=
 | 
				
			||||||
 | 
					github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 | 
				
			||||||
 | 
					github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
 | 
				
			||||||
 | 
					github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
 | 
				
			||||||
 | 
					github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
 | 
				
			||||||
 | 
					github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
 | 
				
			||||||
 | 
					github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 | 
				
			||||||
 | 
					github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
 | 
				
			||||||
 | 
					github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
 | 
				
			||||||
 | 
					github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
 | 
				
			||||||
 | 
					github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
 | 
				
			||||||
 | 
					github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
 | 
				
			||||||
 | 
					github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 | 
				
			||||||
 | 
					github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
 | 
				
			||||||
 | 
					github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
 | 
				
			||||||
 | 
					github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
 | 
				
			||||||
 | 
					github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8=
 | 
				
			||||||
 | 
					github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 | 
				
			||||||
 | 
					github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
 | 
				
			||||||
 | 
					github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 | 
				
			||||||
 | 
					github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 | 
				
			||||||
 | 
					github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
				
			||||||
 | 
					github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 | 
				
			||||||
 | 
					github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 | 
				
			||||||
 | 
					github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 | 
				
			||||||
 | 
					github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 | 
				
			||||||
 | 
					github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
 | 
				
			||||||
 | 
					github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
 | 
				
			||||||
 | 
					github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
 | 
				
			||||||
 | 
					github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
 | 
				
			||||||
 | 
					github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
 | 
				
			||||||
 | 
					github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
 | 
				
			||||||
 | 
					github.com/grmrgecko/go-freeipa v0.0.0-20230814003934-9662b716120c h1:i35K9mKZNYjj8A0kA/tMj0hh+Ms1V9O6x8MsYSb1Dvs=
 | 
				
			||||||
 | 
					github.com/grmrgecko/go-freeipa v0.0.0-20230814003934-9662b716120c/go.mod h1:bg9+b0lCJ2+XwgNfDOCG4gjMwsHH/nwTTKaYF/T3o/Q=
 | 
				
			||||||
 | 
					github.com/grmrgecko/go-unixaccounts v0.0.0-20230814023229-86c46cf9fa3b h1:4twMmYqPkuqUobzM7GkHECiEu6SuH06xgKgHRiviZ2U=
 | 
				
			||||||
 | 
					github.com/grmrgecko/go-unixaccounts v0.0.0-20230814023229-86c46cf9fa3b/go.mod h1:ND6FYE0L6uFGxbi3A+pVpb9doB5mAUiFHjfYEDpHIHI=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
 | 
				
			||||||
 | 
					github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 | 
				
			||||||
 | 
					github.com/influxdata/influxdb-client-go/v2 v2.12.3 h1:28nRlNMRIV4QbtIUvxhWqaxn0IpXeMSkY/uJa/O/vC4=
 | 
				
			||||||
 | 
					github.com/influxdata/influxdb-client-go/v2 v2.12.3/go.mod h1:IrrLUbCjjfkmRuaCiGQg4m2GbkaeJDcuWoxiWdQEbA0=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937 h1:MHJNQ+p99hFATQm6ORoLmpUCF7ovjwEFshs/NHzAbig=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937/go.mod h1:BKR9c0uHSmRgM/se9JhFHtTT7JTO67X23MtKMHtZcpo=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod h1:6+9Xt5Sq1rWx+glMgxhcg2c0DUaehK+5TDcPZ76GypY=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE=
 | 
				
			||||||
 | 
					github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM=
 | 
				
			||||||
 | 
					github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
 | 
				
			||||||
 | 
					github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
 | 
				
			||||||
 | 
					github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
 | 
				
			||||||
 | 
					github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
 | 
				
			||||||
 | 
					github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
 | 
				
			||||||
 | 
					github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
 | 
				
			||||||
 | 
					github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
 | 
				
			||||||
 | 
					github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
 | 
				
			||||||
 | 
					github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
 | 
				
			||||||
 | 
					github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
 | 
				
			||||||
 | 
					github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
 | 
				
			||||||
 | 
					github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
 | 
				
			||||||
 | 
					github.com/jimlambrt/gldap v0.1.7 h1:q6W1xyjnHax/JAhjsN/EQ88+DCOEYPy/GDM7/3tk7bA=
 | 
				
			||||||
 | 
					github.com/jimlambrt/gldap v0.1.7/go.mod h1:BRdefIDhx2uYBjxL0fRBGi3eyOvAkkRIXSJYMCyzCaI=
 | 
				
			||||||
 | 
					github.com/kkyr/fig v0.3.2 h1:+vMj52FL6RJUxeKOBB6JXIMyyi1/2j1ERDrZXjoBjzM=
 | 
				
			||||||
 | 
					github.com/kkyr/fig v0.3.2/go.mod h1:ItUILF8IIzgZOMhx5xpJ1W/bviQsWRKOwKXfE/tqUoA=
 | 
				
			||||||
 | 
					github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
 | 
				
			||||||
 | 
					github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
				
			||||||
 | 
					github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
				
			||||||
 | 
					github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
				
			||||||
 | 
					github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
				
			||||||
 | 
					github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
				
			||||||
 | 
					github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 | 
				
			||||||
 | 
					github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 | 
				
			||||||
 | 
					github.com/labstack/echo/v4 v4.2.1/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
 | 
				
			||||||
 | 
					github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
				
			||||||
 | 
					github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
 | 
				
			||||||
 | 
					github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
 | 
				
			||||||
 | 
					github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 | 
				
			||||||
 | 
					github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
 | 
				
			||||||
 | 
					github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 | 
				
			||||||
 | 
					github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
 | 
				
			||||||
 | 
					github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 | 
				
			||||||
 | 
					github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 | 
				
			||||||
 | 
					github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
 | 
				
			||||||
 | 
					github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 | 
				
			||||||
 | 
					github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
 | 
				
			||||||
 | 
					github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 | 
				
			||||||
 | 
					github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
 | 
				
			||||||
 | 
					github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
 | 
				
			||||||
 | 
					github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
 | 
				
			||||||
 | 
					github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
 | 
				
			||||||
 | 
					github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
 | 
				
			||||||
 | 
					github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
 | 
				
			||||||
 | 
					github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
 | 
				
			||||||
 | 
					github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
 | 
				
			||||||
 | 
					github.com/segmentio/kafka-go v0.4.42 h1:qffhBZCz4WcWyNuHEclHjIMLs2slp6mZO8px+5W5tfU=
 | 
				
			||||||
 | 
					github.com/segmentio/kafka-go v0.4.42/go.mod h1:d0g15xPMqoUookug0OU75DhGZxXwCFxSLeJ4uphwJzg=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
				
			||||||
 | 
					github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
 | 
				
			||||||
 | 
					github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
 | 
				
			||||||
 | 
					github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 | 
				
			||||||
 | 
					github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 | 
				
			||||||
 | 
					github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
 | 
				
			||||||
 | 
					github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 | 
				
			||||||
 | 
					github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 | 
				
			||||||
 | 
					github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 | 
				
			||||||
 | 
					github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
 | 
				
			||||||
 | 
					github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
 | 
				
			||||||
 | 
					github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
 | 
				
			||||||
 | 
					github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 | 
				
			||||||
 | 
					golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
 | 
					golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
				
			||||||
 | 
					golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
				
			||||||
 | 
					golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
				
			||||||
 | 
					golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
 | 
				
			||||||
 | 
					golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
 | 
					golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
 | 
					golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
				
			||||||
 | 
					golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 | 
				
			||||||
 | 
					golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
				
			||||||
 | 
					golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
 | 
				
			||||||
 | 
					golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 | 
				
			||||||
 | 
					golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
				
			||||||
 | 
					golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
 | 
					gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
				
			||||||
							
								
								
									
										107
									
								
								http.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								http.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gorilla/handlers"
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus/promhttp"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The http server output.
 | 
				
			||||||
 | 
					type HTTPOutput struct {
 | 
				
			||||||
 | 
						server *http.Server
 | 
				
			||||||
 | 
						mux    *http.ServeMux
 | 
				
			||||||
 | 
						config *HTTPOutputConfig
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Make a new http output controller.
 | 
				
			||||||
 | 
					func NewHTTPOutput() *HTTPOutput {
 | 
				
			||||||
 | 
						// Create the server.
 | 
				
			||||||
 | 
						s := new(HTTPOutput)
 | 
				
			||||||
 | 
						s.server = &http.Server{}
 | 
				
			||||||
 | 
						// Add update configurations.
 | 
				
			||||||
 | 
						s.Reload()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Creates the handlers and configures the server.
 | 
				
			||||||
 | 
					func (s *HTTPOutput) AddHandlers() {
 | 
				
			||||||
 | 
						// Make a new handler to replace old.
 | 
				
			||||||
 | 
						mux := http.NewServeMux()
 | 
				
			||||||
 | 
						s.mux = mux
 | 
				
			||||||
 | 
						s.server.Handler = mux
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Register handlers.
 | 
				
			||||||
 | 
						mux.Handle(s.config.MetricsPath, handlers.CombinedLoggingHandler(os.Stdout, promhttp.HandlerFor(app.registry, promhttp.HandlerOpts{})))
 | 
				
			||||||
 | 
						mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
 | 
							w.Write([]byte(`<html>
 | 
				
			||||||
 | 
					             <head><title>Metrics Exporter</title></head>
 | 
				
			||||||
 | 
					             <body>
 | 
				
			||||||
 | 
					             <h1>Metrics Exporter</h1>
 | 
				
			||||||
 | 
					             <p><a href='` + s.config.MetricsPath + `'>Metrics</a></p>
 | 
				
			||||||
 | 
					             </body>
 | 
				
			||||||
 | 
					             </html>`))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reload configurations.
 | 
				
			||||||
 | 
					func (s *HTTPOutput) Reload() {
 | 
				
			||||||
 | 
						// Update config reference.
 | 
				
			||||||
 | 
						s.config = &app.config.HTTP
 | 
				
			||||||
 | 
						// Update the address.
 | 
				
			||||||
 | 
						s.server.Addr = fmt.Sprintf("%s:%d", s.config.BindAddr, s.config.Port)
 | 
				
			||||||
 | 
						// Update handlers incase the path was re-configured.
 | 
				
			||||||
 | 
						s.AddHandlers()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns rather or not output is enabled.
 | 
				
			||||||
 | 
					func (s *HTTPOutput) OutputEnabled() bool {
 | 
				
			||||||
 | 
						return s.config.Enabled
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start the HTTP output server.
 | 
				
			||||||
 | 
					func (s *HTTPOutput) Start(ctx context.Context) {
 | 
				
			||||||
 | 
						isListening := make(chan bool)
 | 
				
			||||||
 | 
						// Start server.
 | 
				
			||||||
 | 
						go s.StartWithIsListening(ctx, isListening)
 | 
				
			||||||
 | 
						// Allow the http server to initialize.
 | 
				
			||||||
 | 
						<-isListening
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Starts the HTTP output server with a listening channel.
 | 
				
			||||||
 | 
					func (s *HTTPOutput) StartWithIsListening(ctx context.Context, isListening chan bool) {
 | 
				
			||||||
 | 
						// If http is disabled, stop here.
 | 
				
			||||||
 | 
						if !s.config.Enabled {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Watch the background context for when we need to shutdown.
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							<-ctx.Done()
 | 
				
			||||||
 | 
							err := s.server.Shutdown(context.Background())
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// Error from closing listeners, or context timeout:
 | 
				
			||||||
 | 
								log.Println("Error shutting down http server:", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Start the server.
 | 
				
			||||||
 | 
						log.Println("Starting http server:", s.server.Addr)
 | 
				
			||||||
 | 
						l, err := net.Listen("tcp", s.server.Addr)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Listen: ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Now notify we are listening.
 | 
				
			||||||
 | 
						isListening <- true
 | 
				
			||||||
 | 
						// Serve http server on the listening port.
 | 
				
			||||||
 | 
						err = s.server.Serve(l)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("HTTP server failure:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										86
									
								
								http_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								http_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Setup global app variable with test config for tests.
 | 
				
			||||||
 | 
					func setupHTTPTestApp() {
 | 
				
			||||||
 | 
						app = new(App)
 | 
				
			||||||
 | 
						app.flags = new(Flags)
 | 
				
			||||||
 | 
						app.flags.ConfigPath = "test/test_config.yaml"
 | 
				
			||||||
 | 
						app.ReadConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load exporters.
 | 
				
			||||||
 | 
						app.ldapExporter = NewLDAPExporter()
 | 
				
			||||||
 | 
						app.freeIPAExporter = NewFreeIPAExporter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add exporters to registry.
 | 
				
			||||||
 | 
						reg := prometheus.NewPedanticRegistry()
 | 
				
			||||||
 | 
						reg.Register(app.ldapExporter)
 | 
				
			||||||
 | 
						reg.Register(app.freeIPAExporter)
 | 
				
			||||||
 | 
						app.registry = reg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup influx output.
 | 
				
			||||||
 | 
						app.httpOutput = NewHTTPOutput()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Main HTTP test function.
 | 
				
			||||||
 | 
					func TestHTTP(t *testing.T) {
 | 
				
			||||||
 | 
						// Setup configs.
 | 
				
			||||||
 | 
						setupHTTPTestApp()
 | 
				
			||||||
 | 
						// Run the LDAP test server.
 | 
				
			||||||
 | 
						server := NewLDAPTestServer()
 | 
				
			||||||
 | 
						server.Run()
 | 
				
			||||||
 | 
						// Start http server.
 | 
				
			||||||
 | 
						httpServer := NewFreeIPATestServer()
 | 
				
			||||||
 | 
						httpServer.Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup new background context.
 | 
				
			||||||
 | 
						ctx, ctxCancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Start http output server.
 | 
				
			||||||
 | 
						app.httpOutput.Start(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Make request for metrics.
 | 
				
			||||||
 | 
						httpConf := &app.config.HTTP
 | 
				
			||||||
 | 
						url := fmt.Sprintf("http://%s:%d%s", httpConf.BindAddr, httpConf.Port, httpConf.MetricsPath)
 | 
				
			||||||
 | 
						req, err := http.NewRequest("GET", url, nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Perform request.
 | 
				
			||||||
 | 
						res, err := http.DefaultClient.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Close body after we're done.
 | 
				
			||||||
 | 
						defer res.Body.Close()
 | 
				
			||||||
 | 
						// Read all data from the body.
 | 
				
			||||||
 | 
						body, err := io.ReadAll(res.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check difference.
 | 
				
			||||||
 | 
						difference, err := FileDiff(string(body), "test/http.metrics")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if difference != "" {
 | 
				
			||||||
 | 
							t.Fatalf("Difference from expected result:\n%s", difference)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We're done, let's stop serving the test LDAP server.
 | 
				
			||||||
 | 
						server.Stop()
 | 
				
			||||||
 | 
						httpServer.Stop()
 | 
				
			||||||
 | 
						ctxCancel()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										325
									
								
								influx.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								influx.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,325 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						influxdb2 "github.com/influxdata/influxdb-client-go/v2"
 | 
				
			||||||
 | 
						"github.com/influxdata/line-protocol/v2/lineprotocol"
 | 
				
			||||||
 | 
						io_prometheus_client "github.com/prometheus/client_model/go"
 | 
				
			||||||
 | 
						"github.com/segmentio/kafka-go"
 | 
				
			||||||
 | 
						"github.com/segmentio/kafka-go/sasl/plain"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The influx output controller, used to get InfluxDB lineprotocol and
 | 
				
			||||||
 | 
					// json output of metrics, and publish metrics on a schedule.
 | 
				
			||||||
 | 
					type InfluxOutput struct {
 | 
				
			||||||
 | 
						kwriter *kafka.Writer
 | 
				
			||||||
 | 
						client  *influxdb2.Client
 | 
				
			||||||
 | 
						config  *InfluxOutputConfig
 | 
				
			||||||
 | 
						// Used for testing with a stable timestamp.
 | 
				
			||||||
 | 
						OverrideTimestamp time.Time
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Creates a new influx output controller.
 | 
				
			||||||
 | 
					func NewInfluxOutput() *InfluxOutput {
 | 
				
			||||||
 | 
						i := new(InfluxOutput)
 | 
				
			||||||
 | 
						// Reload the config.
 | 
				
			||||||
 | 
						i.Reload()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return i
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reloads the configuration.
 | 
				
			||||||
 | 
					func (i *InfluxOutput) Reload() {
 | 
				
			||||||
 | 
						// Update config state.
 | 
				
			||||||
 | 
						i.config = &app.config.Influx
 | 
				
			||||||
 | 
						i.kwriter = nil
 | 
				
			||||||
 | 
						i.client = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If kafka output is configured, setup kafka output.
 | 
				
			||||||
 | 
						if len(i.config.KafkaBrokers) != 0 && i.config.KafkaTopic != "" {
 | 
				
			||||||
 | 
							// Configure dialer with configured insecure skip verify.
 | 
				
			||||||
 | 
							dialer := &kafka.Dialer{
 | 
				
			||||||
 | 
								Timeout:   10 * time.Second,
 | 
				
			||||||
 | 
								DualStack: true,
 | 
				
			||||||
 | 
								TLS:       &tls.Config{InsecureSkipVerify: i.config.KafkaInsecureSkipVerify},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If authentication configured, add to dialer.
 | 
				
			||||||
 | 
							if i.config.KafkaUsername != "" {
 | 
				
			||||||
 | 
								dialer.SASLMechanism = plain.Mechanism{
 | 
				
			||||||
 | 
									Username: i.config.KafkaUsername,
 | 
				
			||||||
 | 
									Password: i.config.KafkaPassword,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Make the kafka writer.
 | 
				
			||||||
 | 
							i.kwriter = kafka.NewWriter(kafka.WriterConfig{
 | 
				
			||||||
 | 
								Brokers: i.config.KafkaBrokers,
 | 
				
			||||||
 | 
								Topic:   i.config.KafkaTopic,
 | 
				
			||||||
 | 
								Dialer:  dialer,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If influx output is configured, setup client.
 | 
				
			||||||
 | 
						if i.config.InfluxServer != "" && i.config.Token != "" && i.config.Org != "" && i.config.Bucket != "" {
 | 
				
			||||||
 | 
							c := influxdb2.NewClient(i.config.InfluxServer, i.config.Token)
 | 
				
			||||||
 | 
							// To allow us to detect rather or not the client is configured, we set the pointer value.
 | 
				
			||||||
 | 
							i.client = &c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Collect metrics from prometheus, then parse into lineprotocol format.
 | 
				
			||||||
 | 
					func (i *InfluxOutput) CollectAndLineprotocolFormat() ([]byte, error) {
 | 
				
			||||||
 | 
						res, err := app.registry.Gather()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return i.LineprotocolFormat(res)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parse promteheus metrics into lineprotocol format.
 | 
				
			||||||
 | 
					func (i *InfluxOutput) LineprotocolFormat(res []*io_prometheus_client.MetricFamily) ([]byte, error) {
 | 
				
			||||||
 | 
						var enc lineprotocol.Encoder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get prefix for transforming prometheus name to influx.
 | 
				
			||||||
 | 
						namePrefix := namespace + "_"
 | 
				
			||||||
 | 
						enc.SetPrecision(lineprotocol.Microsecond)
 | 
				
			||||||
 | 
						now := time.Now()
 | 
				
			||||||
 | 
						if !i.OverrideTimestamp.IsZero() {
 | 
				
			||||||
 | 
							now = i.OverrideTimestamp
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Each metric, send to encoder.
 | 
				
			||||||
 | 
						for _, metric := range res {
 | 
				
			||||||
 | 
							// Get name, removing prefix.
 | 
				
			||||||
 | 
							name := metric.GetName()
 | 
				
			||||||
 | 
							if name[0:len(namePrefix)] == namePrefix {
 | 
				
			||||||
 | 
								name = name[len(namePrefix):]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							mtype := metric.GetType()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// There can be multiple results for a metric, with different tags.
 | 
				
			||||||
 | 
							// We need to make the influx metric on each result.
 | 
				
			||||||
 | 
							for _, m := range metric.GetMetric() {
 | 
				
			||||||
 | 
								// Start new line.
 | 
				
			||||||
 | 
								enc.StartLine(namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Add tags.
 | 
				
			||||||
 | 
								enc.AddTag("host", app.config.Hostname)
 | 
				
			||||||
 | 
								for _, l := range m.Label {
 | 
				
			||||||
 | 
									enc.AddTag(l.GetName(), l.GetValue())
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Depending on type, add field.
 | 
				
			||||||
 | 
								switch mtype {
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_COUNTER:
 | 
				
			||||||
 | 
									enc.AddField(name, lineprotocol.MustNewValue(m.Counter.GetValue()))
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_GAUGE:
 | 
				
			||||||
 | 
									enc.AddField(name, lineprotocol.MustNewValue(m.Gauge.GetValue()))
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_SUMMARY:
 | 
				
			||||||
 | 
									enc.AddField(name, lineprotocol.MustNewValue(m.Summary.GetSampleSum()))
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_UNTYPED:
 | 
				
			||||||
 | 
									enc.AddField(name, lineprotocol.MustNewValue(m.Untyped.GetValue()))
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_HISTOGRAM:
 | 
				
			||||||
 | 
									enc.AddField(name, lineprotocol.MustNewValue(m.Histogram.GetSampleSum()))
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_GAUGE_HISTOGRAM:
 | 
				
			||||||
 | 
									enc.AddField(name, lineprotocol.MustNewValue(m.Histogram.GetSampleSum()))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// End line for next metric.
 | 
				
			||||||
 | 
								enc.EndLine(now)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check for errors.
 | 
				
			||||||
 | 
						err := enc.Err()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return enc.Bytes(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Collect metrics from prometheus, then parse into influx json format.
 | 
				
			||||||
 | 
					func (i *InfluxOutput) CollectAndJSONFormat() ([]byte, error) {
 | 
				
			||||||
 | 
						res, err := app.registry.Gather()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return i.JSONFormat(res)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parse promteheus metrics into influx json format.
 | 
				
			||||||
 | 
					func (i *InfluxOutput) JSONFormat(res []*io_prometheus_client.MetricFamily) ([]byte, error) {
 | 
				
			||||||
 | 
						var buff bytes.Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get prefix for transforming prometheus name to influx.
 | 
				
			||||||
 | 
						namePrefix := namespace + "_"
 | 
				
			||||||
 | 
						now := time.Now()
 | 
				
			||||||
 | 
						if !i.OverrideTimestamp.IsZero() {
 | 
				
			||||||
 | 
							now = i.OverrideTimestamp
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Each metric, send to encoder.
 | 
				
			||||||
 | 
						for _, metric := range res {
 | 
				
			||||||
 | 
							// Get name, removing prefix.
 | 
				
			||||||
 | 
							name := metric.GetName()
 | 
				
			||||||
 | 
							if name[0:len(namePrefix)] == namePrefix {
 | 
				
			||||||
 | 
								name = name[len(namePrefix):]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							mtype := metric.GetType()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// There can be multiple results for a metric, with different tags.
 | 
				
			||||||
 | 
							// We need to make the influx metric on each result.
 | 
				
			||||||
 | 
							for _, m := range metric.GetMetric() {
 | 
				
			||||||
 | 
								// Create a base dictionary for housing the metric.
 | 
				
			||||||
 | 
								metric := make(map[string]interface{}, 4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Add tags.
 | 
				
			||||||
 | 
								tags := make(map[string]string, len(m.Label)+1)
 | 
				
			||||||
 | 
								tags["host"] = app.config.Hostname
 | 
				
			||||||
 | 
								for _, l := range m.Label {
 | 
				
			||||||
 | 
									tags[l.GetName()] = l.GetValue()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								metric["tags"] = tags
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Depending on type, add field.
 | 
				
			||||||
 | 
								fields := make(map[string]interface{}, 1)
 | 
				
			||||||
 | 
								switch mtype {
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_COUNTER:
 | 
				
			||||||
 | 
									fields[name] = m.Counter.GetValue()
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_GAUGE:
 | 
				
			||||||
 | 
									fields[name] = m.Gauge.GetValue()
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_SUMMARY:
 | 
				
			||||||
 | 
									fields[name] = m.Summary.GetSampleSum()
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_UNTYPED:
 | 
				
			||||||
 | 
									fields[name] = m.Untyped.GetValue()
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_HISTOGRAM:
 | 
				
			||||||
 | 
									fields[name] = m.Histogram.GetSampleSum()
 | 
				
			||||||
 | 
								case io_prometheus_client.MetricType_GAUGE_HISTOGRAM:
 | 
				
			||||||
 | 
									fields[name] = m.Histogram.GetSampleSum()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								metric["fields"] = fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Set metric name and ending timestamp.
 | 
				
			||||||
 | 
								metric["name"] = namespace
 | 
				
			||||||
 | 
								metric["timestamp"] = now.UnixNano() / int64(time.Microsecond)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Serialize into json.
 | 
				
			||||||
 | 
								serialized, err := json.Marshal(metric)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Append new line for parsing into individual metrics.
 | 
				
			||||||
 | 
								serialized = append(serialized, '\n')
 | 
				
			||||||
 | 
								// Write the serialized metric.
 | 
				
			||||||
 | 
								buff.Write(serialized)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return buff.Bytes(), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Returns rather or not output is enabled.
 | 
				
			||||||
 | 
					func (i *InfluxOutput) OutputEnabled() bool {
 | 
				
			||||||
 | 
						return (i.kwriter != nil || i.client != nil) && i.config.Frequency != 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Start the influx output schedule.
 | 
				
			||||||
 | 
					func (i *InfluxOutput) Start(ctx context.Context) {
 | 
				
			||||||
 | 
						// If no outputs configured, stop here.
 | 
				
			||||||
 | 
						if !i.OutputEnabled() {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup schedule.
 | 
				
			||||||
 | 
						ticker := time.NewTicker(i.config.Frequency)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							select {
 | 
				
			||||||
 | 
							// If schedule tick, gather metrics and send output.
 | 
				
			||||||
 | 
							case <-ticker.C:
 | 
				
			||||||
 | 
								res, err := app.registry.Gather()
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									log.Println("Error collecting metric for influx output:", err)
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If kafka output enabled, send output to kafka.
 | 
				
			||||||
 | 
								if i.kwriter != nil {
 | 
				
			||||||
 | 
									var messages []kafka.Message
 | 
				
			||||||
 | 
									var data []byte
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Parse metrics based on format.
 | 
				
			||||||
 | 
									if i.config.KafkaOutputFormat == "json" {
 | 
				
			||||||
 | 
										data, err = i.JSONFormat(res)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										data, err = i.LineprotocolFormat(res)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										log.Println("Error formatting metrics for kafka:", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Setup parser for new lines.
 | 
				
			||||||
 | 
									r := bytes.NewReader(data)
 | 
				
			||||||
 | 
									scanner := bufio.NewScanner(r)
 | 
				
			||||||
 | 
									scanner.Split(bufio.ScanLines)
 | 
				
			||||||
 | 
									// Set routing key to hostname.
 | 
				
			||||||
 | 
									routingKey := []byte(app.config.Hostname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Scan formatted metrics for each individual metric.
 | 
				
			||||||
 | 
									for scanner.Scan() {
 | 
				
			||||||
 | 
										b := scanner.Bytes()
 | 
				
			||||||
 | 
										// Add back the new line as Kafka output expects it.
 | 
				
			||||||
 | 
										b = append(b, '\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Add message.
 | 
				
			||||||
 | 
										messages = append(messages, kafka.Message{
 | 
				
			||||||
 | 
											Key:   routingKey,
 | 
				
			||||||
 | 
											Value: b,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Write the messages to Kafka.
 | 
				
			||||||
 | 
									err := i.kwriter.WriteMessages(ctx, messages...)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										log.Println("Unable to write to Kafka:", err)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If influx configured, write metrics to Influx's API.
 | 
				
			||||||
 | 
								if i.client != nil {
 | 
				
			||||||
 | 
									c := *i.client
 | 
				
			||||||
 | 
									writeAPI := c.WriteAPIBlocking(i.config.Org, i.config.Bucket)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Parse metrics to lineprotocol.
 | 
				
			||||||
 | 
									data, err := i.LineprotocolFormat(res)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										log.Println("Error collecting metric for influx output:", err)
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Send all metrics to InfluxDB.
 | 
				
			||||||
 | 
									writeAPI.WriteRecord(ctx, string(data))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If the context is done, we need to close out connections.
 | 
				
			||||||
 | 
							case <-ctx.Done():
 | 
				
			||||||
 | 
								if i.kwriter != nil {
 | 
				
			||||||
 | 
									i.kwriter.Close()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if i.client != nil {
 | 
				
			||||||
 | 
									c := *i.client
 | 
				
			||||||
 | 
									c.Close()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										75
									
								
								influx_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								influx_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Setup global app variable with test config for tests.
 | 
				
			||||||
 | 
					func setupInfluxTestApp() {
 | 
				
			||||||
 | 
						app = new(App)
 | 
				
			||||||
 | 
						app.flags = new(Flags)
 | 
				
			||||||
 | 
						app.flags.ConfigPath = "test/test_config.yaml"
 | 
				
			||||||
 | 
						app.ReadConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load exporters.
 | 
				
			||||||
 | 
						app.ldapExporter = NewLDAPExporter()
 | 
				
			||||||
 | 
						app.freeIPAExporter = NewFreeIPAExporter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add exporters to registry.
 | 
				
			||||||
 | 
						reg := prometheus.NewPedanticRegistry()
 | 
				
			||||||
 | 
						reg.Register(app.ldapExporter)
 | 
				
			||||||
 | 
						reg.Register(app.freeIPAExporter)
 | 
				
			||||||
 | 
						app.registry = reg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup influx output.
 | 
				
			||||||
 | 
						app.influxOutput = NewInfluxOutput()
 | 
				
			||||||
 | 
						app.influxOutput.OverrideTimestamp, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Main Influx test function.
 | 
				
			||||||
 | 
					func TestInflux(t *testing.T) {
 | 
				
			||||||
 | 
						// Setup configs.
 | 
				
			||||||
 | 
						setupInfluxTestApp()
 | 
				
			||||||
 | 
						// Run the LDAP test server.
 | 
				
			||||||
 | 
						server := NewLDAPTestServer()
 | 
				
			||||||
 | 
						server.Run()
 | 
				
			||||||
 | 
						// Start http server.
 | 
				
			||||||
 | 
						httpServer := NewFreeIPATestServer()
 | 
				
			||||||
 | 
						httpServer.Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get metrics in influx line protocol format.
 | 
				
			||||||
 | 
						data, err := app.influxOutput.CollectAndLineprotocolFormat()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln("Error collecting metrics for telegraf:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Check difference from .
 | 
				
			||||||
 | 
						difference, err := FileDiff(string(data), "test/influx.lp")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if difference != "" {
 | 
				
			||||||
 | 
							t.Fatalf("Difference from expected result:\n%s", difference)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get metrics in influx json format.
 | 
				
			||||||
 | 
						data, err = app.influxOutput.CollectAndJSONFormat()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalln("Error collecting metrics for telegraf:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Print the encoded data.
 | 
				
			||||||
 | 
						difference, err = FileDiff(string(data), "test/influx.json")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if difference != "" {
 | 
				
			||||||
 | 
							t.Fatalf("Difference from expected result:\n%s", difference)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We're done, let's stop serving the test LDAP server.
 | 
				
			||||||
 | 
						server.Stop()
 | 
				
			||||||
 | 
						httpServer.Stop()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										305
									
								
								ldap.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										305
									
								
								ldap.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,305 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
 | 
						"crypto/x509"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-ldap/ldap/v3"
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Prometheus exporter for LDAP metrics.
 | 
				
			||||||
 | 
					type LDAPExporter struct {
 | 
				
			||||||
 | 
						config  *LDAPConfig
 | 
				
			||||||
 | 
						conn    *ldap.Conn
 | 
				
			||||||
 | 
						mutex   sync.RWMutex
 | 
				
			||||||
 | 
						metrics []metricInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Basic metrics.
 | 
				
			||||||
 | 
						up                          prometheus.Gauge
 | 
				
			||||||
 | 
						totalScrapes, totalFailures prometheus.Counter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Replica metrics and cache.
 | 
				
			||||||
 | 
						replicaLastUpdate    *prometheus.Desc
 | 
				
			||||||
 | 
						replicaErrorCode     *prometheus.Desc
 | 
				
			||||||
 | 
						replicaSyncInfoCache []*ReplicaSyncInfo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Make the LDAP exporter.
 | 
				
			||||||
 | 
					func NewLDAPExporter() *LDAPExporter {
 | 
				
			||||||
 | 
						e := new(LDAPExporter)
 | 
				
			||||||
 | 
						e.Reload()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return e
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reload the configurations.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) Reload() {
 | 
				
			||||||
 | 
						e.config = &app.config.LDAP
 | 
				
			||||||
 | 
						e.metrics = nil
 | 
				
			||||||
 | 
						e.setupMetrics()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Connect to the LDAP server.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) connect() error {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						// Setup TLS configurations.
 | 
				
			||||||
 | 
						tlsConifg := tls.Config{InsecureSkipVerify: e.config.InsecureSkipVerify}
 | 
				
			||||||
 | 
						// Load CA certificates if configured.
 | 
				
			||||||
 | 
						if e.config.CACertificate != "" {
 | 
				
			||||||
 | 
							caCert, err := os.ReadFile(e.config.CACertificate)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Println("Error reading CA certificate:", err)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								caCertPool := x509.NewCertPool()
 | 
				
			||||||
 | 
								caCertPool.AppendCertsFromPEM(caCert)
 | 
				
			||||||
 | 
								tlsConifg.RootCAs = caCertPool
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Depending on connect method, connect to the LDAP server.
 | 
				
			||||||
 | 
						if e.config.ConnectMethod == LDAPMethodSecure {
 | 
				
			||||||
 | 
							e.conn, err = ldap.DialURL(e.config.Address, ldap.DialWithTLSConfig(&tlsConifg))
 | 
				
			||||||
 | 
						} else if e.config.ConnectMethod == LDAPMethodStartTLS {
 | 
				
			||||||
 | 
							e.conn, err = ldap.DialURL(e.config.Address)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err = e.conn.StartTLS(&tlsConifg)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							e.conn, err = ldap.DialURL(e.config.Address)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// If error, may be with StartTLS, so disconnect.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.disconnect()
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Attempt to authenticate.
 | 
				
			||||||
 | 
						if e.config.BindPassword == "" {
 | 
				
			||||||
 | 
							err = e.conn.UnauthenticatedBind(e.config.BindDN)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							err = e.conn.Bind(e.config.BindDN, e.config.BindPassword)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// If error in authenticating, disconnect.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							e.disconnect()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Return error if occurred or nil if no error.
 | 
				
			||||||
 | 
						return err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Disconnect from the LDAP server.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) disconnect() {
 | 
				
			||||||
 | 
						if e.conn != nil {
 | 
				
			||||||
 | 
							// Close the connection.
 | 
				
			||||||
 | 
							e.conn.Close()
 | 
				
			||||||
 | 
							e.conn = nil
 | 
				
			||||||
 | 
							// Clear the cache.
 | 
				
			||||||
 | 
							e.replicaSyncInfoCache = nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper function to pull the `numSubordinates` attribute from LDAP.
 | 
				
			||||||
 | 
					// This attribute is helpful in getting a count of records under a tree,
 | 
				
			||||||
 | 
					// which may be user accounts or otherwise.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) countSubordinates(baseDN string) (float64, error) {
 | 
				
			||||||
 | 
						// Setup request for the `numSubordinates` attribute.
 | 
				
			||||||
 | 
						searchRequest := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
							baseDN+e.config.BaseDN,
 | 
				
			||||||
 | 
							ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
 | 
				
			||||||
 | 
							"(objectClass=*)",
 | 
				
			||||||
 | 
							[]string{"numSubordinates"},
 | 
				
			||||||
 | 
							nil,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Search for the records.
 | 
				
			||||||
 | 
						sr, err := e.conn.Search(searchRequest)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get the string of the entry.
 | 
				
			||||||
 | 
						var count string
 | 
				
			||||||
 | 
						for _, entry := range sr.Entries {
 | 
				
			||||||
 | 
							count = entry.GetAttributeValue("numSubordinates")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parse received string as float64.
 | 
				
			||||||
 | 
						return strconv.ParseFloat(count, 64)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Short hand to append the BaseDN and request only the `dn` entry, as is all that is needed for most metrics.
 | 
				
			||||||
 | 
					// The countEntriesFull function just counts each sub entry from a record.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) countEntries(baseDN, filter string) (float64, error) {
 | 
				
			||||||
 | 
						return e.countEntriesFull(baseDN+e.config.BaseDN, filter, []string{"dn"})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Count sub entries of a record and return the count.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) countEntriesFull(baseDN, filter string, attributes []string) (float64, error) {
 | 
				
			||||||
 | 
						// Setup request.
 | 
				
			||||||
 | 
						searchRequest := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
							baseDN,
 | 
				
			||||||
 | 
							ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, e.config.SearchSizeLimit, false,
 | 
				
			||||||
 | 
							filter,
 | 
				
			||||||
 | 
							attributes,
 | 
				
			||||||
 | 
							nil,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Perform the search.
 | 
				
			||||||
 | 
						sr, err := e.conn.SearchWithPaging(searchRequest, uint32(e.config.SearchSizeLimit))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If no such object error returned, return count of 0 with no error.
 | 
				
			||||||
 | 
						if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
 | 
				
			||||||
 | 
							return 0, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Other errors, return the error code.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Return a float64 representation of the number of entries found.
 | 
				
			||||||
 | 
						return float64(len(sr.Entries)), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The standard date format used in LDAP records.
 | 
				
			||||||
 | 
					const LDAPGeneralizedTimeFormat = "20060102150405Z"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Information on sync status to a replica.
 | 
				
			||||||
 | 
					type ReplicaSyncInfo struct {
 | 
				
			||||||
 | 
						Host            string
 | 
				
			||||||
 | 
						LastUpdateStart time.Time
 | 
				
			||||||
 | 
						LastUpdateEnd   time.Time
 | 
				
			||||||
 | 
						Status          string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Pull and return replica sync information.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) replicaSyncInfo() ([]*ReplicaSyncInfo, error) {
 | 
				
			||||||
 | 
						// If not cached, pull it.
 | 
				
			||||||
 | 
						if len(e.replicaSyncInfoCache) == 0 {
 | 
				
			||||||
 | 
							// Combined dictionary of available peers, both masters and replicas, with their config.
 | 
				
			||||||
 | 
							peers := make(map[string][]string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get the master servers.
 | 
				
			||||||
 | 
							masterRequest := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
								"cn=masters,cn=ipa,cn=etc,"+e.config.BaseDN,
 | 
				
			||||||
 | 
								ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
 | 
				
			||||||
 | 
								"(objectClass=*)",
 | 
				
			||||||
 | 
								[]string{},
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							sr, err := e.conn.Search(masterRequest)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// For each master replica, add them with a simple "master" config.
 | 
				
			||||||
 | 
							for _, entry := range sr.Entries {
 | 
				
			||||||
 | 
								cn := entry.GetAttributeValue("cn")
 | 
				
			||||||
 | 
								peers[cn] = []string{"master", ""}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get replicas.
 | 
				
			||||||
 | 
							replicaRequest := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
								"cn=replicas,cn=ipa,cn=etc,"+e.config.BaseDN,
 | 
				
			||||||
 | 
								ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
 | 
				
			||||||
 | 
								"(objectClass=*)",
 | 
				
			||||||
 | 
								[]string{},
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							sr, err = e.conn.Search(replicaRequest)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Add each replica with their configs.
 | 
				
			||||||
 | 
							for _, entry := range sr.Entries {
 | 
				
			||||||
 | 
								cn := entry.GetAttributeValue("cn")
 | 
				
			||||||
 | 
								configString := entry.GetAttributeValue("ipaConfigString")
 | 
				
			||||||
 | 
								peers[cn] = strings.Split(configString, ":")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Determine if this host is an existing peer and rather or not there
 | 
				
			||||||
 | 
							// is a windows sync peer configuration.
 | 
				
			||||||
 | 
							isReplica := false
 | 
				
			||||||
 | 
							winsyncPeer := ""
 | 
				
			||||||
 | 
							for k, v := range peers {
 | 
				
			||||||
 | 
								// If configured hostname matches this peer, this is a replica.
 | 
				
			||||||
 | 
								if app.config.Hostname == k {
 | 
				
			||||||
 | 
									isReplica = true
 | 
				
			||||||
 | 
									// If the config key is winsync, note the peer for finding the replication agreements.
 | 
				
			||||||
 | 
									if len(v) == 2 && v[0] == "winsync" {
 | 
				
			||||||
 | 
										winsyncPeer = v[1]
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If this host isn't a replica, there is no syncing to/from other nodes. Fail here.
 | 
				
			||||||
 | 
							if !isReplica {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("this is not an replica/master node")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if winsyncPeer != "" {
 | 
				
			||||||
 | 
								// Find replication agreements for winsync.
 | 
				
			||||||
 | 
								suffix := ldap.EscapeDN(e.config.BaseDN)
 | 
				
			||||||
 | 
								dn := "cn=meTo" + ldap.EscapeDN(app.config.Hostname) + "cn=replica,cn=" + suffix + ",cn=mapping tree,cn=config"
 | 
				
			||||||
 | 
								winsyncRequest := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
									dn,
 | 
				
			||||||
 | 
									ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
 | 
				
			||||||
 | 
									"(objectclass=nsDSWindowsReplicationAgreement)",
 | 
				
			||||||
 | 
									[]string{},
 | 
				
			||||||
 | 
									nil,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								sr, err = e.conn.Search(winsyncRequest)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// Find replication agreements for regular ds389 replications.
 | 
				
			||||||
 | 
								filter := "(|(&(objectclass=nsds5ReplicationAgreement)(nsDS5ReplicaRoot=" + e.config.BaseDN + "))(objectclass=nsDSWindowsReplicationAgreement))"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								winsyncRequest := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
									"cn=mapping tree,cn=config",
 | 
				
			||||||
 | 
									ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
 | 
				
			||||||
 | 
									filter,
 | 
				
			||||||
 | 
									[]string{},
 | 
				
			||||||
 | 
									nil,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
								sr, err = e.conn.Search(winsyncRequest)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// For each replication agreement, parse replica info.
 | 
				
			||||||
 | 
							for _, entry := range sr.Entries {
 | 
				
			||||||
 | 
								// Parse the last update start.
 | 
				
			||||||
 | 
								startTime, err := time.Parse(LDAPGeneralizedTimeFormat, entry.GetAttributeValue("nsds5replicaLastUpdateStart"))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Parse the last update end.
 | 
				
			||||||
 | 
								endTime, err := time.Parse(LDAPGeneralizedTimeFormat, entry.GetAttributeValue("nsds5replicaLastUpdateEnd"))
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Create the replica info.
 | 
				
			||||||
 | 
								replica := &ReplicaSyncInfo{
 | 
				
			||||||
 | 
									Host:            entry.GetAttributeValue("nsDS5ReplicaHost"),
 | 
				
			||||||
 | 
									LastUpdateStart: startTime,
 | 
				
			||||||
 | 
									LastUpdateEnd:   endTime,
 | 
				
			||||||
 | 
									Status:          entry.GetAttributeValue("nsds5replicaLastUpdateStatus"),
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								// Append to the cache.
 | 
				
			||||||
 | 
								e.replicaSyncInfoCache = append(e.replicaSyncInfoCache, replica)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Return cached entries.
 | 
				
			||||||
 | 
						return e.replicaSyncInfoCache, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										258
									
								
								ldap_metrics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								ldap_metrics.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,258 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-ldap/ldap/v3"
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Creates a metric and appends it to the to the available metrics if enabled.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) NewMetric(metricName string, docString string, t prometheus.ValueType, value func() (float64, error)) {
 | 
				
			||||||
 | 
						// If metric is disabled, stop here.
 | 
				
			||||||
 | 
						for _, metric := range e.config.DisabledMetrics {
 | 
				
			||||||
 | 
							if metric == metricName {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Create info for this metric.
 | 
				
			||||||
 | 
						info := metricInfo{
 | 
				
			||||||
 | 
							Desc: prometheus.NewDesc(
 | 
				
			||||||
 | 
								prometheus.BuildFQName(namespace, "ldap", metricName),
 | 
				
			||||||
 | 
								docString,
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
							),
 | 
				
			||||||
 | 
							Type:  t,
 | 
				
			||||||
 | 
							Value: value,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Add metric to list.
 | 
				
			||||||
 | 
						e.metrics = append(e.metrics, info)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sets up the exporter with all needed metrics for LDAP.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) setupMetrics() {
 | 
				
			||||||
 | 
						// Setup basic metrics.
 | 
				
			||||||
 | 
						e.up = prometheus.NewGauge(prometheus.GaugeOpts{
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Name:      "ldap_up",
 | 
				
			||||||
 | 
							Help:      "Was the last scrape of FreeIPA successful.",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						e.totalScrapes = prometheus.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Name:      "ldap_scrapes_total",
 | 
				
			||||||
 | 
							Help:      "Current total HAProxy scrapes.",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						e.totalFailures = prometheus.NewCounter(prometheus.CounterOpts{
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Name:      "ldap_failures_total",
 | 
				
			||||||
 | 
							Help:      "Number of errors while scapping metrics.",
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of active users.
 | 
				
			||||||
 | 
						e.NewMetric("user_active_total", "Total number of active users.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countSubordinates("cn=users,cn=accounts,")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of stagged users.
 | 
				
			||||||
 | 
						e.NewMetric("user_stage_total", "Total number of staged users.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countSubordinates("cn=staged users,cn=accounts,cn=provisioning,")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of inactive, preserved users.
 | 
				
			||||||
 | 
						e.NewMetric("user_preserved_total", "Total number of preserved users.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countSubordinates("cn=deleted users,cn=accounts,cn=provisioning,")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of groups.
 | 
				
			||||||
 | 
						e.NewMetric("group_total", "Total number of groups.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntries("cn=groups,cn=accounts,", "(objectClass=ipausergroup)")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of hosts.
 | 
				
			||||||
 | 
						e.NewMetric("host_total", "Total number of hosts.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntries("cn=computers,cn=accounts,", "(fqdn=*)")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of services.
 | 
				
			||||||
 | 
						e.NewMetric("service_total", "Total number of services.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntries("cn=services,cn=accounts,", "(krbprincipalname=*)")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of net groups.
 | 
				
			||||||
 | 
						e.NewMetric("net_group_total", "Total number of net groups.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntries("cn=ng,cn=alt,", "(ipaUniqueID=*)")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of host groups.
 | 
				
			||||||
 | 
						e.NewMetric("host_group_total", "Total number of host groups.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countSubordinates("cn=hostgroups,cn=accounts,")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of host base access crontrols.
 | 
				
			||||||
 | 
						e.NewMetric("hbac_rule_total", "Total number of HBAC rules.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntries("cn=hbac,", "(ipaUniqueID=*)")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of sudo rules.
 | 
				
			||||||
 | 
						e.NewMetric("sudo_rule_total", "Total number of sudo rules.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntries("cn=sudorules,cn=sudo,", "(ipaUniqueID=*)")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of DNS zones.
 | 
				
			||||||
 | 
						e.NewMetric("dns_zone_total", "Total number of DNS zones.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntries("cn=dns,", "(|(objectClass=idnszone)(objectClass=idnsforwardzone))")
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of certificates.
 | 
				
			||||||
 | 
						e.NewMetric("certificate_total", "Total number of certificates.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntriesFull(
 | 
				
			||||||
 | 
								"ou=certificateRepository,ou=ca,o=ipaca",
 | 
				
			||||||
 | 
								"(certStatus=*)",
 | 
				
			||||||
 | 
								[]string{"subjectName"},
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of conflicts.
 | 
				
			||||||
 | 
						e.NewMetric("conflicts_total", "Total number of LDAP conflicts.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							return e.countEntriesFull(
 | 
				
			||||||
 | 
								e.config.BaseDN,
 | 
				
			||||||
 | 
								"(|(nsds5ReplConflict=*)(&(objectclass=ldapsubentry)(nsds5ReplConflict=*)))",
 | 
				
			||||||
 | 
								[]string{"nsds5ReplConflict"},
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Info: Number of ghost replicas.
 | 
				
			||||||
 | 
						e.NewMetric("ghost_replica_total", "Total number of ghost replicas.", prometheus.CounterValue, func() (float64, error) {
 | 
				
			||||||
 | 
							// Setup ghost record request.
 | 
				
			||||||
 | 
							searchRequest := ldap.NewSearchRequest(
 | 
				
			||||||
 | 
								e.config.BaseDN,
 | 
				
			||||||
 | 
								ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false,
 | 
				
			||||||
 | 
								"(&(objectclass=nstombstone)(nsUniqueId=ffffffff-ffffffff-ffffffff-ffffffff))",
 | 
				
			||||||
 | 
								[]string{"nscpentrywsi"},
 | 
				
			||||||
 | 
								nil,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Search for ghost records.
 | 
				
			||||||
 | 
							sr, err := e.conn.Search(searchRequest)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return 0, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check each entry and count replica entries.
 | 
				
			||||||
 | 
							var count float64
 | 
				
			||||||
 | 
							for _, entry := range sr.Entries {
 | 
				
			||||||
 | 
								// If the entry wsi is a replica but doesn't contain ldap, count it as a ghost.
 | 
				
			||||||
 | 
								nscpentrywsi := entry.GetAttributeValue("nscpentrywsi")
 | 
				
			||||||
 | 
								if strings.Contains(nscpentrywsi, "replica ") && !strings.Contains(nscpentrywsi, "ldap") {
 | 
				
			||||||
 | 
									count++
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return count, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Replica specific metrics.
 | 
				
			||||||
 | 
						e.replicaLastUpdate = prometheus.NewDesc(
 | 
				
			||||||
 | 
							prometheus.BuildFQName(namespace, "ldap", "replica_last_update"),
 | 
				
			||||||
 | 
							"The last time a replica sync occurred.",
 | 
				
			||||||
 | 
							[]string{"replica"},
 | 
				
			||||||
 | 
							nil,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						e.replicaErrorCode = prometheus.NewDesc(
 | 
				
			||||||
 | 
							prometheus.BuildFQName(namespace, "ldap", "replica_error_code"),
 | 
				
			||||||
 | 
							"Error code from last replica sync.",
 | 
				
			||||||
 | 
							[]string{"replica"},
 | 
				
			||||||
 | 
							nil,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Provide Promethues all descriptions of metrics exported.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) Describe(ch chan<- *prometheus.Desc) {
 | 
				
			||||||
 | 
						for _, m := range e.metrics {
 | 
				
			||||||
 | 
							ch <- m.Desc
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ch <- e.up.Desc()
 | 
				
			||||||
 | 
						ch <- e.totalScrapes.Desc()
 | 
				
			||||||
 | 
						ch <- e.totalFailures.Desc()
 | 
				
			||||||
 | 
						ch <- e.replicaLastUpdate
 | 
				
			||||||
 | 
						ch <- e.replicaErrorCode
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Collects metrics exported and provide values to Prometheus.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) Collect(ch chan<- prometheus.Metric) {
 | 
				
			||||||
 | 
						// Protect metrics from concurrent collects.
 | 
				
			||||||
 | 
						e.mutex.Lock()
 | 
				
			||||||
 | 
						defer e.mutex.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Scrape LDAP metrics.
 | 
				
			||||||
 | 
						up := e.scrape(ch)
 | 
				
			||||||
 | 
						// Update the up status.
 | 
				
			||||||
 | 
						e.up.Set(up)
 | 
				
			||||||
 | 
						// If not up, count as a failed scrape.
 | 
				
			||||||
 | 
						if up == 0 {
 | 
				
			||||||
 | 
							e.totalFailures.Inc()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send basic metrics.
 | 
				
			||||||
 | 
						ch <- e.up
 | 
				
			||||||
 | 
						ch <- e.totalScrapes
 | 
				
			||||||
 | 
						ch <- e.totalFailures
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Test LDAP and pull metrics.
 | 
				
			||||||
 | 
					func (e *LDAPExporter) scrape(ch chan<- prometheus.Metric) float64 {
 | 
				
			||||||
 | 
						// Increment the total number of scrapes.
 | 
				
			||||||
 | 
						e.totalScrapes.Inc()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Attempt to connect.
 | 
				
			||||||
 | 
						err := e.connect()
 | 
				
			||||||
 | 
						// If failure, LDAP is down.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Println("Error connecting to ldap:", err)
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Disconnect after done scrapping.
 | 
				
			||||||
 | 
						defer e.disconnect()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update data for each metric.
 | 
				
			||||||
 | 
						for _, m := range e.metrics {
 | 
				
			||||||
 | 
							// Get the value of the metric.
 | 
				
			||||||
 | 
							value, err := m.Value()
 | 
				
			||||||
 | 
							// If an error occurred getting the value, log it for debug.
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Printf("Error retrieving value for metric %s: %s\n", m.Desc.String(), err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Update the value.
 | 
				
			||||||
 | 
							ch <- prometheus.MustNewConstMetric(m.Desc, m.Type, value)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get replica sync status.
 | 
				
			||||||
 | 
						replicaSyncInfo, err := e.replicaSyncInfo()
 | 
				
			||||||
 | 
						// If error returned, log it.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("Error retrieving replica sync info: %s\n", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Error code parsing.
 | 
				
			||||||
 | 
						statusRx := regexp.MustCompile(`Error \(([0-9-]+)\)`)
 | 
				
			||||||
 | 
						// Update metric for each replica.
 | 
				
			||||||
 | 
						for _, replica := range replicaSyncInfo {
 | 
				
			||||||
 | 
							// Get the last update date UNIX time and send metric.
 | 
				
			||||||
 | 
							ch <- prometheus.MustNewConstMetric(e.replicaLastUpdate, prometheus.GaugeValue, float64(replica.LastUpdateEnd.Unix()), replica.Host)
 | 
				
			||||||
 | 
							// Check if status code can be parsed.
 | 
				
			||||||
 | 
							match := statusRx.FindStringSubmatch(replica.Status)
 | 
				
			||||||
 | 
							if len(match) == 2 {
 | 
				
			||||||
 | 
								// Make status code a float64 as is used by Prometheus. Ignoring errors as none should exist with the regex match being integers only.
 | 
				
			||||||
 | 
								errorCode, _ := strconv.ParseFloat(match[1], 64)
 | 
				
			||||||
 | 
								// Send the status code as a metric.
 | 
				
			||||||
 | 
								ch <- prometheus.MustNewConstMetric(e.replicaErrorCode, prometheus.GaugeValue, errorCode, replica.Host)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// At this point, we were able to connect, so LDAP is up.
 | 
				
			||||||
 | 
						return 1
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										356
									
								
								ldap_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										356
									
								
								ldap_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,356 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
						"unicode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/jimlambrt/gldap"
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus/testutil"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Setup global app variable with test config for tests.
 | 
				
			||||||
 | 
					func setupLdapTestApp() {
 | 
				
			||||||
 | 
						app = new(App)
 | 
				
			||||||
 | 
						app.flags = new(Flags)
 | 
				
			||||||
 | 
						app.flags.ConfigPath = "test/test_config.yaml"
 | 
				
			||||||
 | 
						app.ReadConfig()
 | 
				
			||||||
 | 
						app.ldapExporter = NewLDAPExporter()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Base LDAP entry, using this as the library doesn't export variables that are useful
 | 
				
			||||||
 | 
					// for working the way I wanted with a generic parser.
 | 
				
			||||||
 | 
					type ldapEntry struct {
 | 
				
			||||||
 | 
						dn         string
 | 
				
			||||||
 | 
						attributes map[string][]string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Prints the ldap entry with all attributes in ldif format.
 | 
				
			||||||
 | 
					// Mainly used in debugging.
 | 
				
			||||||
 | 
					func (e *ldapEntry) Print() {
 | 
				
			||||||
 | 
						fmt.Printf("DN: %s\n", e.dn)
 | 
				
			||||||
 | 
						for name, attr := range e.attributes {
 | 
				
			||||||
 | 
							for _, v := range attr {
 | 
				
			||||||
 | 
								fmt.Printf("%s: %s\n", name, v)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Parse ldif file and return all entries.
 | 
				
			||||||
 | 
					func ParseLDIF(ldifPath string) (res []*ldapEntry) {
 | 
				
			||||||
 | 
						// Open the file provided.
 | 
				
			||||||
 | 
						ldif, err := os.Open(ldifPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal("Error opening tests:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer ldif.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Basic variables used in parsing.
 | 
				
			||||||
 | 
						var dn, fullLine string
 | 
				
			||||||
 | 
						attributes := make(map[string][]string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Parsing handlers.
 | 
				
			||||||
 | 
						scanner := bufio.NewScanner(ldif)
 | 
				
			||||||
 | 
						scanner.Split(bufio.ScanLines)
 | 
				
			||||||
 | 
						parseRx := regexp.MustCompile(`([a-zA-Z0-9:]+):\s(.*)`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check each line of the file amd parse.
 | 
				
			||||||
 | 
						for scanner.Scan() {
 | 
				
			||||||
 | 
							line := scanner.Text()
 | 
				
			||||||
 | 
							// Ignore comment and blank lines.
 | 
				
			||||||
 | 
							if line == "" || strings.HasPrefix(line, "#") {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Check if first chracter is a space.
 | 
				
			||||||
 | 
							isWrapped := false
 | 
				
			||||||
 | 
							for _, c := range line {
 | 
				
			||||||
 | 
								isWrapped = unicode.IsSpace(c)
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If this is wrapped from the last line, append and read next line.
 | 
				
			||||||
 | 
							if isWrapped {
 | 
				
			||||||
 | 
								// Remove leading spaces from line.
 | 
				
			||||||
 | 
								line = strings.TrimLeftFunc(line, unicode.IsSpace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Add this string to the full line.
 | 
				
			||||||
 | 
								fullLine += line
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If the full line has data, parse it.
 | 
				
			||||||
 | 
							if fullLine != "" {
 | 
				
			||||||
 | 
								// Verify we can parse this line.
 | 
				
			||||||
 | 
								if !parseRx.MatchString(fullLine) {
 | 
				
			||||||
 | 
									log.Println("Unable to parse ldif line:", fullLine)
 | 
				
			||||||
 | 
									fullLine = line
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Parse line.
 | 
				
			||||||
 | 
								match := parseRx.FindStringSubmatch(fullLine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If is a new entry, append the entry.
 | 
				
			||||||
 | 
								if match[1] == "dn" {
 | 
				
			||||||
 | 
									if dn != "" {
 | 
				
			||||||
 | 
										entry := &ldapEntry{
 | 
				
			||||||
 | 
											dn:         dn,
 | 
				
			||||||
 | 
											attributes: attributes,
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										res = append(res, entry)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									// Clear attributes and change the DN to the newly discovered entry.
 | 
				
			||||||
 | 
									attributes = make(map[string][]string)
 | 
				
			||||||
 | 
									dn = match[2]
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									// This is an attribute, lets add it.
 | 
				
			||||||
 | 
									_, ok := attributes[match[1]]
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										attributes[match[1]] = []string{
 | 
				
			||||||
 | 
											match[2],
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										attributes[match[1]] = append(attributes[match[1]], match[2])
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// This is a new line, could have additional lines to add with LDIF wrapping.
 | 
				
			||||||
 | 
							fullLine = line
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the full line has data, parse it.
 | 
				
			||||||
 | 
						if fullLine != "" {
 | 
				
			||||||
 | 
							// Verify we can parse this line.
 | 
				
			||||||
 | 
							if !parseRx.MatchString(fullLine) {
 | 
				
			||||||
 | 
								log.Println("Unable to parse ldif line:", fullLine)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Parse line.
 | 
				
			||||||
 | 
							match := parseRx.FindStringSubmatch(fullLine)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If is a new entry, append the entry.
 | 
				
			||||||
 | 
							if match[1] != "dn" {
 | 
				
			||||||
 | 
								// This is an attribute, lets add it.
 | 
				
			||||||
 | 
								_, ok := attributes[match[1]]
 | 
				
			||||||
 | 
								if !ok {
 | 
				
			||||||
 | 
									attributes[match[1]] = []string{
 | 
				
			||||||
 | 
										match[2],
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									attributes[match[1]] = append(attributes[match[1]], match[2])
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// As this is the end, we need to create the last decoded entry.
 | 
				
			||||||
 | 
						if dn != "" {
 | 
				
			||||||
 | 
							entry := &ldapEntry{
 | 
				
			||||||
 | 
								dn:         dn,
 | 
				
			||||||
 | 
								attributes: attributes,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							res = append(res, entry)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ldapPort = 10389
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// LDAP test server.
 | 
				
			||||||
 | 
					type LDAPTestServer struct {
 | 
				
			||||||
 | 
						server    *gldap.Server
 | 
				
			||||||
 | 
						responses map[string]string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewLDAPTestServer() *LDAPTestServer {
 | 
				
			||||||
 | 
						s := new(LDAPTestServer)
 | 
				
			||||||
 | 
						// Requested DN to response ldif file map.
 | 
				
			||||||
 | 
						s.responses = map[string]string{
 | 
				
			||||||
 | 
							"cn=users,cn=accounts,dc=example,dc=com":                         "test/ldap_user_sub.ldif",
 | 
				
			||||||
 | 
							"cn=staged users,cn=accounts,cn=provisioning,dc=example,dc=com":  "test/ldap_stagged_sub.ldif",
 | 
				
			||||||
 | 
							"cn=deleted users,cn=accounts,cn=provisioning,dc=example,dc=com": "test/ldap_deleted_sub.ldif",
 | 
				
			||||||
 | 
							"cn=groups,cn=accounts,dc=example,dc=com":                        "test/ldap_groups.ldif",
 | 
				
			||||||
 | 
							"cn=computers,cn=accounts,dc=example,dc=com":                     "test/ldap_computers.ldif",
 | 
				
			||||||
 | 
							"cn=services,cn=accounts,dc=example,dc=com":                      "test/ldap_services.ldif",
 | 
				
			||||||
 | 
							"cn=ng,cn=alt,dc=example,dc=com":                                 "test/ldap_netgroups.ldif",
 | 
				
			||||||
 | 
							"cn=hostgroups,cn=accounts,dc=example,dc=com":                    "test/ldap_hostgroups.ldif",
 | 
				
			||||||
 | 
							"cn=hbac,dc=example,dc=com":                                      "test/ldap_hbac.ldif",
 | 
				
			||||||
 | 
							"cn=sudorules,cn=sudo,dc=example,dc=com":                         "test/ldap_sudo.ldif",
 | 
				
			||||||
 | 
							"cn=masters,cn=ipa,cn=etc,dc=example,dc=com":                     "test/ldap_masters.ldif",
 | 
				
			||||||
 | 
							"cn=mapping tree,cn=config":                                      "test/ldap_mapping_tree.ldif",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Simple ldap bind to verify authentication with the ldap metrics work.
 | 
				
			||||||
 | 
					func (s *LDAPTestServer) bindHandler(w *gldap.ResponseWriter, r *gldap.Request) {
 | 
				
			||||||
 | 
						// Setup invalid response which will be sent unless the response code is changed by a successful login.
 | 
				
			||||||
 | 
						resp := r.NewBindResponse(
 | 
				
			||||||
 | 
							gldap.WithResponseCode(gldap.ResultInvalidCredentials),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						// Send response at the end of the function call.
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							w.Write(resp)
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Decode bind message from request.
 | 
				
			||||||
 | 
						m, err := r.GetSimpleBindMessage()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("not a simple bind message: %s", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If credentials match config, return success.
 | 
				
			||||||
 | 
						if m.UserName == app.config.LDAP.BindDN && string(m.Password) == app.config.LDAP.BindPassword {
 | 
				
			||||||
 | 
							resp.SetResultCode(gldap.ResultSuccess)
 | 
				
			||||||
 | 
							log.Println("bind success")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Write LDIF entries from file to LDAP request.
 | 
				
			||||||
 | 
					func (s *LDAPTestServer) writeLdif(w *gldap.ResponseWriter, r *gldap.Request, ldifPath string) {
 | 
				
			||||||
 | 
						// Parse entries.
 | 
				
			||||||
 | 
						entries := ParseLDIF(ldifPath)
 | 
				
			||||||
 | 
						// For each entry, write it to the request.
 | 
				
			||||||
 | 
						for _, entry := range entries {
 | 
				
			||||||
 | 
							// Print debug info.
 | 
				
			||||||
 | 
							// entry.Print()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Make a response entry for this request and write it.
 | 
				
			||||||
 | 
							w.Write(r.NewSearchResponseEntry(entry.dn, gldap.WithAttributes(entry.attributes)))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Handle LDAP search requests.
 | 
				
			||||||
 | 
					func (s *LDAPTestServer) searchHandler(w *gldap.ResponseWriter, r *gldap.Request) {
 | 
				
			||||||
 | 
						// Setup general response.
 | 
				
			||||||
 | 
						resp := r.NewSearchDoneResponse()
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							w.Write(resp)
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get message from request.
 | 
				
			||||||
 | 
						m, err := r.GetSearchMessage()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("not a search message: %s", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Print debug info.
 | 
				
			||||||
 | 
						// log.Printf("search base dn: %s", m.BaseDN)
 | 
				
			||||||
 | 
						// log.Printf("search scope: %d", m.Scope)
 | 
				
			||||||
 | 
						// log.Printf("search filter: %s", m.Filter)
 | 
				
			||||||
 | 
						// log.Printf("search attributes: %v", m.Attributes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send test ldif response based on request DN.
 | 
				
			||||||
 | 
						ldifFile, ok := s.responses[m.BaseDN]
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							s.writeLdif(w, r, ldifFile)
 | 
				
			||||||
 | 
							resp.SetResultCode(gldap.ResultSuccess)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper to start and wait for server to be running.
 | 
				
			||||||
 | 
					func (s *LDAPTestServer) Run() {
 | 
				
			||||||
 | 
						go s.Start()
 | 
				
			||||||
 | 
						for s.server == nil || !s.server.Ready() {
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Helper to stop LDAP server.
 | 
				
			||||||
 | 
					func (s *LDAPTestServer) Stop() {
 | 
				
			||||||
 | 
						if s.server != nil {
 | 
				
			||||||
 | 
							s.server.Stop()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Setup LDAP test server for verifying metrics.
 | 
				
			||||||
 | 
					func (s *LDAPTestServer) Start() {
 | 
				
			||||||
 | 
						server, err := gldap.NewServer()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("unable to create server: %s", err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Set global variable for test function access.
 | 
				
			||||||
 | 
						s.server = server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// create a router and add a bind handler
 | 
				
			||||||
 | 
						r, err := gldap.NewMux()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf("unable to create router: %s", err.Error())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r.Bind(s.bindHandler)
 | 
				
			||||||
 | 
						r.Search(s.searchHandler)
 | 
				
			||||||
 | 
						server.Router(r)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run the LDAP test server.
 | 
				
			||||||
 | 
						server.Run(fmt.Sprintf("127.0.0.1:%d", ldapPort))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Main LDAP test function that verifies metrics for LDAP works.
 | 
				
			||||||
 | 
					func TestLdap(t *testing.T) {
 | 
				
			||||||
 | 
						// Setup configs.
 | 
				
			||||||
 | 
						setupLdapTestApp()
 | 
				
			||||||
 | 
						// Run the LDAP test server.
 | 
				
			||||||
 | 
						server := NewLDAPTestServer()
 | 
				
			||||||
 | 
						server.Run()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Open the expected prometheus metrics.
 | 
				
			||||||
 | 
						expected, err := os.Open("test/ldap.metrics")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Error opening tests:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer expected.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test the LDAP exporter and verify metrics match what's expected.
 | 
				
			||||||
 | 
						err = testutil.CollectAndCompare(app.ldapExporter, expected)
 | 
				
			||||||
 | 
						// If results are not as expected, fail test with the error.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Unexpected metrics returned:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Remove all responses from ldap server to cause failure in all metrics.
 | 
				
			||||||
 | 
						server.responses = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Open the expected prometheus metrics.
 | 
				
			||||||
 | 
						expected, err = os.Open("test/ldap_fail.metrics")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Error opening tests:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer expected.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test the LDAP exporter and verify metrics match what's expected.
 | 
				
			||||||
 | 
						err = testutil.CollectAndCompare(app.ldapExporter, expected)
 | 
				
			||||||
 | 
						// If results are not as expected, fail test with the error.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Unexpected metrics returned:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test failure to connect.
 | 
				
			||||||
 | 
						app.config.LDAP.Address = "bad-address"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Open the expected prometheus metrics.
 | 
				
			||||||
 | 
						expected, err = os.Open("test/ldap_fail_connect.metrics")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Error opening tests:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer expected.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Test the LDAP exporter and verify metrics match what's expected.
 | 
				
			||||||
 | 
						err = testutil.CollectAndCompare(app.ldapExporter, expected)
 | 
				
			||||||
 | 
						// If results are not as expected, fail test with the error.
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal("Unexpected metrics returned:", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// We're done, let's stop serving the test LDAP server.
 | 
				
			||||||
 | 
						server.Stop()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										137
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,137 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"os/signal"
 | 
				
			||||||
 | 
						"syscall"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/prometheus/client_golang/prometheus"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Basic application info.
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						serviceName        = "freeipa-health-metrics"
 | 
				
			||||||
 | 
						serviceDescription = "Provides metrics of FreeIPA's health"
 | 
				
			||||||
 | 
						serviceVersion     = "0.1"
 | 
				
			||||||
 | 
						namespace          = "freeipa"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The standard prometheus metric info structure which includes the description, type,
 | 
				
			||||||
 | 
					// and a sub function to collect the value.
 | 
				
			||||||
 | 
					type metricInfo struct {
 | 
				
			||||||
 | 
						Desc  *prometheus.Desc
 | 
				
			||||||
 | 
						Type  prometheus.ValueType
 | 
				
			||||||
 | 
						Value func() (float64, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The global application structure used to access diffent state structures and configuration.
 | 
				
			||||||
 | 
					type App struct {
 | 
				
			||||||
 | 
						flags           *Flags
 | 
				
			||||||
 | 
						config          *Config
 | 
				
			||||||
 | 
						registry        *prometheus.Registry
 | 
				
			||||||
 | 
						ldapExporter    *LDAPExporter
 | 
				
			||||||
 | 
						freeIPAExporter *FreeIPAExporter
 | 
				
			||||||
 | 
						httpOutput      *HTTPOutput
 | 
				
			||||||
 | 
						influxOutput    *InfluxOutput
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Global variable for the app structure to make it easy to get the active state.
 | 
				
			||||||
 | 
					var app *App
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The main program function/run loop.
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						// Setup the app structure.
 | 
				
			||||||
 | 
						app = new(App)
 | 
				
			||||||
 | 
						app.ParseFlags()
 | 
				
			||||||
 | 
						app.ReadConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load exporters.
 | 
				
			||||||
 | 
						app.ldapExporter = NewLDAPExporter()
 | 
				
			||||||
 | 
						app.freeIPAExporter = NewFreeIPAExporter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add exporters to registry.
 | 
				
			||||||
 | 
						reg := prometheus.NewPedanticRegistry()
 | 
				
			||||||
 | 
						reg.Register(app.ldapExporter)
 | 
				
			||||||
 | 
						reg.Register(app.freeIPAExporter)
 | 
				
			||||||
 | 
						app.registry = reg
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Load outputs.
 | 
				
			||||||
 | 
						app.httpOutput = NewHTTPOutput()
 | 
				
			||||||
 | 
						app.influxOutput = NewInfluxOutput()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If requested telegraf output.
 | 
				
			||||||
 | 
						if app.flags.TelegrafOutput {
 | 
				
			||||||
 | 
							// Get metrics in influx line protocol format.
 | 
				
			||||||
 | 
							data, err := app.influxOutput.CollectAndLineprotocolFormat()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								log.Fatalln("Error collecting metrics for telegraf:", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Print the encoded data.
 | 
				
			||||||
 | 
							fmt.Println(string(data))
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setup context with cancellation function to allow background services to gracefully stop.
 | 
				
			||||||
 | 
						ctx, ctxCancel := context.WithCancel(context.Background())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Start http output server.
 | 
				
			||||||
 | 
						go app.httpOutput.Start(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Start the influx output schedule.
 | 
				
			||||||
 | 
						go app.influxOutput.Start(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Monitor common signals.
 | 
				
			||||||
 | 
						c := make(chan os.Signal, 1)
 | 
				
			||||||
 | 
						signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Run program until cancelled.
 | 
				
			||||||
 | 
						for sig := range c {
 | 
				
			||||||
 | 
							switch sig {
 | 
				
			||||||
 | 
							// If hangup signal receivied, reload the configurations.
 | 
				
			||||||
 | 
							case syscall.SIGHUP:
 | 
				
			||||||
 | 
								log.Println("Reloading configurations")
 | 
				
			||||||
 | 
								// Capture old config for checks.
 | 
				
			||||||
 | 
								oldConfig := app.config
 | 
				
			||||||
 | 
								// Get prior state of influx output.
 | 
				
			||||||
 | 
								influxOutputWasEnabled := app.influxOutput.OutputEnabled()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Read new config.
 | 
				
			||||||
 | 
								app.ReadConfig()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Reload config on each exporter and output.
 | 
				
			||||||
 | 
								app.ldapExporter.Reload()
 | 
				
			||||||
 | 
								app.freeIPAExporter.Reload()
 | 
				
			||||||
 | 
								app.httpOutput.Reload()
 | 
				
			||||||
 | 
								app.influxOutput.Reload()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Check if httpd server config changes require restart.
 | 
				
			||||||
 | 
								httpNeedsRestart := oldConfig.HTTP.BindAddr != app.config.HTTP.BindAddr || oldConfig.HTTP.Port != app.config.HTTP.Port || oldConfig.HTTP.Enabled != app.config.HTTP.Enabled
 | 
				
			||||||
 | 
								// Check if influx output config changes require restart.
 | 
				
			||||||
 | 
								influxNeedsRestart := app.influxOutput.OutputEnabled() != influxOutputWasEnabled || oldConfig.Influx.Frequency != app.config.Influx.Frequency
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// If either output service requires restart, restart both.
 | 
				
			||||||
 | 
								if httpNeedsRestart || influxNeedsRestart {
 | 
				
			||||||
 | 
									// Cancel prior background context.
 | 
				
			||||||
 | 
									ctxCancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Setup new background context.
 | 
				
			||||||
 | 
									ctx, ctxCancel = context.WithCancel(context.Background())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Start http output server.
 | 
				
			||||||
 | 
									go app.httpOutput.Start(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Start the influx output schedule.
 | 
				
			||||||
 | 
									go app.influxOutput.Start(ctx)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The default signal is either termination or interruption, so cancel the
 | 
				
			||||||
 | 
							// background context and exit this program.
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								ctxCancel()
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										131
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								readme.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					# freeipa-health-metrics
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A prometheus/influxdb exporter for FreeIPA metrics to provide indication of cluster health.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Requirements:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- FreeIPA 4 or later
 | 
				
			||||||
 | 
					- Golang 1.20 or later
 | 
				
			||||||
 | 
					- FreeIPA user with admin privileges
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You can install either by downloading the latest binary release or by building.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Building
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Building should be as simple as running:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					go build
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Running as a service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					You are likely going to want to run the exporter as a service to ensure it runs at boot and restarts in case of failures. Below is an example service config file you can place in `/etc/systemd/system/freeipa-health-metrics.service` on a linux system to run as a service if you install the binary in `/usr/local/bin/`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```systemd
 | 
				
			||||||
 | 
					[Unit]
 | 
				
			||||||
 | 
					Description=FreeIPA Health Metrics
 | 
				
			||||||
 | 
					After=network.target
 | 
				
			||||||
 | 
					StartLimitIntervalSec=500
 | 
				
			||||||
 | 
					StartLimitBurst=5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Service]
 | 
				
			||||||
 | 
					ExecStart=/usr/local/bin/freeipa-health-metrics
 | 
				
			||||||
 | 
					ExecReload=/bin/kill -s HUP $MAINPID
 | 
				
			||||||
 | 
					Restart=on-failure
 | 
				
			||||||
 | 
					RestartSec=5s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[Install]
 | 
				
			||||||
 | 
					WantedBy=multi-user.target
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Once the service file is installed, you can run the following to start it:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					systemctl daemon-reload
 | 
				
			||||||
 | 
					systemctl start freeipa-health-metrics.service
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The default configuration paths are:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- `./config.yaml` - A file in the current working directory.
 | 
				
			||||||
 | 
					- `~/.config/freeipa-health-metrics/config.yaml` - A file in your home directory's config path.
 | 
				
			||||||
 | 
					- `/etc/ipa/freeipa-health-metrics.yaml` - A file in the IPA config folder.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### For local monitoring
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					ldap:
 | 
				
			||||||
 | 
					  insecure_skip_verify: true
 | 
				
			||||||
 | 
					  connect_method: Secure
 | 
				
			||||||
 | 
					  base_dn: dc=example,dc=com
 | 
				
			||||||
 | 
					  bind_dn: uid=freeipa-health-metrics,cn=users,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					  bind_password: PASSWORD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					freeipa:
 | 
				
			||||||
 | 
					  krb5_realm: EXAMPLE.COM
 | 
				
			||||||
 | 
					  insecure_skip_verify: true
 | 
				
			||||||
 | 
					  username: freeipa-health-metrics
 | 
				
			||||||
 | 
					  password: PASSWORD
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### For remote monitoring
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					hostname: ipa1.example.com
 | 
				
			||||||
 | 
					ldap:
 | 
				
			||||||
 | 
					  insecure_skip_verify: true
 | 
				
			||||||
 | 
					  connect_method: Secure
 | 
				
			||||||
 | 
					  base_dn: dc=example,dc=com
 | 
				
			||||||
 | 
					  bind_dn: uid=freeipa-health-metrics,cn=users,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					  bind_password: PASSWORD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					freeipa:
 | 
				
			||||||
 | 
					  krb5_realm: EXAMPLE.COM
 | 
				
			||||||
 | 
					  insecure_skip_verify: true
 | 
				
			||||||
 | 
					  username: freeipa-health-metrics
 | 
				
			||||||
 | 
					  password: PASSWORD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Disable metrics which only work locally.
 | 
				
			||||||
 | 
					  disabled_metrics:
 | 
				
			||||||
 | 
					    - krb5_auth
 | 
				
			||||||
 | 
					    - krb5_workers
 | 
				
			||||||
 | 
					    - proxy_secret
 | 
				
			||||||
 | 
					    - group_members
 | 
				
			||||||
 | 
					    - ipa_cert_auto_renew
 | 
				
			||||||
 | 
					    - ldap_cert_auto_renew
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Output to InfluxDB only
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```yaml
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					ldap:
 | 
				
			||||||
 | 
					  insecure_skip_verify: true
 | 
				
			||||||
 | 
					  connect_method: Secure
 | 
				
			||||||
 | 
					  base_dn: dc=example,dc=com
 | 
				
			||||||
 | 
					  bind_dn: uid=freeipa-health-metrics,cn=users,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					  bind_password: PASSWORD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					freeipa:
 | 
				
			||||||
 | 
					  krb5_realm: EXAMPLE.COM
 | 
				
			||||||
 | 
					  insecure_skip_verify: true
 | 
				
			||||||
 | 
					  username: freeipa-health-metrics
 | 
				
			||||||
 | 
					  password: PASSWORD
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					influx_output:
 | 
				
			||||||
 | 
					  frequency: 5m
 | 
				
			||||||
 | 
					  influx_server: http://example.com:8086
 | 
				
			||||||
 | 
					  token: INFLUX_TOKEN
 | 
				
			||||||
 | 
					  org: company
 | 
				
			||||||
 | 
					  bucket: freeipa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					http:
 | 
				
			||||||
 | 
					  enabled: false
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										28
									
								
								test/cert.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								test/cert.pem
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					-----BEGIN CERTIFICATE-----
 | 
				
			||||||
 | 
					MIIE0DCCArgCCQCTUB7qC+C7MDANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDDB5p
 | 
				
			||||||
 | 
					cGExLmV4YW1wbGUuY29tLE89RVhBTVBMRS5DT00wIBcNMjMwODMwMTcxNDA0WhgP
 | 
				
			||||||
 | 
					MjA1MzA4MjIxNzE0MDRaMCkxJzAlBgNVBAMMHmlwYTEuZXhhbXBsZS5jb20sTz1F
 | 
				
			||||||
 | 
					WEFNUExFLkNPTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMRg/nBP
 | 
				
			||||||
 | 
					pAT/jQExqS9icLDkQwmTMX745VWKxLFGfIox2CnA5d7qvFKaeaZJ10qy3utIrTj8
 | 
				
			||||||
 | 
					eEfA8Yhrn5Kwu7TUWmuVGi7I7hBFqXG1Qtp1SwGDw1r8RYLuwiByaCU/lS7KBpd2
 | 
				
			||||||
 | 
					r7gGo0I0KBvE/HCd9pJ4ABv+ZpJgBBgvXJGrVISV8UV0W85cMwDrrNOBrbENIqG5
 | 
				
			||||||
 | 
					383Gf3qlNgXL68tQBWRD5U58VlpYx4ETV2sY6ctWRK3VMW0WVM3lg6/IqNhRCwri
 | 
				
			||||||
 | 
					XvNyoX+KNC56sfGdAy9nLXjd5bgowSiC+0whH4qW2dS+FIIscfFgnAefN42Z84Co
 | 
				
			||||||
 | 
					CeYzcdwvp47almZe+iLQFgZCEwTCG3CxhA2C02pu2krjCpolSPZYNT6qgIhULXcE
 | 
				
			||||||
 | 
					VB1BKrno0Zkz9jac+1ffd/gD2F8ufGoRZHgOwMdP6EpHN2dKr9pj+JD1NANjSAfN
 | 
				
			||||||
 | 
					495VTS2D5681zu7qvDpmqHR6Jj9XaLflpIQlN/ULGpV0zw7pCejErq6YkqA4ES3p
 | 
				
			||||||
 | 
					1Nr5rnT7U2oz25++TMhlRzkcacVQGy8x6vuXpRA2rVJMH7jDfee1CZu2+C82cZeZ
 | 
				
			||||||
 | 
					eAHn1MYGMtz/6RUEEMuf5HfLMbiYuqktTKRfxxFxO6XpEiE3EyI0jTZjXmQUwn4w
 | 
				
			||||||
 | 
					4fKMcpgpiRLVnIrA9FCQhKJe5kA1pP69gIsfAgMBAAEwDQYJKoZIhvcNAQELBQAD
 | 
				
			||||||
 | 
					ggIBAMMmZoEvF4/koymO9aTBmLMMgZphXyss5ng7MDAFntyG/z+zBpVuzfPb1mhF
 | 
				
			||||||
 | 
					Nkege5qhMUfIUmMsuBCTdfVn1OlhwHrTNIggmEvWHeucniA2WLZy7HIucPqP+mNN
 | 
				
			||||||
 | 
					XVFE7PM9XVS9CayBU/k44/7sxJbxH1YK2gUYsAliBJKup73+jgKslqh0ISW0AhJS
 | 
				
			||||||
 | 
					4JFR02z0FLypyFcUmnRj7fatGsAr8NRajPDVaDE+F5VISwYuBzNGzgefj1O+xnlL
 | 
				
			||||||
 | 
					bWfRn95GKmMlA4k15JXwjSDV1nZqgh1JAw9Alp2scAZwMnoYoQhj882nkxLkNc9+
 | 
				
			||||||
 | 
					uKApC6aNYgJEjrR/CUTDLagqcSkzETHUhVyMpXPDGWsp+u9jp7jEYIPhx4rbsAv/
 | 
				
			||||||
 | 
					sCl3dZpB9IKjZUOprq4b1T/qF7rohUtrbawwYpY8ai5o+VlSeLVT7+j194JdZuSc
 | 
				
			||||||
 | 
					ROjlzw/47VVMlGdwYEYr1caTutFmVxnUnlaPrn27OnfIGEIKrE1WncveBgRNJ8WF
 | 
				
			||||||
 | 
					eG0WObRTP+RRhraR7tzigvWx9Yhjyk8tSyUbzvICmlnqWKewet4ZwCZ/sw9r0Kuk
 | 
				
			||||||
 | 
					TIC0/iPzd1EKxMNf3eoHw0xhumXbNAniXSsXSXAUtDltnZfCtzvcEQL1r8uOl4bK
 | 
				
			||||||
 | 
					JXfbb0+HmAw+PWWuNQm1g4MCPTEH5e/ij5WyGgDm36vu3HEm
 | 
				
			||||||
 | 
					-----END CERTIFICATE-----
 | 
				
			||||||
							
								
								
									
										45
									
								
								test/freeipa.metrics
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								test/freeipa.metrics
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					# HELP freeipa_config_dna_range DNA range is defined.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_dna_range gauge
 | 
				
			||||||
 | 
					freeipa_config_dna_range 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_ca_issued_cert The FreeIPA API was issued a certificate by the CA cert.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_ca_issued_cert gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_ca_issued_cert 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_ca_issued_ldap_cert The LDAP cert was issued a certificate by the CA cert.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_ca_issued_ldap_cert gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_ca_issued_ldap_cert 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_cert_auto_renew The FreeIPA API certificate is managed and set to auto renew.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_cert_auto_renew gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_cert_auto_renew 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_earliest_cert_expiry The earliest certificate expiry date for FreeIPA API.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_earliest_cert_expiry gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_earliest_cert_expiry 2.639495644e+09
 | 
				
			||||||
 | 
					# HELP freeipa_config_krb5_auth Kerberos can authenticate.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_krb5_auth gauge
 | 
				
			||||||
 | 
					freeipa_config_krb5_auth 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_krb5_workers Workers match processors.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_krb5_workers gauge
 | 
				
			||||||
 | 
					freeipa_config_krb5_workers 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_ldap_cert_auto_renew The LDAP certificate is managed and set to auto renew.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ldap_cert_auto_renew gauge
 | 
				
			||||||
 | 
					freeipa_config_ldap_cert_auto_renew 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ldap_earliest_cert_expiry The earliest certificate expiry date for LDAP.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ldap_earliest_cert_expiry gauge
 | 
				
			||||||
 | 
					freeipa_config_ldap_earliest_cert_expiry 2.639495644e+09
 | 
				
			||||||
 | 
					# HELP freeipa_config_proxy_secret Proxy secret is configured.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_proxy_secret gauge
 | 
				
			||||||
 | 
					freeipa_config_proxy_secret 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_renewal_master This server is the renewal master.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_renewal_master gauge
 | 
				
			||||||
 | 
					freeipa_config_renewal_master 1
 | 
				
			||||||
 | 
					# HELP freeipa_failures_total Number of errors while scapping metrics.
 | 
				
			||||||
 | 
					# TYPE freeipa_failures_total counter
 | 
				
			||||||
 | 
					freeipa_failures_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_freeipa_failed_tests Number of failed tests in the most recent scrape.
 | 
				
			||||||
 | 
					# TYPE freeipa_freeipa_failed_tests gauge
 | 
				
			||||||
 | 
					freeipa_freeipa_failed_tests 1
 | 
				
			||||||
 | 
					# HELP freeipa_scrapes_total Current total HAProxy scrapes.
 | 
				
			||||||
 | 
					# TYPE freeipa_scrapes_total counter
 | 
				
			||||||
 | 
					freeipa_scrapes_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_up Was the last scrape of FreeIPA successful.
 | 
				
			||||||
 | 
					# TYPE freeipa_up gauge
 | 
				
			||||||
 | 
					freeipa_up 1
 | 
				
			||||||
							
								
								
									
										11
									
								
								test/freeipa_ca_is_enabled.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/freeipa_ca_is_enabled.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"result": {
 | 
				
			||||||
 | 
							"result": true,
 | 
				
			||||||
 | 
							"value": null,
 | 
				
			||||||
 | 
							"summary": null
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"version": "4.6.8",
 | 
				
			||||||
 | 
						"error": null,
 | 
				
			||||||
 | 
						"id": null,
 | 
				
			||||||
 | 
						"principal": "freeipa-health-metrics@EXAMPLE.COM"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										29
									
								
								test/freeipa_ca_show.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								test/freeipa_ca_show.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"result": {
 | 
				
			||||||
 | 
							"result": {
 | 
				
			||||||
 | 
								"dn": "cn=ipa,cn=cas,cn=ca,dc=example,dc=com",
 | 
				
			||||||
 | 
								"certificate": "MIIE0DCCArgCCQCTUB7qC+C7MDANBgkqhkiG9w0BAQsFADApMScwJQYDVQQDDB5pcGExLmV4YW1wbGUuY29tLE89RVhBTVBMRS5DT00wIBcNMjMwODMwMTcxNDA0WhgPMjA1MzA4MjIxNzE0MDRaMCkxJzAlBgNVBAMMHmlwYTEuZXhhbXBsZS5jb20sTz1FWEFNUExFLkNPTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMRg/nBPpAT/jQExqS9icLDkQwmTMX745VWKxLFGfIox2CnA5d7qvFKaeaZJ10qy3utIrTj8eEfA8Yhrn5Kwu7TUWmuVGi7I7hBFqXG1Qtp1SwGDw1r8RYLuwiByaCU/lS7KBpd2r7gGo0I0KBvE/HCd9pJ4ABv+ZpJgBBgvXJGrVISV8UV0W85cMwDrrNOBrbENIqG5383Gf3qlNgXL68tQBWRD5U58VlpYx4ETV2sY6ctWRK3VMW0WVM3lg6/IqNhRCwriXvNyoX+KNC56sfGdAy9nLXjd5bgowSiC+0whH4qW2dS+FIIscfFgnAefN42Z84CoCeYzcdwvp47almZe+iLQFgZCEwTCG3CxhA2C02pu2krjCpolSPZYNT6qgIhULXcEVB1BKrno0Zkz9jac+1ffd/gD2F8ufGoRZHgOwMdP6EpHN2dKr9pj+JD1NANjSAfN495VTS2D5681zu7qvDpmqHR6Jj9XaLflpIQlN/ULGpV0zw7pCejErq6YkqA4ES3p1Nr5rnT7U2oz25++TMhlRzkcacVQGy8x6vuXpRA2rVJMH7jDfee1CZu2+C82cZeZeAHn1MYGMtz/6RUEEMuf5HfLMbiYuqktTKRfxxFxO6XpEiE3EyI0jTZjXmQUwn4w4fKMcpgpiRLVnIrA9FCQhKJe5kA1pP69gIsfAgMBAAEwDQYJKoZIhvcNAQELBQADggIBAMMmZoEvF4/koymO9aTBmLMMgZphXyss5ng7MDAFntyG/z+zBpVuzfPb1mhFNkege5qhMUfIUmMsuBCTdfVn1OlhwHrTNIggmEvWHeucniA2WLZy7HIucPqP+mNNXVFE7PM9XVS9CayBU/k44/7sxJbxH1YK2gUYsAliBJKup73+jgKslqh0ISW0AhJS4JFR02z0FLypyFcUmnRj7fatGsAr8NRajPDVaDE+F5VISwYuBzNGzgefj1O+xnlLbWfRn95GKmMlA4k15JXwjSDV1nZqgh1JAw9Alp2scAZwMnoYoQhj882nkxLkNc9+uKApC6aNYgJEjrR/CUTDLagqcSkzETHUhVyMpXPDGWsp+u9jp7jEYIPhx4rbsAv/sCl3dZpB9IKjZUOprq4b1T/qF7rohUtrbawwYpY8ai5o+VlSeLVT7+j194JdZuScROjlzw/47VVMlGdwYEYr1caTutFmVxnUnlaPrn27OnfIGEIKrE1WncveBgRNJ8WFeG0WObRTP+RRhraR7tzigvWx9Yhjyk8tSyUbzvICmlnqWKewet4ZwCZ/sw9r0KukTIC0/iPzd1EKxMNf3eoHw0xhumXbNAniXSsXSXAUtDltnZfCtzvcEQL1r8uOl4bKJXfbb0+HmAw+PWWuNQm1g4MCPTEH5e/ij5WyGgDm36vu3HEm",
 | 
				
			||||||
 | 
								"cn": [
 | 
				
			||||||
 | 
									"ipa"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipacaissuerdn": [
 | 
				
			||||||
 | 
									"CN=Certificate Authority,O=EXAMPLE.COM"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipacasubjectdn": [
 | 
				
			||||||
 | 
									"CN=Certificate Authority,O=EXAMPLE.COM"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipacaid": [
 | 
				
			||||||
 | 
									"c5b227ea-6490-45bf-9da4-f162e6896440"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"description": [
 | 
				
			||||||
 | 
									"IPA CA"
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"value": "ipa",
 | 
				
			||||||
 | 
							"summary": null
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"version": "4.6.8",
 | 
				
			||||||
 | 
						"error": null,
 | 
				
			||||||
 | 
						"id": null,
 | 
				
			||||||
 | 
						"principal": "freeipa-health-metrics@EXAMPLE.COM"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										81
									
								
								test/freeipa_config_show.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								test/freeipa_config_show.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"result": {
 | 
				
			||||||
 | 
							"result": {
 | 
				
			||||||
 | 
								"ipausersearchfields": [
 | 
				
			||||||
 | 
									"uid,givenname,sn,telephonenumber,ou,title"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipauserauthtype": [
 | 
				
			||||||
 | 
									"otp"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ca_renewal_master_server": "ipa1.example.com",
 | 
				
			||||||
 | 
								"ipadefaultprimarygroup": [
 | 
				
			||||||
 | 
									"Example"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipapwdexpadvnotify": [
 | 
				
			||||||
 | 
									"14"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipasearchtimelimit": [
 | 
				
			||||||
 | 
									"60"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipasearchrecordslimit": [
 | 
				
			||||||
 | 
									"100"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ntp_server_server": [
 | 
				
			||||||
 | 
									"ipa1.example.com",
 | 
				
			||||||
 | 
									"ipa2.example.com",
 | 
				
			||||||
 | 
									"ipa3.example.com"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipadefaultloginshell": [
 | 
				
			||||||
 | 
									"/bin/bash"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipacertificatesubjectbase": [
 | 
				
			||||||
 | 
									"O=EXAMPLE.COM"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipa_master_server": [
 | 
				
			||||||
 | 
									"ipa1.example.com",
 | 
				
			||||||
 | 
									"ipa2.example.com",
 | 
				
			||||||
 | 
									"ipa3.example.com"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipaconfigstring": [
 | 
				
			||||||
 | 
									"KDC:Disable Last Success"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"dn": "cn=ipaConfig,cn=etc,dc=example,dc=com",
 | 
				
			||||||
 | 
								"ipakrbauthzdata": [
 | 
				
			||||||
 | 
									"MS-PAC",
 | 
				
			||||||
 | 
									"nfs:NONE"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipagroupsearchfields": [
 | 
				
			||||||
 | 
									"cn,description"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ca_server_server": [
 | 
				
			||||||
 | 
									"ipa1.example.com",
 | 
				
			||||||
 | 
									"ipa2.example.com",
 | 
				
			||||||
 | 
									"ipa3.example.com"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipamaxusernamelength": [
 | 
				
			||||||
 | 
									"32"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipaselinuxusermapdefault": [
 | 
				
			||||||
 | 
									"unconfined_u:s0-s0:c0.c1023"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipahomesrootdir": [
 | 
				
			||||||
 | 
									"/home/staff"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipamigrationenabled": [
 | 
				
			||||||
 | 
									"FALSE"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipadefaultemaildomain": [
 | 
				
			||||||
 | 
									"example.com"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"ipaselinuxusermaporder": [
 | 
				
			||||||
 | 
									"guest_u:s0$xguest_u:s0$user_u:s0$staff_u:s0-s0:c0.c1023$unconfined_u:s0-s0:c0.c1023"
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"value": null,
 | 
				
			||||||
 | 
							"summary": null
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"version": "4.6.8",
 | 
				
			||||||
 | 
						"error": null,
 | 
				
			||||||
 | 
						"id": null,
 | 
				
			||||||
 | 
						"principal": "freeipa-health-metrics@EXAMPLE.COM"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										45
									
								
								test/freeipa_fail.metrics
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								test/freeipa_fail.metrics
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					# HELP freeipa_config_dna_range DNA range is defined.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_dna_range gauge
 | 
				
			||||||
 | 
					freeipa_config_dna_range 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_ca_issued_cert The FreeIPA API was issued a certificate by the CA cert.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_ca_issued_cert gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_ca_issued_cert 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_ca_issued_ldap_cert The LDAP cert was issued a certificate by the CA cert.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_ca_issued_ldap_cert gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_ca_issued_ldap_cert 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_cert_auto_renew The FreeIPA API certificate is managed and set to auto renew.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_cert_auto_renew gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_cert_auto_renew 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_earliest_cert_expiry The earliest certificate expiry date for FreeIPA API.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_earliest_cert_expiry gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_earliest_cert_expiry 2.639495644e+09
 | 
				
			||||||
 | 
					# HELP freeipa_config_krb5_auth Kerberos can authenticate.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_krb5_auth gauge
 | 
				
			||||||
 | 
					freeipa_config_krb5_auth 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_krb5_workers Workers match processors.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_krb5_workers gauge
 | 
				
			||||||
 | 
					freeipa_config_krb5_workers 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_ldap_cert_auto_renew The LDAP certificate is managed and set to auto renew.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ldap_cert_auto_renew gauge
 | 
				
			||||||
 | 
					freeipa_config_ldap_cert_auto_renew 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ldap_earliest_cert_expiry The earliest certificate expiry date for LDAP.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ldap_earliest_cert_expiry gauge
 | 
				
			||||||
 | 
					freeipa_config_ldap_earliest_cert_expiry 2.639495644e+09
 | 
				
			||||||
 | 
					# HELP freeipa_config_proxy_secret Proxy secret is configured.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_proxy_secret gauge
 | 
				
			||||||
 | 
					freeipa_config_proxy_secret 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_renewal_master This server is the renewal master.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_renewal_master gauge
 | 
				
			||||||
 | 
					freeipa_config_renewal_master 0
 | 
				
			||||||
 | 
					# HELP freeipa_failures_total Number of errors while scapping metrics.
 | 
				
			||||||
 | 
					# TYPE freeipa_failures_total counter
 | 
				
			||||||
 | 
					freeipa_failures_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_freeipa_failed_tests Number of failed tests in the most recent scrape.
 | 
				
			||||||
 | 
					# TYPE freeipa_freeipa_failed_tests gauge
 | 
				
			||||||
 | 
					freeipa_freeipa_failed_tests 2
 | 
				
			||||||
 | 
					# HELP freeipa_scrapes_total Current total HAProxy scrapes.
 | 
				
			||||||
 | 
					# TYPE freeipa_scrapes_total counter
 | 
				
			||||||
 | 
					freeipa_scrapes_total 2
 | 
				
			||||||
 | 
					# HELP freeipa_up Was the last scrape of FreeIPA successful.
 | 
				
			||||||
 | 
					# TYPE freeipa_up gauge
 | 
				
			||||||
 | 
					freeipa_up 1
 | 
				
			||||||
							
								
								
									
										12
									
								
								test/freeipa_fail_connect.metrics
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								test/freeipa_fail_connect.metrics
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					# HELP freeipa_failures_total Number of errors while scapping metrics.
 | 
				
			||||||
 | 
					# TYPE freeipa_failures_total counter
 | 
				
			||||||
 | 
					freeipa_failures_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_freeipa_failed_tests Number of failed tests in the most recent scrape.
 | 
				
			||||||
 | 
					# TYPE freeipa_freeipa_failed_tests gauge
 | 
				
			||||||
 | 
					freeipa_freeipa_failed_tests 0
 | 
				
			||||||
 | 
					# HELP freeipa_scrapes_total Current total HAProxy scrapes.
 | 
				
			||||||
 | 
					# TYPE freeipa_scrapes_total counter
 | 
				
			||||||
 | 
					freeipa_scrapes_total 3
 | 
				
			||||||
 | 
					# HELP freeipa_up Was the last scrape of FreeIPA successful.
 | 
				
			||||||
 | 
					# TYPE freeipa_up gauge
 | 
				
			||||||
 | 
					freeipa_up 0
 | 
				
			||||||
							
								
								
									
										31
									
								
								test/freeipa_idrange_find.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								test/freeipa_idrange_find.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"result": {
 | 
				
			||||||
 | 
							"count": 1,
 | 
				
			||||||
 | 
							"truncated": false,
 | 
				
			||||||
 | 
							"result": [
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									"dn": "cn=EXAMPLE.COM_id_range,cn=ranges,cn=etc,dc=example,dc=com",
 | 
				
			||||||
 | 
									"iparangetype": [
 | 
				
			||||||
 | 
										"local domain range"
 | 
				
			||||||
 | 
									],
 | 
				
			||||||
 | 
									"cn": [
 | 
				
			||||||
 | 
										"EXAMPLE.COM_id_range"
 | 
				
			||||||
 | 
									],
 | 
				
			||||||
 | 
									"ipabaseid": [
 | 
				
			||||||
 | 
										"431800000"
 | 
				
			||||||
 | 
									],
 | 
				
			||||||
 | 
									"iparangetyperaw": [
 | 
				
			||||||
 | 
										"ipa-local"
 | 
				
			||||||
 | 
									],
 | 
				
			||||||
 | 
									"ipaidrangesize": [
 | 
				
			||||||
 | 
										"200000"
 | 
				
			||||||
 | 
									]
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							],
 | 
				
			||||||
 | 
							"summary": "1 range matched"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"version": "4.6.8",
 | 
				
			||||||
 | 
						"error": null,
 | 
				
			||||||
 | 
						"id": null,
 | 
				
			||||||
 | 
						"principal": "freeipa-health-metrics@EXAMPLE.COM"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								test/freeipa_invalid_json.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								test/freeipa_invalid_json.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"result": null,
 | 
				
			||||||
 | 
						"version": "4.6.8",
 | 
				
			||||||
 | 
						"error": {
 | 
				
			||||||
 | 
							"message": "Invalid JSON-RPC request: Expecting property name enclosed in double quotes: line 4 column 4 (char 44)",
 | 
				
			||||||
 | 
							"code": 909,
 | 
				
			||||||
 | 
							"data": {
 | 
				
			||||||
 | 
								"error": "Expecting property name enclosed in double quotes: line 4 column 4 (char 44)"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"name": "JSONError"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"id": null,
 | 
				
			||||||
 | 
						"principal": "admin@EXAMPLE.COM"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										104
									
								
								test/http.metrics
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								test/http.metrics
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,104 @@
 | 
				
			|||||||
 | 
					# HELP freeipa_config_dna_range DNA range is defined.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_dna_range gauge
 | 
				
			||||||
 | 
					freeipa_config_dna_range 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_ca_issued_cert The FreeIPA API was issued a certificate by the CA cert.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_ca_issued_cert gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_ca_issued_cert 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_ca_issued_ldap_cert The LDAP cert was issued a certificate by the CA cert.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_ca_issued_ldap_cert gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_ca_issued_ldap_cert 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_cert_auto_renew The FreeIPA API certificate is managed and set to auto renew.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_cert_auto_renew gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_cert_auto_renew 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_ipa_earliest_cert_expiry The earliest certificate expiry date for FreeIPA API.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ipa_earliest_cert_expiry gauge
 | 
				
			||||||
 | 
					freeipa_config_ipa_earliest_cert_expiry 2.639495644e+09
 | 
				
			||||||
 | 
					# HELP freeipa_config_krb5_auth Kerberos can authenticate.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_krb5_auth gauge
 | 
				
			||||||
 | 
					freeipa_config_krb5_auth 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_krb5_workers Workers match processors.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_krb5_workers gauge
 | 
				
			||||||
 | 
					freeipa_config_krb5_workers 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_ldap_cert_auto_renew The LDAP certificate is managed and set to auto renew.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ldap_cert_auto_renew gauge
 | 
				
			||||||
 | 
					freeipa_config_ldap_cert_auto_renew 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_ldap_earliest_cert_expiry The earliest certificate expiry date for LDAP.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_ldap_earliest_cert_expiry gauge
 | 
				
			||||||
 | 
					freeipa_config_ldap_earliest_cert_expiry 0
 | 
				
			||||||
 | 
					# HELP freeipa_config_proxy_secret Proxy secret is configured.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_proxy_secret gauge
 | 
				
			||||||
 | 
					freeipa_config_proxy_secret 1
 | 
				
			||||||
 | 
					# HELP freeipa_config_renewal_master This server is the renewal master.
 | 
				
			||||||
 | 
					# TYPE freeipa_config_renewal_master gauge
 | 
				
			||||||
 | 
					freeipa_config_renewal_master 1
 | 
				
			||||||
 | 
					# HELP freeipa_failures_total Number of errors while scapping metrics.
 | 
				
			||||||
 | 
					# TYPE freeipa_failures_total counter
 | 
				
			||||||
 | 
					freeipa_failures_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_freeipa_failed_tests Number of failed tests in the most recent scrape.
 | 
				
			||||||
 | 
					# TYPE freeipa_freeipa_failed_tests gauge
 | 
				
			||||||
 | 
					freeipa_freeipa_failed_tests 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_certificate_total Total number of certificates.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_certificate_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_certificate_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_conflicts_total Total number of LDAP conflicts.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_conflicts_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_conflicts_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_dns_zone_total Total number of DNS zones.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_dns_zone_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_dns_zone_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_failures_total Number of errors while scapping metrics.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_failures_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_failures_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_ghost_replica_total Total number of ghost replicas.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_ghost_replica_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_ghost_replica_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_group_total Total number of groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_group_total 3
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_hbac_rule_total Total number of HBAC rules.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_hbac_rule_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_hbac_rule_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_host_group_total Total number of host groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_host_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_host_group_total 2
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_host_total Total number of hosts.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_host_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_host_total 4
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_net_group_total Total number of net groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_net_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_net_group_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_replica_error_code Error code from last replica sync.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_replica_error_code gauge
 | 
				
			||||||
 | 
					freeipa_ldap_replica_error_code{replica="ipa2.example.com"} 0
 | 
				
			||||||
 | 
					freeipa_ldap_replica_error_code{replica="ipa3.example.com"} 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_replica_last_update The last time a replica sync occurred.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_replica_last_update gauge
 | 
				
			||||||
 | 
					freeipa_ldap_replica_last_update{replica="ipa2.example.com"} 1.693362478e+09
 | 
				
			||||||
 | 
					freeipa_ldap_replica_last_update{replica="ipa3.example.com"} 1.693362636e+09
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_scrapes_total Current total HAProxy scrapes.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_scrapes_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_scrapes_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_service_total Total number of services.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_service_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_service_total 6
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_sudo_rule_total Total number of sudo rules.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_sudo_rule_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_sudo_rule_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_up Was the last scrape of FreeIPA successful.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_up gauge
 | 
				
			||||||
 | 
					freeipa_ldap_up 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_active_total Total number of active users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_active_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_active_total 7
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_preserved_total Total number of preserved users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_preserved_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_preserved_total 3
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_stage_total Total number of staged users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_stage_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_stage_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_scrapes_total Current total HAProxy scrapes.
 | 
				
			||||||
 | 
					# TYPE freeipa_scrapes_total counter
 | 
				
			||||||
 | 
					freeipa_scrapes_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_up Was the last scrape of FreeIPA successful.
 | 
				
			||||||
 | 
					# TYPE freeipa_up gauge
 | 
				
			||||||
 | 
					freeipa_up 1
 | 
				
			||||||
							
								
								
									
										36
									
								
								test/influx.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								test/influx.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					{"fields":{"config_dna_range":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_ipa_ca_issued_cert":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_ipa_ca_issued_ldap_cert":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_ipa_cert_auto_renew":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_ipa_earliest_cert_expiry":2639495644},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_krb5_auth":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_krb5_workers":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_ldap_cert_auto_renew":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_ldap_earliest_cert_expiry":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_proxy_secret":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"config_renewal_master":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"failures_total":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"freeipa_failed_tests":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_certificate_total":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_conflicts_total":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_dns_zone_total":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_failures_total":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_ghost_replica_total":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_group_total":3},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_hbac_rule_total":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_host_group_total":2},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_host_total":4},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_net_group_total":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_replica_error_code":0},"name":"freeipa","tags":{"host":"ipa1.example.com","replica":"ipa2.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_replica_error_code":0},"name":"freeipa","tags":{"host":"ipa1.example.com","replica":"ipa3.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_replica_last_update":1693362478},"name":"freeipa","tags":{"host":"ipa1.example.com","replica":"ipa2.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_replica_last_update":1693362636},"name":"freeipa","tags":{"host":"ipa1.example.com","replica":"ipa3.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_scrapes_total":2},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_service_total":6},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_sudo_rule_total":0},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_up":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_user_active_total":7},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_user_preserved_total":3},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"ldap_user_stage_total":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"scrapes_total":2},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
 | 
					{"fields":{"up":1},"name":"freeipa","tags":{"host":"ipa1.example.com"},"timestamp":1136214245000000}
 | 
				
			||||||
							
								
								
									
										36
									
								
								test/influx.lp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								test/influx.lp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					freeipa,host=ipa1.example.com config_dna_range=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_ipa_ca_issued_cert=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_ipa_ca_issued_ldap_cert=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_ipa_cert_auto_renew=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_ipa_earliest_cert_expiry=2.639495644e+09 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_krb5_auth=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_krb5_workers=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_ldap_cert_auto_renew=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_ldap_earliest_cert_expiry=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_proxy_secret=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com config_renewal_master=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com failures_total=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com freeipa_failed_tests=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_certificate_total=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_conflicts_total=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_dns_zone_total=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_failures_total=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_ghost_replica_total=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_group_total=3 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_hbac_rule_total=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_host_group_total=2 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_host_total=4 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_net_group_total=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com,replica=ipa2.example.com ldap_replica_error_code=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com,replica=ipa3.example.com ldap_replica_error_code=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com,replica=ipa2.example.com ldap_replica_last_update=1.693362478e+09 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com,replica=ipa3.example.com ldap_replica_last_update=1.693362636e+09 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_scrapes_total=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_service_total=6 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_sudo_rule_total=0 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_up=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_user_active_total=7 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_user_preserved_total=3 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com ldap_user_stage_total=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com scrapes_total=1 1136214245000000
 | 
				
			||||||
 | 
					freeipa,host=ipa1.example.com up=1 1136214245000000
 | 
				
			||||||
							
								
								
									
										47
									
								
								test/ipa-getcert
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										47
									
								
								test/ipa-getcert
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ $1 != "list" ]]; then
 | 
				
			||||||
 | 
					    echo "Not listing."
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Return basic listing
 | 
				
			||||||
 | 
					cat <<EOF
 | 
				
			||||||
 | 
					Number of certificates and requests being tracked: 9.
 | 
				
			||||||
 | 
					Request ID '20230123225030':
 | 
				
			||||||
 | 
						status: MONITORING
 | 
				
			||||||
 | 
						stuck: no
 | 
				
			||||||
 | 
						key pair storage: type=NSSDB,location='/etc/dirsrv/slapd-EXAMPLE-COM',nickname='Server-Cert',token='NSS Certificate DB',pinfile='/etc/dirsrv/slapd-EXAMPLE-COM/pwdfile.txt'
 | 
				
			||||||
 | 
						certificate: type=NSSDB,location='/etc/dirsrv/slapd-EXAMPLE-COM',nickname='Server-Cert',token='NSS Certificate DB'
 | 
				
			||||||
 | 
						CA: IPA
 | 
				
			||||||
 | 
						issuer: CN=Certificate Authority,O=EXAMPLE.COM
 | 
				
			||||||
 | 
						subject: CN=ipa1.example.com,O=EXAMPLE.COM
 | 
				
			||||||
 | 
						expires: 2050-05-22 16:07:16 UTC
 | 
				
			||||||
 | 
						dns: ipa1.example.com,ipa.example.com
 | 
				
			||||||
 | 
						principal name: ldap/ipa1.example.com@EXAMPLE.COM
 | 
				
			||||||
 | 
						key usage: digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
 | 
				
			||||||
 | 
						eku: id-kp-serverAuth,id-kp-clientAuth
 | 
				
			||||||
 | 
						pre-save command:
 | 
				
			||||||
 | 
						post-save command: /usr/libexec/ipa/certmonger/restart_dirsrv EXAMPLE-COM
 | 
				
			||||||
 | 
						track: yes
 | 
				
			||||||
 | 
						auto-renew: yes
 | 
				
			||||||
 | 
					Request ID '20230123225047':
 | 
				
			||||||
 | 
						status: MONITORING
 | 
				
			||||||
 | 
						stuck: no
 | 
				
			||||||
 | 
						key pair storage: type=NSSDB,location='/etc/httpd/alias',nickname='Server-Cert',token='NSS Certificate DB',pinfile='/etc/httpd/alias/pwdfile.txt'
 | 
				
			||||||
 | 
						certificate: type=NSSDB,location='/etc/httpd/alias',nickname='Server-Cert',token='NSS Certificate DB'
 | 
				
			||||||
 | 
						CA: IPA
 | 
				
			||||||
 | 
						issuer: CN=Certificate Authority,O=EXAMPLE.COM
 | 
				
			||||||
 | 
						subject: CN=ipa1.example.com,O=EXAMPLE.COM
 | 
				
			||||||
 | 
						expires: 2050-06-21 00:14:29 UTC
 | 
				
			||||||
 | 
						dns: ipa1.example.com
 | 
				
			||||||
 | 
						key usage: digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment
 | 
				
			||||||
 | 
						eku: id-kp-serverAuth,id-kp-clientAuth
 | 
				
			||||||
 | 
						pre-save command:
 | 
				
			||||||
 | 
						post-save command: /usr/libexec/ipa/certmonger/restart_httpd
 | 
				
			||||||
 | 
						track: yes
 | 
				
			||||||
 | 
						auto-renew: yes
 | 
				
			||||||
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Return zero exit
 | 
				
			||||||
 | 
					exit 0
 | 
				
			||||||
							
								
								
									
										46
									
								
								test/ipa-pki-proxy.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								test/ipa-pki-proxy.conf
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,46 @@
 | 
				
			|||||||
 | 
					# VERSION 14 - DO NOT REMOVE THIS LINE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ProxyRequests Off
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# matches for ee port
 | 
				
			||||||
 | 
					<LocationMatch "^/ca/ee/ca/checkRequest|^/ca/ee/ca/getCertChain|^/ca/ee/ca/getTokenInfo|^/ca/ee/ca/tokenAuthenticate|^/ca/ocsp|^/ca/ee/ca/updateNumberRange|^/ca/ee/ca/getCRL|^/ca/ee/ca/profileSubmit">
 | 
				
			||||||
 | 
					    NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
 | 
				
			||||||
 | 
					    NSSVerifyClient none
 | 
				
			||||||
 | 
					    ProxyPassMatch ajp://localhost:8009 secret=testSecret
 | 
				
			||||||
 | 
					    ProxyPassReverse ajp://localhost:8009
 | 
				
			||||||
 | 
					</LocationMatch>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# matches for admin port and installer
 | 
				
			||||||
 | 
					<LocationMatch "^/ca/admin/ca/getCertChain|^/ca/admin/ca/getConfigEntries|^/ca/admin/ca/getCookie|^/ca/admin/ca/getStatus|^/ca/admin/ca/securityDomainLogin|^/ca/admin/ca/getDomainXML|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/tokenAuthenticate|^/ca/admin/ca/updateNumberRange|^/ca/admin/ca/updateDomainXML|^/ca/admin/ca/updateConnector|^/ca/admin/ca/getSubsystemCert|^/kra/admin/kra/updateNumberRange|^/kra/admin/kra/getConfigEntries">
 | 
				
			||||||
 | 
					    NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
 | 
				
			||||||
 | 
					    NSSVerifyClient none
 | 
				
			||||||
 | 
					    ProxyPassMatch ajp://localhost:8009 secret=testSecret
 | 
				
			||||||
 | 
					    ProxyPassReverse ajp://localhost:8009
 | 
				
			||||||
 | 
					</LocationMatch>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# matches for agent port and eeca port
 | 
				
			||||||
 | 
					<LocationMatch "^/ca/agent/ca/displayBySerial|^/ca/agent/ca/doRevoke|^/ca/agent/ca/doUnrevoke|^/ca/agent/ca/updateDomainXML|^/ca/eeca/ca/profileSubmitSSLClient|^/kra/agent/kra/connector">
 | 
				
			||||||
 | 
					    NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
 | 
				
			||||||
 | 
					    NSSVerifyClient require
 | 
				
			||||||
 | 
					    ProxyPassMatch ajp://localhost:8009 secret=testSecret
 | 
				
			||||||
 | 
					    ProxyPassReverse ajp://localhost:8009
 | 
				
			||||||
 | 
					</LocationMatch>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# matches for CA REST API
 | 
				
			||||||
 | 
					<LocationMatch "^/ca/rest/account/login|^/ca/rest/account/logout|^/ca/rest/installer/installToken|^/ca/rest/securityDomain/domainInfo|^/ca/rest/securityDomain/installToken|^/ca/rest/profiles|^/ca/rest/authorities|^/ca/rest/certrequests|^/ca/rest/admin/kraconnector/remove|^/ca/rest/certs/search">
 | 
				
			||||||
 | 
					    NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
 | 
				
			||||||
 | 
					    NSSVerifyClient optional
 | 
				
			||||||
 | 
					    ProxyPassMatch ajp://localhost:8009 secret=testSecret
 | 
				
			||||||
 | 
					    ProxyPassReverse ajp://localhost:8009
 | 
				
			||||||
 | 
					</LocationMatch>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# matches for KRA REST API
 | 
				
			||||||
 | 
					<LocationMatch "^/kra/rest/config/cert/transport|^/kra/rest/account|^/kra/rest/agent/keyrequests|^/kra/rest/agent/keys">
 | 
				
			||||||
 | 
					    NSSOptions +StdEnvVars +ExportCertData +StrictRequire +OptRenegotiate
 | 
				
			||||||
 | 
					    NSSVerifyClient optional
 | 
				
			||||||
 | 
					    ProxyPassMatch ajp://localhost:8009 secret=testSecret
 | 
				
			||||||
 | 
					    ProxyPassReverse ajp://localhost:8009
 | 
				
			||||||
 | 
					</LocationMatch>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Only enable this on servers that are not generating a CRL
 | 
				
			||||||
 | 
					RewriteRule ^/ipa/crl/MasterCRL.bin http://ipa1.example.com/ca/ee/ca/getCRL?op=getCRL&crlIssuingPoint=MasterCRL [L,R=301,NC]
 | 
				
			||||||
							
								
								
									
										52
									
								
								test/key.pem
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								test/key.pem
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					-----BEGIN PRIVATE KEY-----
 | 
				
			||||||
 | 
					MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDEYP5wT6QE/40B
 | 
				
			||||||
 | 
					MakvYnCw5EMJkzF++OVVisSxRnyKMdgpwOXe6rxSmnmmSddKst7rSK04/HhHwPGI
 | 
				
			||||||
 | 
					a5+SsLu01FprlRouyO4QRalxtULadUsBg8Na/EWC7sIgcmglP5UuygaXdq+4BqNC
 | 
				
			||||||
 | 
					NCgbxPxwnfaSeAAb/maSYAQYL1yRq1SElfFFdFvOXDMA66zTga2xDSKhud/Nxn96
 | 
				
			||||||
 | 
					pTYFy+vLUAVkQ+VOfFZaWMeBE1drGOnLVkSt1TFtFlTN5YOvyKjYUQsK4l7zcqF/
 | 
				
			||||||
 | 
					ijQuerHxnQMvZy143eW4KMEogvtMIR+KltnUvhSCLHHxYJwHnzeNmfOAqAnmM3Hc
 | 
				
			||||||
 | 
					L6eO2pZmXvoi0BYGQhMEwhtwsYQNgtNqbtpK4wqaJUj2WDU+qoCIVC13BFQdQSq5
 | 
				
			||||||
 | 
					6NGZM/Y2nPtX33f4A9hfLnxqEWR4DsDHT+hKRzdnSq/aY/iQ9TQDY0gHzePeVU0t
 | 
				
			||||||
 | 
					g+evNc7u6rw6Zqh0eiY/V2i35aSEJTf1CxqVdM8O6QnoxK6umJKgOBEt6dTa+a50
 | 
				
			||||||
 | 
					+1NqM9ufvkzIZUc5HGnFUBsvMer7l6UQNq1STB+4w33ntQmbtvgvNnGXmXgB59TG
 | 
				
			||||||
 | 
					BjLc/+kVBBDLn+R3yzG4mLqpLUykX8cRcTul6RIhNxMiNI02Y15kFMJ+MOHyjHKY
 | 
				
			||||||
 | 
					KYkS1ZyKwPRQkISiXuZANaT+vYCLHwIDAQABAoICAQCrfYRUccfrMXtiUorLPWzp
 | 
				
			||||||
 | 
					nLxKDUdI+XPUKtWvdb1mNTbu52wWKekBPbMEGzGuItv2ncXfoOIszvpdxpZYVIvm
 | 
				
			||||||
 | 
					0xaPImr19jOm9B6PlNnnykwQ647a0rilKXlPOnlmJctSS8xL0rKKwwko1EE+VtyY
 | 
				
			||||||
 | 
					P+nGaJK334aVRtHsiNeOwg6RphtHKuDNKcjEggqvvWv/1Fes4ZPmr/Q9Fy9BCp5E
 | 
				
			||||||
 | 
					MwIyV/RUgNIsHaFDP6+0b9Ii5pgdMbLy73BpSYehJ1sDZGp/O8XtVOphZUBCYpUo
 | 
				
			||||||
 | 
					SJQyfijAhw6HrtdXWGK5TaessCVT2hYwwz1Rq6s2IL0zpAB4FsZmSACjZt4tKwfw
 | 
				
			||||||
 | 
					oFsJrw+Hbw1EYSmCxP+RWY0ohOo/tqJchuaPdcc9Z7RTFTRtt2W1c2zVcoaqa3/1
 | 
				
			||||||
 | 
					kkb1Y+fY4pPPpqFmP4oKuSmG8VBYnUaMK6vPh4snpf0GyL9uj+oQ2BOsAGT52eOr
 | 
				
			||||||
 | 
					r/xN+Y4IFZtVIWagO/HNEujqFFBefepnkgVwTTD12sN+0+8VVYGupB9jFMHnIpGc
 | 
				
			||||||
 | 
					AhiJsnjOe0E30dIpXaIFRrNPIVnjBiI7mBWgvpOrlYiRuy8gsdUR+uLCEGoLG54u
 | 
				
			||||||
 | 
					te7I/oyWWp6buc9ml93IWKoxo4lqXU3dmy0d7ZYma/tf4FSUzs1GyWXxf/daJuPJ
 | 
				
			||||||
 | 
					pDMxFjtUBEoAF/W2ePRTwQKCAQEA4kv0Ew8gBlwPqDsXqHN1NadaFYDZK2QfHBCj
 | 
				
			||||||
 | 
					zEfhBD1CMRqMrXAlvCaUq6EF8cION03FYFrpVjTl/bCW4kCpfydfcy89jMVP8UbL
 | 
				
			||||||
 | 
					XkJJ5ZpP7IL116MJPiERAh62lqpR9uV4yMSLTxOCoIbyn15VBS4oGkid7AcKTHOv
 | 
				
			||||||
 | 
					NGjtGCAq+femCVuWp2d5y6BPDrpNnLOA2dVg5pY8aCHEl9eZi9yWVSxr6Y9HiUJ+
 | 
				
			||||||
 | 
					UthD+yeSzk7GgPg6qowc8KbTTedSKKjd2JBhqtaITRo6lsBLEI+LDSOSYbEcSUoX
 | 
				
			||||||
 | 
					WsIo/B90DgZ0k9f6T/GiZ8svUmfpreUjfgsmJjrzbtrGwzMH4QKCAQEA3ie9KDAk
 | 
				
			||||||
 | 
					wpfphK9/U8zEi1raKDai3ksIJdIURURZUdtJ7GEGZ4hyqvywewVaFlEDcZ+QYaFX
 | 
				
			||||||
 | 
					XT/WquA1myVjoWxlm8SGBu0PKHT0y4FGY6tW6FYdEAdnQL40+XsX6V4IHbh289Bk
 | 
				
			||||||
 | 
					Ti0cmv0e6tgFPquNaIJnnrag/5Ak/Ac+fGJLRQGwHT2eD8VmJ8hF8i7E08uxNnQb
 | 
				
			||||||
 | 
					sPDXmw81Yb+rWRSD3KQkTllhMKLjfmqI+P4npqlgizHoK6k2a4GQYumHZmxZPALa
 | 
				
			||||||
 | 
					+SLgEGOL++3rbiHulGPOaox/iU2GtS9aRwSgfKlKSKdNylHz38CgL9Kidg8Fz1O1
 | 
				
			||||||
 | 
					c7EOGYC8s1ny/wKCAQAoF6LPeZ+H4OmZOZbwbjw23EZ2htRy/pMQatZKS/XOxXej
 | 
				
			||||||
 | 
					sXt5AuR8mC1A1w9xjJruK2Yrsw+iCU8yCgZBYYlmELi1dIooFZEbQxqmwYHMHvHI
 | 
				
			||||||
 | 
					Ck+5+5WYn00fHgflW5mX74HduAyiXueGv0HfAFx5xXqvZWwtM/YcI2bIF0riOljC
 | 
				
			||||||
 | 
					3qBZChP/5rJKZEV9a35yo87RSR+Y2scq/8iPyk/W2qb7whoAUDUxWUl+LfilV5aH
 | 
				
			||||||
 | 
					3KcIlHH4Y0iBTl0jcTc6IujjBHl5RfbyChKVQM5LydKt6j519mX3ihvnJX0TZhMu
 | 
				
			||||||
 | 
					pPAkfWBIp5vJXdMte2GIQI9wNlN09H7KhhIu5SyhAoIBACAbnjswuh9l1VpYAw8Z
 | 
				
			||||||
 | 
					iU6a0uz8+I0oSwUsV8GrHz21c/m2DDbqgag03UzqeRrAmr7RUQzLRNU1ZNFNlnHV
 | 
				
			||||||
 | 
					9ZBfGlBpFvXpTUeLn9XJ2WKOYQEzcP/gEgxJcV6da9dOv92Ly6VxeQ3Td07vRoiq
 | 
				
			||||||
 | 
					sBdetBFmx5Mo0hwduTqz0VQo4LgYhluzjCS7Ywhc6b8XA1uZFQPJxDbOmFrQ1+ZI
 | 
				
			||||||
 | 
					zXsSe/xnvNeWE3X0FO0weJuEIDb2Q/3aOLQWwMbI8xVYqzkib8M8pmlboQa9XH4M
 | 
				
			||||||
 | 
					5PoF7XWE91Bu/f/aNJ37OhEJmihqT1Iw3A1hyt2L+Zrv1os5oJ1We+M8s8z7zkod
 | 
				
			||||||
 | 
					tgECggEBAI6GadDOWXjzcTPyf+yj9km0aCxxFq6oJ3veYMa4fo9ae0driQ/4MLrC
 | 
				
			||||||
 | 
					JxlD8Bnb9nWt6AKuQpK7YFGPbbE7FgPwBi7V/nHVXPSNvsNk+cMo580ZHJEjro3z
 | 
				
			||||||
 | 
					+4CiBuaNuA7avZlc7ie8dL7RqawngFwjwRnJkxkUoxGRPvgZMD16imPnuVgiwFYQ
 | 
				
			||||||
 | 
					VsLtFQUwk+kCSYD/iitDw6p9IYTWbgC6YFAmakL9jPQvPBsfDquwQxN2flG+heVZ
 | 
				
			||||||
 | 
					W4GVRXdttUNizAQRWrjS2eYCdyOcOXzBOlI8Bleau0GmpRwJioyG9vNJGSEzlViy
 | 
				
			||||||
 | 
					HEkW+ikazbeshT71rcSFAE4eOZzg+y4=
 | 
				
			||||||
 | 
					-----END PRIVATE KEY-----
 | 
				
			||||||
							
								
								
									
										34
									
								
								test/kinit
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										34
									
								
								test/kinit
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					keytab=""
 | 
				
			||||||
 | 
					cache=""
 | 
				
			||||||
 | 
					credentials=""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Parse arguments.
 | 
				
			||||||
 | 
					while (( $# > 0 )); do
 | 
				
			||||||
 | 
					    case "$1" in
 | 
				
			||||||
 | 
					        -kt)
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            keytab=$1
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					        ;;
 | 
				
			||||||
 | 
					        -c)
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            cache=$1
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					        ;;
 | 
				
			||||||
 | 
					        *)
 | 
				
			||||||
 | 
					            credentials=$1
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					        ;;
 | 
				
			||||||
 | 
					    esac
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Return basic kinit error if expected values do match.
 | 
				
			||||||
 | 
					if [[ $keytab != "/etc/krb5.keytab" ]] || [[ $credentials != "host/ipa1.example.com@EXAMPLE.COM" ]] || ! [[ $cache =~ \/tmp\/krb5_cache_.* ]]; then
 | 
				
			||||||
 | 
					    echo "kinit: Keytab contains no suitable keys for $credentials while getting initial credentials"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Return zero exit
 | 
				
			||||||
 | 
					exit 0
 | 
				
			||||||
							
								
								
									
										32
									
								
								test/klist
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										32
									
								
								test/klist
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					#!/bin/bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cache=""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Parse arguments.
 | 
				
			||||||
 | 
					while (( $# > 0 )); do
 | 
				
			||||||
 | 
					    case "$1" in
 | 
				
			||||||
 | 
					        -c)
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					            cache=$1
 | 
				
			||||||
 | 
					            shift
 | 
				
			||||||
 | 
					        ;;
 | 
				
			||||||
 | 
					    esac
 | 
				
			||||||
 | 
					done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# If cache file isn't expected path, return error.
 | 
				
			||||||
 | 
					if ! [[ $cache =~ \/tmp\/krb5_cache_.* ]]; then
 | 
				
			||||||
 | 
					    echo "klist: No credentials cache found (filename: $cache)"
 | 
				
			||||||
 | 
					    exit 1
 | 
				
			||||||
 | 
					fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Return basic klist response.
 | 
				
			||||||
 | 
					cat <<EOF
 | 
				
			||||||
 | 
					Ticket cache: FILE:$cache
 | 
				
			||||||
 | 
					Default principal: host/ipa1.example.com@EXAMPLE.COM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Valid starting       Expires              Service principal
 | 
				
			||||||
 | 
					08/30/2023 17:04:18  08/31/2050 17:04:18  krbtgt/EXAMPLE.COM@EXAMPLE.COM
 | 
				
			||||||
 | 
					EOF
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Return zero exit
 | 
				
			||||||
 | 
					exit 0
 | 
				
			||||||
							
								
								
									
										3
									
								
								test/krb5kdc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/krb5kdc
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					KRB5KDC_ARGS='-w 5555'
 | 
				
			||||||
 | 
					KRB5REALM=EXAMPLE.COM
 | 
				
			||||||
 | 
					KRB5KDC_ARGS='-w 5555'
 | 
				
			||||||
							
								
								
									
										59
									
								
								test/ldap.metrics
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								test/ldap.metrics
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					# HELP freeipa_ldap_certificate_total Total number of certificates.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_certificate_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_certificate_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_conflicts_total Total number of LDAP conflicts.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_conflicts_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_conflicts_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_dns_zone_total Total number of DNS zones.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_dns_zone_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_dns_zone_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_failures_total Number of errors while scapping metrics.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_failures_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_failures_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_ghost_replica_total Total number of ghost replicas.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_ghost_replica_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_ghost_replica_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_group_total Total number of groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_group_total 3
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_hbac_rule_total Total number of HBAC rules.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_hbac_rule_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_hbac_rule_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_host_group_total Total number of host groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_host_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_host_group_total 2
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_host_total Total number of hosts.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_host_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_host_total 4
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_net_group_total Total number of net groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_net_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_net_group_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_replica_error_code Error code from last replica sync.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_replica_error_code gauge
 | 
				
			||||||
 | 
					freeipa_ldap_replica_error_code{replica="ipa2.example.com"} 0
 | 
				
			||||||
 | 
					freeipa_ldap_replica_error_code{replica="ipa3.example.com"} 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_replica_last_update The last time a replica sync occurred.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_replica_last_update gauge
 | 
				
			||||||
 | 
					freeipa_ldap_replica_last_update{replica="ipa2.example.com"} 1.693362478e+09
 | 
				
			||||||
 | 
					freeipa_ldap_replica_last_update{replica="ipa3.example.com"} 1.693362636e+09
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_scrapes_total Current total HAProxy scrapes.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_scrapes_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_scrapes_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_service_total Total number of services.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_service_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_service_total 6
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_sudo_rule_total Total number of sudo rules.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_sudo_rule_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_sudo_rule_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_up Was the last scrape of FreeIPA successful.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_up gauge
 | 
				
			||||||
 | 
					freeipa_ldap_up 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_active_total Total number of active users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_active_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_active_total 7
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_preserved_total Total number of preserved users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_preserved_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_preserved_total 3
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_stage_total Total number of staged users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_stage_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_stage_total 1
 | 
				
			||||||
							
								
								
									
										14
									
								
								test/ldap_computers.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								test/ldap_computers.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					# ipa1.example.com, computers, accounts, example.com
 | 
				
			||||||
 | 
					dn: fqdn=ipa1.example.com,cn=computers,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ipa2.example.com, computers, accounts, example.com
 | 
				
			||||||
 | 
					dn: fqdn=ipa2.example.com,cn=computers,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ipa3.example.com, computers, accounts, example.com
 | 
				
			||||||
 | 
					dn: fqdn=ipa3.example.com,cn=computers,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# centos6.example.com, computers, accounts, example.com
 | 
				
			||||||
 | 
					dn: fqdn=centos6.example.com,cn=computers,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# centos7.example.com, computers, accounts, example.com
 | 
				
			||||||
 | 
					dn: fqdn=centos7.example.com,cn=computers,cn=accounts,dc=example,dc=com
 | 
				
			||||||
							
								
								
									
										3
									
								
								test/ldap_deleted_sub.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/ldap_deleted_sub.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					# deleted users, accounts, provisioning, example.com
 | 
				
			||||||
 | 
					dn: cn=deleted users,cn=accounts,cn=provisioning,dc=example,dc=com
 | 
				
			||||||
 | 
					numSubordinates: 3
 | 
				
			||||||
							
								
								
									
										51
									
								
								test/ldap_fail.metrics
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								test/ldap_fail.metrics
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					# HELP freeipa_ldap_certificate_total Total number of certificates.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_certificate_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_certificate_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_conflicts_total Total number of LDAP conflicts.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_conflicts_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_conflicts_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_dns_zone_total Total number of DNS zones.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_dns_zone_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_dns_zone_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_failures_total Number of errors while scapping metrics.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_failures_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_failures_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_ghost_replica_total Total number of ghost replicas.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_ghost_replica_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_ghost_replica_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_group_total Total number of groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_group_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_hbac_rule_total Total number of HBAC rules.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_hbac_rule_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_hbac_rule_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_host_group_total Total number of host groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_host_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_host_group_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_host_total Total number of hosts.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_host_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_host_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_net_group_total Total number of net groups.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_net_group_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_net_group_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_scrapes_total Current total HAProxy scrapes.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_scrapes_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_scrapes_total 2
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_service_total Total number of services.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_service_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_service_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_sudo_rule_total Total number of sudo rules.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_sudo_rule_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_sudo_rule_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_up Was the last scrape of FreeIPA successful.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_up gauge
 | 
				
			||||||
 | 
					freeipa_ldap_up 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_active_total Total number of active users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_active_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_active_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_preserved_total Total number of preserved users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_preserved_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_preserved_total 0
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_user_stage_total Total number of staged users.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_user_stage_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_user_stage_total 0
 | 
				
			||||||
							
								
								
									
										9
									
								
								test/ldap_fail_connect.metrics
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								test/ldap_fail_connect.metrics
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					# HELP freeipa_ldap_failures_total Number of errors while scapping metrics.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_failures_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_failures_total 1
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_scrapes_total Current total HAProxy scrapes.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_scrapes_total counter
 | 
				
			||||||
 | 
					freeipa_ldap_scrapes_total 3
 | 
				
			||||||
 | 
					# HELP freeipa_ldap_up Was the last scrape of FreeIPA successful.
 | 
				
			||||||
 | 
					# TYPE freeipa_ldap_up gauge
 | 
				
			||||||
 | 
					freeipa_ldap_up 0
 | 
				
			||||||
							
								
								
									
										11
									
								
								test/ldap_groups.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								test/ldap_groups.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					# admins, groups, accounts, example.com
 | 
				
			||||||
 | 
					dn: cn=admins,cn=groups,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ipausers, groups, accounts, example.com
 | 
				
			||||||
 | 
					dn: cn=ipausers,cn=groups,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# editors, groups, accounts, example.com
 | 
				
			||||||
 | 
					dn: cn=editors,cn=groups,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# trust admins, groups, accounts, example.com
 | 
				
			||||||
 | 
					dn: cn=trust admins,cn=groups,cn=accounts,dc=example,dc=com
 | 
				
			||||||
							
								
								
									
										5
									
								
								test/ldap_hbac.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								test/ldap_hbac.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					# 6044f548-9c19-11ed-812d-52540006a399, hbac, example.com
 | 
				
			||||||
 | 
					dn: ipaUniqueID=6044f548-9c19-11ed-812d-52540006a399,cn=hbac,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# 610318a2-9c19-11ed-a39e-52540006a399, hbac, example.com
 | 
				
			||||||
 | 
					dn: ipaUniqueID=610318a2-9c19-11ed-a39e-52540006a399,cn=hbac,dc=example,dc=com
 | 
				
			||||||
							
								
								
									
										3
									
								
								test/ldap_hostgroups.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/ldap_hostgroups.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					# hostgroups, accounts, example.com
 | 
				
			||||||
 | 
					dn: cn=hostgroups,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					numSubordinates: 2
 | 
				
			||||||
							
								
								
									
										83
									
								
								test/ldap_mapping_tree.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								test/ldap_mapping_tree.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					# meToipa2.example.com, replica, dc\3Dexample\2Cdc\3Dcom, mapping tree, config
 | 
				
			||||||
 | 
					dn: cn=meToipa2.example.com,cn=replica,cn=dc\3Dexample\2Cdc\3Dcom,cn=mapping tree,cn=con
 | 
				
			||||||
 | 
					 fig
 | 
				
			||||||
 | 
					cn: meToipa2.example.com
 | 
				
			||||||
 | 
					description: me to ipa2.example.com
 | 
				
			||||||
 | 
					ipaReplTopoManagedAgreementState: managed agreement - controlled by topology p
 | 
				
			||||||
 | 
					 lugin
 | 
				
			||||||
 | 
					nsDS5ReplicaBindMethod: SASL/GSSAPI
 | 
				
			||||||
 | 
					nsDS5ReplicaHost: ipa2.example.com
 | 
				
			||||||
 | 
					nsDS5ReplicaPort: 389
 | 
				
			||||||
 | 
					nsDS5ReplicaRoot: dc=example,dc=com
 | 
				
			||||||
 | 
					nsDS5ReplicaTransportInfo: LDAP
 | 
				
			||||||
 | 
					nsDS5ReplicatedAttributeList: (objectclass=*) $ EXCLUDE memberof idnssoaserial
 | 
				
			||||||
 | 
					  entryusn krblastsuccessfulauth krblastfailedauth krbloginfailedcount
 | 
				
			||||||
 | 
					nsDS5ReplicatedAttributeListTotal: (objectclass=*) $ EXCLUDE entryusn krblasts
 | 
				
			||||||
 | 
					 uccessfulauth krblastfailedauth krbloginfailedcount
 | 
				
			||||||
 | 
					nsds50ruv: {replicageneration} 63d03746000000040000
 | 
				
			||||||
 | 
					nsds50ruv: {replica 3 ldap://ipa2.example.com:389} 63d0375b000000030000 64cbbe17000
 | 
				
			||||||
 | 
					 600030000
 | 
				
			||||||
 | 
					nsds50ruv: {replica 4 ldap://ipa1.example.com:389} 63d03746000100040000 64cbbe1b000
 | 
				
			||||||
 | 
					 300040000
 | 
				
			||||||
 | 
					nsds50ruv: {replica 5 ldap://ipa3.example.com:389} 63d03858000000050000 64cbbe07000
 | 
				
			||||||
 | 
					 100050000
 | 
				
			||||||
 | 
					nsds5ReplicaStripAttrs: modifiersName modifyTimestamp internalModifiersName in
 | 
				
			||||||
 | 
					 ternalModifyTimestamp
 | 
				
			||||||
 | 
					nsds5replicaTimeout: 120
 | 
				
			||||||
 | 
					nsruvReplicaLastModified: {replica 3 ldap://ipa2.example.com:389} 00000000
 | 
				
			||||||
 | 
					nsruvReplicaLastModified: {replica 4 ldap://ipa1.example.com:389} 00000000
 | 
				
			||||||
 | 
					nsruvReplicaLastModified: {replica 5 ldap://ipa3.example.com:389} 00000000
 | 
				
			||||||
 | 
					objectClass: nsds5replicationagreement
 | 
				
			||||||
 | 
					objectClass: top
 | 
				
			||||||
 | 
					objectClass: ipaReplTopoManagedAgreement
 | 
				
			||||||
 | 
					nsds5replicareapactive: 0
 | 
				
			||||||
 | 
					nsds5replicaLastUpdateStart: 20230830022758Z
 | 
				
			||||||
 | 
					nsds5replicaLastUpdateEnd: 20230830022758Z
 | 
				
			||||||
 | 
					nsds5replicaChangesSentSinceStartup:: NToyLzAgNDozLzIyNzEg
 | 
				
			||||||
 | 
					nsds5replicaLastUpdateStatus: Error (0) Replica acquired successfully: Increme
 | 
				
			||||||
 | 
					 ntal update succeeded
 | 
				
			||||||
 | 
					nsds5replicaUpdateInProgress: FALSE
 | 
				
			||||||
 | 
					nsds5replicaLastInitStart: 19700101000000Z
 | 
				
			||||||
 | 
					nsds5replicaLastInitEnd: 19700101000000Z
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# meToipa3.example.com, replica, dc\3Dexample\2Cdc\3Dcom, mapping tree, config
 | 
				
			||||||
 | 
					dn: cn=meToipa3.example.com,cn=replica,cn=dc\3Dexample\2Cdc\3Dcom,cn=mapping tree,cn=con
 | 
				
			||||||
 | 
					 fig
 | 
				
			||||||
 | 
					cn: meToipa3.example.com
 | 
				
			||||||
 | 
					description: me to ipa3.example.com
 | 
				
			||||||
 | 
					ipaReplTopoManagedAgreementState: managed agreement - controlled by topology p
 | 
				
			||||||
 | 
					 lugin
 | 
				
			||||||
 | 
					nsDS5ReplicaBindMethod: SASL/GSSAPI
 | 
				
			||||||
 | 
					nsDS5ReplicaHost: ipa3.example.com
 | 
				
			||||||
 | 
					nsDS5ReplicaPort: 389
 | 
				
			||||||
 | 
					nsDS5ReplicaRoot: dc=example,dc=com
 | 
				
			||||||
 | 
					nsDS5ReplicaTransportInfo: LDAP
 | 
				
			||||||
 | 
					nsDS5ReplicatedAttributeList: (objectclass=*) $ EXCLUDE memberof idnssoaserial
 | 
				
			||||||
 | 
					  entryusn krblastsuccessfulauth krblastfailedauth krbloginfailedcount
 | 
				
			||||||
 | 
					nsDS5ReplicatedAttributeListTotal: (objectclass=*) $ EXCLUDE entryusn krblasts
 | 
				
			||||||
 | 
					 uccessfulauth krblastfailedauth krbloginfailedcount
 | 
				
			||||||
 | 
					nsds50ruv: {replicageneration} 63d03746000000040000
 | 
				
			||||||
 | 
					nsds50ruv: {replica 5 ldap://ipa3.example.com:389} 63d03858000000050000 64cbbe07000
 | 
				
			||||||
 | 
					 100050000
 | 
				
			||||||
 | 
					nsds50ruv: {replica 4 ldap://ipa1.example.com:389} 63d03746000100040000 64cbbe1b000
 | 
				
			||||||
 | 
					 300040000
 | 
				
			||||||
 | 
					nsds50ruv: {replica 3 ldap://ipa2.example.com:389} 63d0375b000000030000 64cbbe17000
 | 
				
			||||||
 | 
					 600030000
 | 
				
			||||||
 | 
					nsds5ReplicaStripAttrs: modifiersName modifyTimestamp internalModifiersName in
 | 
				
			||||||
 | 
					 ternalModifyTimestamp
 | 
				
			||||||
 | 
					nsds5replicaTimeout: 120
 | 
				
			||||||
 | 
					nsruvReplicaLastModified: {replica 5 ldap://ipa3.example.com:389} 00000000
 | 
				
			||||||
 | 
					nsruvReplicaLastModified: {replica 4 ldap://ipa1.example.com:389} 00000000
 | 
				
			||||||
 | 
					nsruvReplicaLastModified: {replica 3 ldap://ipa2.example.com:389} 00000000
 | 
				
			||||||
 | 
					objectClass: nsds5replicationagreement
 | 
				
			||||||
 | 
					objectClass: top
 | 
				
			||||||
 | 
					objectClass: ipaReplTopoManagedAgreement
 | 
				
			||||||
 | 
					nsds5replicareapactive: 0
 | 
				
			||||||
 | 
					nsds5replicaLastUpdateStart: 20230830023036Z
 | 
				
			||||||
 | 
					nsds5replicaLastUpdateEnd: 20230830023036Z
 | 
				
			||||||
 | 
					nsds5replicaChangesSentSinceStartup:: MzoyLzAgNDozLzIyNzIg
 | 
				
			||||||
 | 
					nsds5replicaLastUpdateStatus: Error (0) Replica acquired successfully: Increme
 | 
				
			||||||
 | 
					 ntal update succeeded
 | 
				
			||||||
 | 
					nsds5replicaUpdateInProgress: FALSE
 | 
				
			||||||
 | 
					nsds5replicaLastInitStart: 19700101000000Z
 | 
				
			||||||
 | 
					nsds5replicaLastInitEnd: 19700101000000Z
 | 
				
			||||||
							
								
								
									
										36
									
								
								test/ldap_masters.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								test/ldap_masters.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					# ipa1.example.com, masters, ipa, etc, example.com
 | 
				
			||||||
 | 
					dn: cn=ipa1.example.com,cn=masters,cn=ipa,cn=etc,dc=example,dc=com
 | 
				
			||||||
 | 
					objectClass: top
 | 
				
			||||||
 | 
					objectClass: nsContainer
 | 
				
			||||||
 | 
					objectClass: ipaReplTopoManagedServer
 | 
				
			||||||
 | 
					objectClass: ipaConfigObject
 | 
				
			||||||
 | 
					objectClass: ipaSupportedDomainLevelConfig
 | 
				
			||||||
 | 
					cn: ipa1.example.com
 | 
				
			||||||
 | 
					ipaReplTopoManagedSuffix: dc=example,dc=com
 | 
				
			||||||
 | 
					ipaReplTopoManagedSuffix: o=ipaca
 | 
				
			||||||
 | 
					ipaMinDomainLevel: 0
 | 
				
			||||||
 | 
					ipaMaxDomainLevel: 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ipa2.example.com, masters, ipa, etc, example.com
 | 
				
			||||||
 | 
					dn: cn=ipa2.example.com,cn=masters,cn=ipa,cn=etc,dc=example,dc=com
 | 
				
			||||||
 | 
					objectClass: top
 | 
				
			||||||
 | 
					objectClass: nsContainer
 | 
				
			||||||
 | 
					objectClass: ipaReplTopoManagedServer
 | 
				
			||||||
 | 
					objectClass: ipaConfigObject
 | 
				
			||||||
 | 
					objectClass: ipaSupportedDomainLevelConfig
 | 
				
			||||||
 | 
					cn: ipa2.example.com
 | 
				
			||||||
 | 
					ipaReplTopoManagedSuffix: dc=example,dc=com
 | 
				
			||||||
 | 
					ipaMinDomainLevel: 0
 | 
				
			||||||
 | 
					ipaMaxDomainLevel: 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ipa3.example.com, masters, ipa, etc, example.com
 | 
				
			||||||
 | 
					dn: cn=ipa3.example.com,cn=masters,cn=ipa,cn=etc,dc=example,dc=com
 | 
				
			||||||
 | 
					objectClass: top
 | 
				
			||||||
 | 
					objectClass: nsContainer
 | 
				
			||||||
 | 
					objectClass: ipaReplTopoManagedServer
 | 
				
			||||||
 | 
					objectClass: ipaConfigObject
 | 
				
			||||||
 | 
					objectClass: ipaSupportedDomainLevelConfig
 | 
				
			||||||
 | 
					cn: ipa3.example.com
 | 
				
			||||||
 | 
					ipaReplTopoManagedSuffix: dc=example,dc=com
 | 
				
			||||||
 | 
					ipaMinDomainLevel: 0
 | 
				
			||||||
 | 
					ipaMaxDomainLevel: 1
 | 
				
			||||||
							
								
								
									
										2
									
								
								test/ldap_netgroups.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								test/ldap_netgroups.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					# otpauth, ng, alt, example.com
 | 
				
			||||||
 | 
					dn: cn=otpauth,cn=ng,cn=alt,dc=example,dc=com
 | 
				
			||||||
							
								
								
									
										27
									
								
								test/ldap_services.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								test/ldap_services.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					# ldap/ipa1.example.com@EXAMPLE.COM, services, accounts, example.com
 | 
				
			||||||
 | 
					dn: krbprincipalname=ldap/ipa1.example.com@EXAMPLE.COM,cn=services,cn=accounts
 | 
				
			||||||
 | 
					 ,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# dogtag/ipa1.example.com@EXAMPLE.COM, services, accounts, example.com
 | 
				
			||||||
 | 
					dn: krbprincipalname=dogtag/ipa1.example.com@EXAMPLE.COM,cn=services,cn=accoun
 | 
				
			||||||
 | 
					 ts,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# HTTP/ipa1.example.com@EXAMPLE.COM, services, accounts, example.com
 | 
				
			||||||
 | 
					dn: krbprincipalname=HTTP/ipa1.example.com@EXAMPLE.COM,cn=services,cn=accounts
 | 
				
			||||||
 | 
					 ,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ldap/ipa2.example.com@EXAMPLE.COM, services, accounts, example.com
 | 
				
			||||||
 | 
					dn: krbprincipalname=ldap/ipa2.example.com@EXAMPLE.COM,cn=services,cn=accounts
 | 
				
			||||||
 | 
					 ,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# HTTP/ipa2.example.com@EXAMPLE.COM, services, accounts, example.com
 | 
				
			||||||
 | 
					dn: krbprincipalname=HTTP/ipa2.example.com@EXAMPLE.COM,cn=services,cn=accounts
 | 
				
			||||||
 | 
					 ,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# ldap/ipa3.example.com@EXAMPLE.COM, services, accounts, example.com
 | 
				
			||||||
 | 
					dn: krbprincipalname=ldap/ipa3.example.com@EXAMPLE.COM,cn=services,cn=accounts
 | 
				
			||||||
 | 
					 ,dc=example,dc=com
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# HTTP/ipa3.example.com@EXAMPLE.COM, services, accounts, example.com
 | 
				
			||||||
 | 
					dn: krbprincipalname=HTTP/ipa3.example.com@EXAMPLE.COM,cn=services,cn=accounts
 | 
				
			||||||
 | 
					 ,dc=example,dc=com
 | 
				
			||||||
							
								
								
									
										3
									
								
								test/ldap_stagged_sub.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/ldap_stagged_sub.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					# staged users, accounts, provisioning, example.com
 | 
				
			||||||
 | 
					dn: cn=staged users,cn=accounts,cn=provisioning,dc=example,dc=com
 | 
				
			||||||
 | 
					numSubordinates: 1
 | 
				
			||||||
							
								
								
									
										3
									
								
								test/ldap_sudo.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/ldap_sudo.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					# e800b6ce-b718-11ed-87b0-52540006a399, sudorules, sudo, example.com
 | 
				
			||||||
 | 
					dn: ipaUniqueID=e800b6ce-b718-11ed-87b0-52540006a399,cn=sudorules,cn=sudo,dc=e
 | 
				
			||||||
 | 
					 xample,dc=com
 | 
				
			||||||
							
								
								
									
										3
									
								
								test/ldap_user_sub.ldif
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								test/ldap_user_sub.ldif
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					# users, accounts, example.com
 | 
				
			||||||
 | 
					dn: cn=users,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					numSubordinates: 7
 | 
				
			||||||
							
								
								
									
										248
									
								
								test/server.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								test/server.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,248 @@
 | 
				
			|||||||
 | 
					<!-- BEGIN COPYRIGHT BLOCK
 | 
				
			||||||
 | 
					     Copyright (C) 2012 Red Hat, Inc.
 | 
				
			||||||
 | 
					     All rights reserved.
 | 
				
			||||||
 | 
					     Modifications: configuration parameters
 | 
				
			||||||
 | 
					     END COPYRIGHT BLOCK -->
 | 
				
			||||||
 | 
					<!--
 | 
				
			||||||
 | 
					  Licensed to the Apache Software Foundation (ASF) under one or more
 | 
				
			||||||
 | 
					  contributor license agreements.  See the NOTICE file distributed with
 | 
				
			||||||
 | 
					  this work for additional information regarding copyright ownership.
 | 
				
			||||||
 | 
					  The ASF licenses this file to You under the Apache License, Version 2.0
 | 
				
			||||||
 | 
					  (the "License"); you may not use this file except in compliance with
 | 
				
			||||||
 | 
					  the License.  You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					  distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					  See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					  limitations under the License.
 | 
				
			||||||
 | 
					-->
 | 
				
			||||||
 | 
					<!-- Note:  A "Server" is not itself a "Container", so you may not
 | 
				
			||||||
 | 
					     define subcomponents such as "Valves" at this level.
 | 
				
			||||||
 | 
					     Documentation at /docs/config/server.html
 | 
				
			||||||
 | 
					-->
 | 
				
			||||||
 | 
					<!-- DO NOT REMOVE - Begin PKI Status Definitions -->
 | 
				
			||||||
 | 
					<!-- CA Status Definitions -->
 | 
				
			||||||
 | 
					<?pkidaemon Unsecure URL        = http://ipa1.example.com:8080/ca/ee/ca
 | 
				
			||||||
 | 
					Secure Agent URL    = https://ipa1.example.com:8443/ca/agent/ca
 | 
				
			||||||
 | 
					Secure EE URL       = https://ipa1.example.com:8443/ca/ee/ca
 | 
				
			||||||
 | 
					Secure Admin URL    = https://ipa1.example.com:8443/ca/services
 | 
				
			||||||
 | 
					PKI Console Command = pkiconsole https://ipa1.example.com:8443/ca
 | 
				
			||||||
 | 
					Tomcat Port         = 8005 (for shutdown)
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					<!-- KRA Status Definitions -->
 | 
				
			||||||
 | 
					<?pkidaemon Secure Agent URL    = https://ipa1.example.com:8443/kra/agent/kra
 | 
				
			||||||
 | 
					Secure Admin URL    = https://ipa1.example.com:8443/kra/services
 | 
				
			||||||
 | 
					PKI Console Command = pkiconsole https://ipa1.example.com:8443/kra
 | 
				
			||||||
 | 
					Tomcat Port         = 8005 (for shutdown)
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					<!-- OCSP Status Definitions -->
 | 
				
			||||||
 | 
					<?pkidaemon Unsecure URL        = http://ipa1.example.com:8080/ocsp/ee/ocsp/<ocsp request blob>
 | 
				
			||||||
 | 
					Secure Agent URL    = https://ipa1.example.com:8443/ocsp/agent/ocsp
 | 
				
			||||||
 | 
					Secure EE URL       = https://ipa1.example.com:8443/ocsp/ee/ocsp/<ocsp request blob>
 | 
				
			||||||
 | 
					Secure Admin URL    = https://ipa1.example.com:8443/ocsp/services
 | 
				
			||||||
 | 
					PKI Console Command = pkiconsole https://ipa1.example.com:8443/ocsp
 | 
				
			||||||
 | 
					Tomcat Port         = 8005 (for shutdown)
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					<!-- TKS Status Definitions -->
 | 
				
			||||||
 | 
					<?pkidaemon Secure Agent URL    = https://ipa1.example.com:8443/tks/agent/tks
 | 
				
			||||||
 | 
					Secure Admin URL    = https://ipa1.example.com:8443/tks/services
 | 
				
			||||||
 | 
					PKI Console Command = pkiconsole https://ipa1.example.com:8443/tks
 | 
				
			||||||
 | 
					Tomcat Port         = 8005 (for shutdown)
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					<!-- TPS Status Definitions -->
 | 
				
			||||||
 | 
					<?pkidaemon Unsecure URL        = http://ipa1.example.com:8080/tps
 | 
				
			||||||
 | 
					Secure URL          = https://ipa1.example.com:8443/tps
 | 
				
			||||||
 | 
					Unsecure PHONE HOME = http://ipa1.example.com:8080/tps/phoneHome
 | 
				
			||||||
 | 
					Secure PHONE HOME   = https://ipa1.example.com:8443/tps/phoneHome
 | 
				
			||||||
 | 
					Tomcat Port         = 8005 (for shutdown)
 | 
				
			||||||
 | 
					?>
 | 
				
			||||||
 | 
					<!-- DO NOT REMOVE - End PKI Status Definitions -->
 | 
				
			||||||
 | 
					<Server port="8005" shutdown="SHUTDOWN">
 | 
				
			||||||
 | 
					  <!--APR library loader. Documentation at /docs/apr.html -->
 | 
				
			||||||
 | 
					  <!-- The following Listener class has been commented out because this -->
 | 
				
			||||||
 | 
					  <!-- implementation depends upon the 'tomcatjss' JSSE module, 'JSS',  -->
 | 
				
			||||||
 | 
					  <!-- and 'NSS' rather than the 'tomcat-native' module! -->
 | 
				
			||||||
 | 
					  <!-- Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" -->
 | 
				
			||||||
 | 
					  <!--Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html -->
 | 
				
			||||||
 | 
					  <Listener className="org.apache.catalina.core.JasperListener"/>
 | 
				
			||||||
 | 
					  <!-- JMX Support for the Tomcat server. Documentation at /docs/non-existent.html -->
 | 
				
			||||||
 | 
					  <!-- The following class has been commented out because it -->
 | 
				
			||||||
 | 
					  <!-- has been EXCLUDED from the Tomcat 7 'tomcat-lib' RPM! -->
 | 
				
			||||||
 | 
					  <!-- Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" -->
 | 
				
			||||||
 | 
					  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener"/>
 | 
				
			||||||
 | 
					  <Listener className="com.netscape.cms.tomcat.PKIListener"/>
 | 
				
			||||||
 | 
					  <!-- Global JNDI resources
 | 
				
			||||||
 | 
					       Documentation at /docs/jndi-resources-howto.html
 | 
				
			||||||
 | 
					  -->
 | 
				
			||||||
 | 
					  <GlobalNamingResources>
 | 
				
			||||||
 | 
					    <!-- Editable user database that can also be used by
 | 
				
			||||||
 | 
					         UserDatabaseRealm to authenticate users
 | 
				
			||||||
 | 
					    -->
 | 
				
			||||||
 | 
					    <Resource name="UserDatabase" auth="Container" type="org.apache.catalina.UserDatabase" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" pathname="conf/tomcat-users.xml"/>
 | 
				
			||||||
 | 
					  </GlobalNamingResources>
 | 
				
			||||||
 | 
					  <!-- A "Service" is a collection of one or more "Connectors" that share
 | 
				
			||||||
 | 
					       a single "Container" Note:  A "Service" is not itself a "Container",
 | 
				
			||||||
 | 
					       so you may not define subcomponents such as "Valves" at this level.
 | 
				
			||||||
 | 
					       Documentation at /docs/config/service.html
 | 
				
			||||||
 | 
					  -->
 | 
				
			||||||
 | 
					  <Service name="Catalina">
 | 
				
			||||||
 | 
					    <!--The connectors can use a shared executor, you can define one or more named thread pools-->
 | 
				
			||||||
 | 
					    <!--
 | 
				
			||||||
 | 
					    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
 | 
				
			||||||
 | 
					        maxThreads="150" minSpareThreads="4"/>
 | 
				
			||||||
 | 
					    -->
 | 
				
			||||||
 | 
					    <!-- A "Connector" represents an endpoint by which requests are received
 | 
				
			||||||
 | 
					         and responses are returned. Documentation at :
 | 
				
			||||||
 | 
					         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
 | 
				
			||||||
 | 
					         Java AJP  Connector: /docs/config/ajp.html
 | 
				
			||||||
 | 
					         APR (HTTP/AJP) Connector: /docs/apr.html
 | 
				
			||||||
 | 
					         Define a non-SSL HTTP/1.1 Connector on port 8080
 | 
				
			||||||
 | 
					    -->
 | 
				
			||||||
 | 
					    <!-- Shared Ports:  Unsecure Port Connector -->
 | 
				
			||||||
 | 
					    <Connector name="Unsecure" port="8080" protocol="HTTP/1.1" redirectPort="8443" maxHttpHeaderSize="8192" acceptCount="100" maxThreads="150" minSpareThreads="25" enableLookups="false" connectionTimeout="80000" disableUploadTimeout="true"/>
 | 
				
			||||||
 | 
					    <!-- Define a SSL HTTP/1.1 Connector on port 8443 -->
 | 
				
			||||||
 | 
					    <!-- Shared Ports:  Agent, EE, and Admin Secure Port Connector -->
 | 
				
			||||||
 | 
					    <!-- DO NOT REMOVE - Begin define PKI secure port
 | 
				
			||||||
 | 
					    NOTE: The following 'keys' (and their assigned values) are exclusive to
 | 
				
			||||||
 | 
					          the 'tomcatjss' JSSE module:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              'enableOCSP'
 | 
				
			||||||
 | 
					              'ocspResponderURL'
 | 
				
			||||||
 | 
					              'ocspResponderCertNickname'
 | 
				
			||||||
 | 
					              'ocspCacheSize'
 | 
				
			||||||
 | 
					              'ocspMinCacheEntryDuration'
 | 
				
			||||||
 | 
					              'ocspMaxCacheEntryDuration'
 | 
				
			||||||
 | 
					              'ocspTimeout'
 | 
				
			||||||
 | 
					              'strictCiphers'
 | 
				
			||||||
 | 
					              'clientauth' (ALL lowercase)
 | 
				
			||||||
 | 
					              'sslVersionRangeStream'
 | 
				
			||||||
 | 
					              'sslVersionRangeDatagram'
 | 
				
			||||||
 | 
					              'sslRangeCiphers'
 | 
				
			||||||
 | 
					              'serverCertNickFile'
 | 
				
			||||||
 | 
					              'passwordFile'
 | 
				
			||||||
 | 
					              'passwordClass'
 | 
				
			||||||
 | 
					              'certdbDir'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          and are referenced via the value of the 'sslImplementationName' key.
 | 
				
			||||||
 | 
					    NOTE: The OCSP settings take effect globally, so it should only be set once.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      In setup where SSL clientauth="true", OCSP can be turned on by
 | 
				
			||||||
 | 
					      setting enableOCSP to true like the following:
 | 
				
			||||||
 | 
					        enableOCSP="true"
 | 
				
			||||||
 | 
					      along with changes to related settings, especially:
 | 
				
			||||||
 | 
					        ocspResponderURL=<see example in connector definition below>
 | 
				
			||||||
 | 
					        ocspResponderCertNickname=<see example in connector definition below>
 | 
				
			||||||
 | 
					      Here are the definition to all the OCSP-related settings:
 | 
				
			||||||
 | 
					        enableOCSP - turns on/off the ocsp check
 | 
				
			||||||
 | 
					        ocspResponderURL - sets the url where the ocsp requests are sent
 | 
				
			||||||
 | 
					          Make sure this URL uses the NON SSL or HTTP port for the OCSP interface.
 | 
				
			||||||
 | 
					          Ex: use 8080 instead of say 8443.
 | 
				
			||||||
 | 
					        ocspResponderCertNickname - sets the nickname of the cert that is
 | 
				
			||||||
 | 
					        either CA's signing certificate or the OCSP server's signing
 | 
				
			||||||
 | 
					        certificate.
 | 
				
			||||||
 | 
					        The CA's signing certificate should already be in the db, in
 | 
				
			||||||
 | 
					        case of the same security domain.
 | 
				
			||||||
 | 
					        In case of an ocsp signing certificate, one must import the cert
 | 
				
			||||||
 | 
					        into the subsystem's nss db and set trust. e.g.:
 | 
				
			||||||
 | 
					          certutil -d . -A -n "ocspSigningCert cert-pki-ca" -t "C,," -a -i ocspCert.b64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        If both ocspResponderURL and ocspResponderCertNickname are both unset
 | 
				
			||||||
 | 
					        all OCSP checks will be made using the URL encoded within the AIA extension
 | 
				
			||||||
 | 
					        of each cert being verified.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ocspCacheSize - sets max cache entries
 | 
				
			||||||
 | 
					        ocspMinCacheEntryDuration - sets minimum seconds to next fetch attempt
 | 
				
			||||||
 | 
					        ocspMaxCacheEntryDuration - sets maximum seconds to next fetch attempt
 | 
				
			||||||
 | 
					        ocspTimeout -sets OCSP timeout in seconds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        See <instance dir>/conf/ciphers.info
 | 
				
			||||||
 | 
					            About the TLS range related parameters
 | 
				
			||||||
 | 
					    -->
 | 
				
			||||||
 | 
					    <Connector name="Secure" port="8443" protocol="HTTP/1.1" SSLEnabled="true" sslProtocol="SSL" scheme="https" secure="true" maxHttpHeaderSize="8192" connectionTimeout="80000" keepAliveTimeout="300000" acceptCount="100" maxThreads="150" minSpareThreads="25" enableLookups="false" disableUploadTimeout="true" sslImplementationName="org.apache.tomcat.util.net.jss.JSSImplementation" enableOCSP="false" ocspResponderURL="http://ipa1.example.com:8080/ca/ocsp" ocspResponderCertNickname="ocspSigningCert cert-pki-ca" ocspCacheSize="1000" ocspMinCacheEntryDuration="7200" ocspMaxCacheEntryDuration="14400" ocspTimeout="10" strictCiphers="true" clientAuth="want" sslVersionRangeStream="tls1_1:tls1_2" sslVersionRangeDatagram="tls1_1:tls1_2" sslRangeCiphers="-TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA,-TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA,-TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA,-TLS_ECDH_RSA_WITH_AES_128_CBC_SHA,-TLS_ECDH_RSA_WITH_AES_256_CBC_SHA,-TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA,-TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,-TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,-TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,-TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,+TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,+TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,-TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,-TLS_DHE_DSS_WITH_AES_128_CBC_SHA,-TLS_DHE_DSS_WITH_AES_256_CBC_SHA,-TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,+TLS_DHE_RSA_WITH_AES_128_CBC_SHA,+TLS_DHE_RSA_WITH_AES_256_CBC_SHA,+TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,+TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,+TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,-TLS_DHE_DSS_WITH_AES_128_GCM_SHA256,-TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,+TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,-TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,+TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,-TLS_RSA_WITH_AES_128_CBC_SHA256,-TLS_RSA_WITH_AES_256_CBC_SHA256,-TLS_RSA_WITH_AES_128_GCM_SHA256,-TLS_RSA_WITH_3DES_EDE_CBC_SHA,-TLS_RSA_WITH_AES_128_CBC_SHA,-TLS_RSA_WITH_AES_256_CBC_SHA,+TLS_DHE_RSA_WITH_AES_256_GCM_SHA384,+TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,+TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,-TLS_RSA_WITH_AES_256_GCM_SHA384" serverCertNickFile="/var/lib/pki/pki-tomcat/conf/serverCertNick.conf" passwordFile="/var/lib/pki/pki-tomcat/conf/password.conf" passwordClass="org.apache.tomcat.util.net.jss.PlainPasswordFile" certdbDir="/var/lib/pki/pki-tomcat/alias"/>
 | 
				
			||||||
 | 
					    <!-- DO NOT REMOVE - End define PKI secure port -->
 | 
				
			||||||
 | 
					    <!-- Define an AJP 1.3 Connector on port 8009 -->
 | 
				
			||||||
 | 
					    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" address="localhost" requiredSecret="testSecret"/>
 | 
				
			||||||
 | 
					    <!-- An Engine represents the entry point (within Catalina) that processes
 | 
				
			||||||
 | 
					         every request.  The Engine implementation for Tomcat stand alone
 | 
				
			||||||
 | 
					         analyzes the HTTP headers included with the request, and passes them
 | 
				
			||||||
 | 
					         on to the appropriate Host (virtual host).
 | 
				
			||||||
 | 
					         Documentation at /docs/config/engine.html -->
 | 
				
			||||||
 | 
					    <!-- You should set jvmRoute to support load-balancing via AJP ie :
 | 
				
			||||||
 | 
					    <Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
 | 
				
			||||||
 | 
					    -->
 | 
				
			||||||
 | 
					    <Engine name="Catalina" defaultHost="localhost">
 | 
				
			||||||
 | 
					      <!--For clustering, please take a look at documentation at:
 | 
				
			||||||
 | 
					          /docs/cluster-howto.html  (simple how to)
 | 
				
			||||||
 | 
					          /docs/config/cluster.html (reference documentation) -->
 | 
				
			||||||
 | 
					      <!--
 | 
				
			||||||
 | 
					      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
 | 
				
			||||||
 | 
					      -->
 | 
				
			||||||
 | 
					      <!-- The request dumper valve dumps useful debugging information about
 | 
				
			||||||
 | 
					           the request and response data received and sent by Tomcat.
 | 
				
			||||||
 | 
					           Documentation at: /docs/config/valve.html -->
 | 
				
			||||||
 | 
					      <!--
 | 
				
			||||||
 | 
					      <Valve className="org.apache.catalina.valves.RequestDumperValve"/>
 | 
				
			||||||
 | 
					      -->
 | 
				
			||||||
 | 
					      <!-- This Realm uses the UserDatabase configured in the global JNDI
 | 
				
			||||||
 | 
					           resources under the key "UserDatabase".  Any edits
 | 
				
			||||||
 | 
					           that are performed against this UserDatabase are immediately
 | 
				
			||||||
 | 
					           available for use by the Realm.  -->
 | 
				
			||||||
 | 
					      <!--
 | 
				
			||||||
 | 
					      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
 | 
				
			||||||
 | 
					             resourceName="UserDatabase"/>
 | 
				
			||||||
 | 
					      -->
 | 
				
			||||||
 | 
					      <!--
 | 
				
			||||||
 | 
					      <Realm className="com.netscape.cmscore.realm.PKIRealm" />
 | 
				
			||||||
 | 
					      -->
 | 
				
			||||||
 | 
					      <!-- Define the default virtual host
 | 
				
			||||||
 | 
					           Note: XML Schema validation will not work with Xerces 2.2.
 | 
				
			||||||
 | 
					      -->
 | 
				
			||||||
 | 
					      <Host name="localhost" appBase="/var/lib/pki/pki-tomcat/webapps" unpackWARs="true" autoDeploy="true" xmlValidation="false" xmlNamespaceAware="false">
 | 
				
			||||||
 | 
					        <!--
 | 
				
			||||||
 | 
					        <Context path="/ca"
 | 
				
			||||||
 | 
					                 docBase="ca"
 | 
				
			||||||
 | 
					                 allowLinking="true">
 | 
				
			||||||
 | 
					          <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
 | 
				
			||||||
 | 
					                  virtualClasspath="/var/lib/pki/pki-tomcat/ca/webapps/ca/WEB-INF/classes;/var/lib/pki/pki-tomcat/ca/webapps/ca/WEB-INF/lib" />" />
 | 
				
			||||||
 | 
					          <JarScanner scanAllDirectories="true" />
 | 
				
			||||||
 | 
					        </Context>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Context path="/kra"
 | 
				
			||||||
 | 
					                 docBase="kra"
 | 
				
			||||||
 | 
					                 allowLinking="true">
 | 
				
			||||||
 | 
					          <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
 | 
				
			||||||
 | 
					                  virtualClasspath="/var/lib/pki/pki-tomcat/kra/webapps/kra/WEB-INF/classes;/var/lib/pki/pki-tomcat/kra/webapps/kra/WEB-INF/lib" />
 | 
				
			||||||
 | 
					          <JarScanner scanAllDirectories="true" />
 | 
				
			||||||
 | 
					        </Context>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Context path="/ocsp"
 | 
				
			||||||
 | 
					                 docBase="ocsp"
 | 
				
			||||||
 | 
					                 allowLinking="true">
 | 
				
			||||||
 | 
					          <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
 | 
				
			||||||
 | 
					                  virtualClasspath="/var/lib/pki/pki-tomcat/ocsp/webapps/ocsp/WEB-INF/classes;/var/lib/pki/pki-tomcat/ocsp/webapps/ocsp/WEB-INF/lib" />
 | 
				
			||||||
 | 
					          <JarScanner scanAllDirectories="true" />
 | 
				
			||||||
 | 
					        </Context>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <Context path="/tks"
 | 
				
			||||||
 | 
					                 docBase="tks"
 | 
				
			||||||
 | 
					                 allowLinking="true">
 | 
				
			||||||
 | 
					          <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
 | 
				
			||||||
 | 
					                  virtualClasspath="/var/lib/pki/pki-tomcat/tks/webapps/tks/WEB-INF/classes;/var/lib/pki/pki-tomcat/tks/webapps/tks/WEB-INF/lib" />
 | 
				
			||||||
 | 
					          <JarScanner scanAllDirectories="true" />
 | 
				
			||||||
 | 
					        </Context>
 | 
				
			||||||
 | 
					        -->
 | 
				
			||||||
 | 
					        <!-- SingleSignOn valve, share authentication between web applications
 | 
				
			||||||
 | 
					             Documentation at: /docs/config/valve.html -->
 | 
				
			||||||
 | 
					        <!--
 | 
				
			||||||
 | 
					        <Valve className="org.apache.catalina.authenticator.SingleSignOn" />
 | 
				
			||||||
 | 
					        -->
 | 
				
			||||||
 | 
					        <!-- Access log processes all example.
 | 
				
			||||||
 | 
					             Documentation at: /docs/config/valve.html -->
 | 
				
			||||||
 | 
					        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log." suffix=".txt" pattern="common" resolveHosts="false"/>
 | 
				
			||||||
 | 
					      </Host>
 | 
				
			||||||
 | 
					    </Engine>
 | 
				
			||||||
 | 
					  </Service>
 | 
				
			||||||
 | 
					</Server>
 | 
				
			||||||
							
								
								
									
										30
									
								
								test/test_config.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								test/test_config.yaml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					hostname: ipa1.example.com
 | 
				
			||||||
 | 
					http:
 | 
				
			||||||
 | 
					  bind_addr: 127.0.0.1
 | 
				
			||||||
 | 
					  port: 8832
 | 
				
			||||||
 | 
					ldap:
 | 
				
			||||||
 | 
					  address: ldap://127.0.0.1:10389
 | 
				
			||||||
 | 
					  connect_method: Unsecure
 | 
				
			||||||
 | 
					  base_dn: dc=example,dc=com
 | 
				
			||||||
 | 
					  bind_dn: uid=freeipa-health-metrics,cn=users,cn=accounts,dc=example,dc=com
 | 
				
			||||||
 | 
					  bind_password: testPassword
 | 
				
			||||||
 | 
					  search_size_limit: 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					freeipa:
 | 
				
			||||||
 | 
					  krb5_realm: EXAMPLE.COM
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  host: localhost:8831
 | 
				
			||||||
 | 
					  insecure_skip_verify: true
 | 
				
			||||||
 | 
					  username: freeipa-health-metrics
 | 
				
			||||||
 | 
					  password: testPassword
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  disabled_metrics:
 | 
				
			||||||
 | 
					    - group_members
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					krb5_sysconfig_path: test/krb5kdc
 | 
				
			||||||
 | 
					pki_tomcat_server_xml: test/server.xml
 | 
				
			||||||
 | 
					httpd_pki_proxy_conf: test/ipa-pki-proxy.conf
 | 
				
			||||||
 | 
					kinit_bin: test/kinit
 | 
				
			||||||
 | 
					klist_bin: test/klist
 | 
				
			||||||
 | 
					ipa_getcert_bin: test/ipa-getcert
 | 
				
			||||||
							
								
								
									
										28
									
								
								test_utils.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								test_utils.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/kylelemons/godebug/diff"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Generate a diff between a string a a file.
 | 
				
			||||||
 | 
					func FileDiff(s string, fileToDiff string) (string, error) {
 | 
				
			||||||
 | 
						// Open file.
 | 
				
			||||||
 | 
						f, err := os.Open(fileToDiff)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("error opening file %s: %s", fileToDiff, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Close file after done.
 | 
				
			||||||
 | 
						defer f.Close()
 | 
				
			||||||
 | 
						// Read all data from file.
 | 
				
			||||||
 | 
						expected, err := io.ReadAll(f)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return "", fmt.Errorf("error reading file %s: %s", fileToDiff, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Compare expected file against provided string.
 | 
				
			||||||
 | 
						return diff.Diff(string(expected), s), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user