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