From fa8d28c181ccc0065ab9753d502ea1f4e1e4c1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Tue, 23 Jul 2024 13:02:25 +0200 Subject: [PATCH] license collector (#1524) --- README.md | 1 + docs/collector.license.md | 53 +++++++++++++++ pkg/collector/collector.go | 3 + pkg/collector/config.go | 3 + pkg/collector/license/license.go | 95 +++++++++++++++++++++++++++ pkg/collector/license/license_test.go | 12 ++++ pkg/collector/map.go | 2 + pkg/headers/slc/slc.go | 40 +++++++++++ 8 files changed, 209 insertions(+) create mode 100644 docs/collector.license.md create mode 100644 pkg/collector/license/license.go create mode 100644 pkg/collector/license/license_test.go create mode 100644 pkg/headers/slc/slc.go diff --git a/README.md b/README.md index 4a788aebf..526403b80 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Name | Description | Enabled by default [fsrmquota](docs/collector.fsrmquota.md) | Microsoft File Server Resource Manager (FSRM) Quotas collector | [hyperv](docs/collector.hyperv.md) | Hyper-V hosts | [iis](docs/collector.iis.md) | IIS sites and applications | +[license](docs/collector.license.md) | Windows license status | [logical_disk](docs/collector.logical_disk.md) | Logical disks, disk I/O | ✓ [logon](docs/collector.logon.md) | User logon sessions | [memory](docs/collector.memory.md) | Memory usage metrics | diff --git a/docs/collector.license.md b/docs/collector.license.md new file mode 100644 index 000000000..176c4efce --- /dev/null +++ b/docs/collector.license.md @@ -0,0 +1,53 @@ +# license collector + +The license collector exposes metrics about the Windows license status. + +||| +-|- +Metric name prefix | `license` +Data source | Win32 +Enabled by default? | No + +## Flags + +None + +## Metrics + +| Name | Description | Type | Labels | +|--------------------------|----------------|-------|---------| +| `windows_license_status` | license status | gauge | `state` | + +### Example metric + +``` +# HELP windows_license_status Status of windows license +# TYPE windows_license_status gauge +windows_license_status{state="genuine"} 1 +windows_license_status{state="invalid_license"} 0 +windows_license_status{state="last"} 0 +windows_license_status{state="offline"} 0 +windows_license_status{state="tampered"} 0 +``` + + +## Useful queries + +Show if the license is genuine + +``` +windows_license_status{state="genuine"} +``` + +## Alerting examples +**prometheus.rules** +```yaml + - alert: "WindowsLicense" + expr: 'windows_license_status{state="genuine"} == 0' + for: "10m" + labels: + severity: "high" + annotations: + summary: "Windows system license is not genuine" + description: "The Windows system license is not genuine. Please check the license status." +``` diff --git a/pkg/collector/collector.go b/pkg/collector/collector.go index 35ca12e7d..ed9dd7c3b 100644 --- a/pkg/collector/collector.go +++ b/pkg/collector/collector.go @@ -8,6 +8,7 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/go-kit/log" + "github.com/prometheus-community/windows_exporter/pkg/collector/ad" "github.com/prometheus-community/windows_exporter/pkg/collector/adcs" "github.com/prometheus-community/windows_exporter/pkg/collector/adfs" @@ -23,6 +24,7 @@ import ( "github.com/prometheus-community/windows_exporter/pkg/collector/exchange" "github.com/prometheus-community/windows_exporter/pkg/collector/hyperv" "github.com/prometheus-community/windows_exporter/pkg/collector/iis" + "github.com/prometheus-community/windows_exporter/pkg/collector/license" "github.com/prometheus-community/windows_exporter/pkg/collector/logical_disk" "github.com/prometheus-community/windows_exporter/pkg/collector/logon" "github.com/prometheus-community/windows_exporter/pkg/collector/memory" @@ -103,6 +105,7 @@ func NewWithConfig(logger log.Logger, config Config) Collectors { collectors[exchange.Name] = exchange.New(logger, &config.Fsrmquota) collectors[hyperv.Name] = hyperv.New(logger, &config.Hyperv) collectors[iis.Name] = iis.New(logger, &config.Iis) + collectors[license.Name] = license.New(logger, &config.License) collectors[logical_disk.Name] = logical_disk.New(logger, &config.LogicalDisk) collectors[logon.Name] = logon.New(logger, &config.Logon) collectors[memory.Name] = memory.New(logger, &config.Memory) diff --git a/pkg/collector/config.go b/pkg/collector/config.go index ecf10f77d..5d51841b9 100644 --- a/pkg/collector/config.go +++ b/pkg/collector/config.go @@ -16,6 +16,7 @@ import ( "github.com/prometheus-community/windows_exporter/pkg/collector/exchange" "github.com/prometheus-community/windows_exporter/pkg/collector/hyperv" "github.com/prometheus-community/windows_exporter/pkg/collector/iis" + "github.com/prometheus-community/windows_exporter/pkg/collector/license" "github.com/prometheus-community/windows_exporter/pkg/collector/logical_disk" "github.com/prometheus-community/windows_exporter/pkg/collector/logon" "github.com/prometheus-community/windows_exporter/pkg/collector/memory" @@ -74,6 +75,7 @@ type Config struct { Fsrmquota exchange.Config `yaml:"fsrmquota"` Hyperv hyperv.Config `yaml:"hyperv"` Iis iis.Config `yaml:"iis"` + License license.Config `yaml:"license"` LogicalDisk logical_disk.Config `yaml:"logical_disk"` Logon logon.Config `yaml:"logon"` Memory memory.Config `yaml:"memory"` @@ -133,6 +135,7 @@ var ConfigDefaults = Config{ Fsrmquota: exchange.ConfigDefaults, Hyperv: hyperv.ConfigDefaults, Iis: iis.ConfigDefaults, + License: license.ConfigDefaults, LogicalDisk: logical_disk.ConfigDefaults, Logon: logon.ConfigDefaults, Memory: memory.ConfigDefaults, diff --git a/pkg/collector/license/license.go b/pkg/collector/license/license.go new file mode 100644 index 000000000..1eac27cf6 --- /dev/null +++ b/pkg/collector/license/license.go @@ -0,0 +1,95 @@ +//go:build windows + +package license + +import ( + "github.com/alecthomas/kingpin/v2" + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/prometheus/client_golang/prometheus" + + "github.com/prometheus-community/windows_exporter/pkg/headers/slc" + "github.com/prometheus-community/windows_exporter/pkg/types" +) + +const Name = "license" + +var labelMap = map[slc.SL_GENUINE_STATE]string{ + slc.SL_GEN_STATE_IS_GENUINE: "genuine", + slc.SL_GEN_STATE_INVALID_LICENSE: "invalid_license", + slc.SL_GEN_STATE_TAMPERED: "tampered", + slc.SL_GEN_STATE_OFFLINE: "offline", + slc.SL_GEN_STATE_LAST: "last", +} + +type Config struct{} + +var ConfigDefaults = Config{} + +// A collector is a Prometheus collector for WMI Win32_PerfRawData_DNS_DNS metrics +type collector struct { + logger log.Logger + + LicenseStatus *prometheus.Desc +} + +func New(logger log.Logger, _ *Config) types.Collector { + c := &collector{} + c.SetLogger(logger) + return c +} + +func NewWithFlags(_ *kingpin.Application) types.Collector { + return &collector{} +} + +func (c *collector) GetName() string { + return Name +} + +func (c *collector) SetLogger(logger log.Logger) { + c.logger = log.With(logger, "collector", Name) +} + +func (c *collector) GetPerfCounter() ([]string, error) { + return []string{}, nil +} + +func (c *collector) Build() error { + c.LicenseStatus = prometheus.NewDesc( + prometheus.BuildFQName(types.Namespace, Name, "status"), + "Status of windows license", + []string{"state"}, + nil, + ) + + return nil +} + +// Collect sends the metric values for each metric +// to the provided prometheus Metric channel. +func (c *collector) Collect(_ *types.ScrapeContext, ch chan<- prometheus.Metric) error { + if err := c.collect(ch); err != nil { + _ = level.Error(c.logger).Log("msg", "failed collecting license metrics", "err", err) + return err + } + return nil +} + +func (c *collector) collect(ch chan<- prometheus.Metric) error { + status, err := slc.SLIsWindowsGenuineLocal() + if err != nil { + return err + } + + for k, v := range labelMap { + val := 0.0 + if status == k { + val = 1.0 + } + + ch <- prometheus.MustNewConstMetric(c.LicenseStatus, prometheus.GaugeValue, val, v) + } + + return nil +} diff --git a/pkg/collector/license/license_test.go b/pkg/collector/license/license_test.go new file mode 100644 index 000000000..2ee6db7f9 --- /dev/null +++ b/pkg/collector/license/license_test.go @@ -0,0 +1,12 @@ +package license_test + +import ( + "testing" + + "github.com/prometheus-community/windows_exporter/pkg/collector/license" + "github.com/prometheus-community/windows_exporter/pkg/testutils" +) + +func BenchmarkCollector(b *testing.B) { + testutils.FuncBenchmarkCollector(b, license.Name, license.NewWithFlags) +} diff --git a/pkg/collector/map.go b/pkg/collector/map.go index 7c3099182..a0a059256 100644 --- a/pkg/collector/map.go +++ b/pkg/collector/map.go @@ -17,6 +17,7 @@ import ( "github.com/prometheus-community/windows_exporter/pkg/collector/fsrmquota" "github.com/prometheus-community/windows_exporter/pkg/collector/hyperv" "github.com/prometheus-community/windows_exporter/pkg/collector/iis" + "github.com/prometheus-community/windows_exporter/pkg/collector/license" "github.com/prometheus-community/windows_exporter/pkg/collector/logical_disk" "github.com/prometheus-community/windows_exporter/pkg/collector/logon" "github.com/prometheus-community/windows_exporter/pkg/collector/memory" @@ -78,6 +79,7 @@ var Map = map[string]types.CollectorBuilderWithFlags{ fsrmquota.Name: fsrmquota.NewWithFlags, hyperv.Name: hyperv.NewWithFlags, iis.Name: iis.NewWithFlags, + license.Name: license.NewWithFlags, logical_disk.Name: logical_disk.NewWithFlags, logon.Name: logon.NewWithFlags, memory.Name: memory.NewWithFlags, diff --git a/pkg/headers/slc/slc.go b/pkg/headers/slc/slc.go new file mode 100644 index 000000000..b441aff35 --- /dev/null +++ b/pkg/headers/slc/slc.go @@ -0,0 +1,40 @@ +package slc + +import ( + "errors" + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + slc = windows.NewLazySystemDLL("slc.dll") + procSLIsWindowsGenuineLocal = slc.NewProc("SLIsWindowsGenuineLocal") +) + +// Define SL_GENUINE_STATE enumeration +// https://learn.microsoft.com/en-us/windows/win32/api/slpublic/ne-slpublic-sl_genuine_state +type SL_GENUINE_STATE uint32 + +const ( + SL_GEN_STATE_IS_GENUINE SL_GENUINE_STATE = iota + SL_GEN_STATE_INVALID_LICENSE + SL_GEN_STATE_TAMPERED + SL_GEN_STATE_OFFLINE + SL_GEN_STATE_LAST +) + +// SLIsWindowsGenuineLocal function wrapper +func SLIsWindowsGenuineLocal() (SL_GENUINE_STATE, error) { + var genuineState SL_GENUINE_STATE + + _, _, err := procSLIsWindowsGenuineLocal.Call( + uintptr(unsafe.Pointer(&genuineState)), + ) + + if !errors.Is(err, windows.NTE_OP_OK) { + return 0, err + } + + return genuineState, nil +}