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