First commit

This commit is contained in:
GRMrGecko 2023-09-05 11:46:19 -05:00
commit 245788f3f8
57 changed files with 5033 additions and 0 deletions

20
.github/workflows/release.yaml vendored Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
config.yaml
freeipa-health-metrics

7
.vscode/launch.json vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View 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
View 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"
}

View 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
View 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

View 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

View 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"
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
KRB5KDC_ARGS='-w 5555'
KRB5REALM=EXAMPLE.COM
KRB5KDC_ARGS='-w 5555'

59
test/ldap.metrics Normal file
View 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
View 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

View 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
View 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

View 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
View 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
View 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

View File

@ -0,0 +1,3 @@
# hostgroups, accounts, example.com
dn: cn=hostgroups,cn=accounts,dc=example,dc=com
numSubordinates: 2

View 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
View 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
View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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
}