claranet/wallix_bastion_exporter

View on GitHub
exporter/exporter.go

Summary

Maintainability
A
0 mins
Test Coverage
package exporter

import (
    "fmt"
    "log"
    "net/http"
    "sync"

    "github.com/claranet/wallix_bastion_exporter/config"
    "github.com/claranet/wallix_bastion_exporter/httpclient"
    "github.com/claranet/wallix_bastion_exporter/wallix"
    "github.com/prometheus/client_golang/prometheus"
)

const (
    // ony used for metrics based on past timeframe like the closed sessions.
    sessionsClosedMinutes = 5 // TODO expose as config parameter?
    // prometheus exporter Namespace.
    Namespace = "wallix_bastion"
)

var (
    metricUp = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "up"),
        "Was able to request and authenticate to Wallix Bastion API successfully.",
        nil, nil,
    )
    metricUsers = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "users"),
        "Current number of users.",
        nil, nil,
    )
    metricGroups = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "groups"),
        "Current number of groups.",
        nil, nil,
    )
    metricDevices = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "devices"),
        "Current number of devices.",
        nil, nil,
    )
    metricSessions = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "sessions"),
        fmt.Sprintf("Number of sessions for the last %dm.", sessionsClosedMinutes),
        []string{"status"}, nil,
    )
    metricTargets = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "targets"),
        "Current number of targets.",
        []string{"type"}, nil,
    )
    metricEncryptionStatus = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "encryption_status"),
        "Encryption status (need_setup=0, ready=1, need_passphrase=2).",
        []string{"status", "security_level"}, nil,
    )
    metricEncryptionSecurityLevel = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "encryption_security_level"),
        "Encryption security level (need_setup=0, passphrase_defined=1, passphrase_not_used=2, [hidden]=-1).",
        []string{"security_level", "status"}, nil,
    )
    metricLicenseIsExpired = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "license_is_expired"),
        "Is the Wallix is expired (0=false, 1=true).",
        nil, nil,
    )
    metricLicensePrimaryPct = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "license_primary_ratio"),
        "License usage percentage of primary.",
        nil, nil,
    )
    metricLicenseSecondaryPct = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "license_secondary_ratio"),
        "License usage percentage of secondary.",
        nil, nil,
    )
    metricLicenseNameUserPct = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "license_named_user_ratio"),
        "License usage percentage of named user.",
        nil, nil,
    )
    metricLicenseResourcePct = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "license_resource_ratio"),
        "License usage percentage of resource.",
        nil, nil,
    )
    metricLicenseWaapmPct = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "license_waapm_ratio"),
        "License usage percentage of waapm.",
        nil, nil,
    )
    metricLicensePmTargetPct = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "license_pm_target_ratio"),
        "License usage percentage of pm target.",
        nil, nil,
    )
    metricLicenseSmTargetPct = prometheus.NewDesc(
        prometheus.BuildFQName(Namespace, "", "license_sm_target_ratio"),
        "License usage percentage of sm target.",
        nil, nil,
    )
)

type Exporter struct {
    Config config.Config
}

func NewExporter(config config.Config) *Exporter {
    return &Exporter{
        Config: config,
    }
}

func (e *Exporter) Describe(metricsChannel chan<- *prometheus.Desc) {
    metricsChannel <- metricUp
    metricsChannel <- metricUsers
    metricsChannel <- metricGroups
    metricsChannel <- metricDevices
    metricsChannel <- metricSessions
    metricsChannel <- metricTargets
    metricsChannel <- metricEncryptionStatus
    metricsChannel <- metricEncryptionSecurityLevel
}

func (e *Exporter) Collect(metricsChannel chan<- prometheus.Metric) {
    httpConfig := httpclient.HTTPConfig{
        SkipVerify: e.Config.SkipVerify,
        Timeout:    e.Config.Timeout,
        Headers: map[string]string{
            "User-Agent": "prometheus_exporter_" + Namespace,
        },
        // Using a cookie speed up metrics fetch by avoiding basic auth on every requests
        CookieManager: true,
    }
    client, err := httpConfig.Build()
    if err != nil {
        log.Println(fmt.Errorf("init exporter failed: %w", err))

        return
    }

    err = e.AuthenticateWallixAPI(metricsChannel, client)
    if err != nil {
        log.Println(fmt.Errorf("determine up metric failed: %w", err))

        return
    }

    e.FetchWallixMetrics(metricsChannel, client)
}

// The first request done to wallix API. It allows to:
// - determine "up" metric for the exporter
// - prevent trying to fetch other metrics if down
// - retrieve the cookie to not have to authenticate subsequent requests
// Notice it uses "POST" methode in contrast to all other requests.
func (e *Exporter) AuthenticateWallixAPI(metricsChannel chan<- prometheus.Metric, client *http.Client) (err error) {
    err = wallix.Authenticate(
        client,
        e.Config.ScrapeURI,
        e.Config.WallixUsername,
        e.Config.WallixPassword,
    )
    if err != nil {
        metricsChannel <- prometheus.MustNewConstMetric(
            metricUp, prometheus.GaugeValue, 0,
        )

        return fmt.Errorf("wallix authentication failed: %w", err)
    }

    metricsChannel <- prometheus.MustNewConstMetric(
        metricUp, prometheus.GaugeValue, 1,
    )

    return nil
}

// All other metrics fetched from the API essentially
// by counting the number of elements of list returned
// by different routes.
func (e *Exporter) FetchWallixMetrics(
    metricsChannel chan<- prometheus.Metric, client *http.Client,
) {
    var wg sync.WaitGroup

    wg.Add(1)
    go e.gatherMetricsUsers(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsGroups(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsDevices(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsTargetsSessionAccounts(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsTargetsSessionAccountMappings(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsTargetsSessionInteractiveLogins(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsTargetsSessionScenarioAccounts(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsTargetsPasswordRetrievalAccounts(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsEncryption(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsLicense(&wg, metricsChannel, client)
    wg.Add(1)
    go e.gatherMetricsSessions(&wg, metricsChannel, client)

    wg.Wait()
}