Skip to content

Commit

Permalink
Split config of auth and modules
Browse files Browse the repository at this point in the history
Allow configuration of auth/version parameters separately from the walk
and metrics in the generator and exporter configuration.

Fixes: #619

Signed-off-by: SuperQ <[email protected]>
  • Loading branch information
SuperQ committed Mar 26, 2023
1 parent 87bf22c commit 3fb0b34
Show file tree
Hide file tree
Showing 7 changed files with 57,416 additions and 57,275 deletions.
27 changes: 14 additions & 13 deletions collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,19 @@ type ScrapeResults struct {
retries uint64
}

func ScrapeTarget(ctx context.Context, target string, config *config.Module, logger log.Logger) (ScrapeResults, error) {
func ScrapeTarget(ctx context.Context, target string, auth *config.Auth, module *config.Module, logger log.Logger) (ScrapeResults, error) {
results := ScrapeResults{}
// Set the options.
snmp := gosnmp.GoSNMP{}
snmp.Context = ctx
snmp.MaxRepetitions = config.WalkParams.MaxRepetitions
snmp.Retries = *config.WalkParams.Retries
snmp.Timeout = config.WalkParams.Timeout
snmp.UseUnconnectedUDPSocket = config.WalkParams.UseUnconnectedUDPSocket
snmp.MaxRepetitions = module.WalkParams.MaxRepetitions
snmp.Retries = *module.WalkParams.Retries
snmp.Timeout = module.WalkParams.Timeout
snmp.UseUnconnectedUDPSocket = module.WalkParams.UseUnconnectedUDPSocket
snmp.LocalAddr = *srcAddress

// Allow a set of OIDs that aren't in a strictly increasing order
if config.WalkParams.AllowNonIncreasingOIDs {
if module.WalkParams.AllowNonIncreasingOIDs {
snmp.AppOpts = make(map[string]interface{})
snmp.AppOpts["c"] = true
}
Expand Down Expand Up @@ -158,7 +158,7 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
}

// Configure auth.
config.WalkParams.ConfigureSNMP(&snmp)
auth.ConfigureSNMP(&snmp)

// Do the actual walk.
err := snmp.Connect()
Expand All @@ -170,8 +170,8 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
}
defer snmp.Conn.Close()

getOids := config.Get
maxOids := int(config.WalkParams.MaxRepetitions)
getOids := module.Get
maxOids := int(module.WalkParams.MaxRepetitions)
// Max Repetition can be 0, maxOids cannot. SNMPv1 can only report one OID error per call.
if maxOids == 0 || snmp.Version == gosnmp.Version1 {
maxOids = 1
Expand Down Expand Up @@ -213,7 +213,7 @@ func ScrapeTarget(ctx context.Context, target string, config *config.Module, log
getOids = getOids[oids:]
}

for _, subtree := range config.Walk {
for _, subtree := range module.Walk {
var pdus []gosnmp.SnmpPDU
level.Debug(logger).Log("msg", "Walking subtree", "oid", subtree)
walkStart := time.Now()
Expand Down Expand Up @@ -261,12 +261,13 @@ func buildMetricTree(metrics []*config.Metric) *MetricNode {
type collector struct {
ctx context.Context
target string
auth *config.Auth
module *config.Module
logger log.Logger
}

func New(ctx context.Context, target string, module *config.Module, logger log.Logger) *collector {
return &collector{ctx: ctx, target: target, module: module, logger: logger}
func New(ctx context.Context, target string, auth *config.Auth, module *config.Module, logger log.Logger) *collector {
return &collector{ctx: ctx, target: target, auth: auth, module: module, logger: logger}
}

// Describe implements Prometheus.Collector.
Expand All @@ -277,7 +278,7 @@ func (c collector) Describe(ch chan<- *prometheus.Desc) {
// Collect implements Prometheus.Collector.
func (c collector) Collect(ch chan<- prometheus.Metric) {
start := time.Now()
results, err := ScrapeTarget(c.ctx, c.target, c.module, c.logger)
results, err := ScrapeTarget(c.ctx, c.target, c.auth, c.module, c.logger)
if err != nil {
level.Info(c.logger).Log("msg", "Error scraping target", "err", err)
ch <- prometheus.NewInvalidMetric(prometheus.NewDesc("snmp_error", "Error scraping target", nil, nil), err)
Expand Down
100 changes: 54 additions & 46 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,12 @@ var (
SecurityLevel: "noAuthNoPriv",
AuthProtocol: "MD5",
PrivProtocol: "DES",
Version: 2,
}
DefaultWalkParams = WalkParams{
Version: 2,
MaxRepetitions: 25,
Retries: &defaultRetries,
Timeout: time.Second * 5,
Auth: DefaultAuth,
UseUnconnectedUDPSocket: false,
AllowNonIncreasingOIDs: false,
}
Expand All @@ -63,14 +62,15 @@ var (
)

// Config for the snmp_exporter.
type Config map[string]*Module
type Config struct {
Auths map[string]*Auth `yaml:"auths",omitempty"`
Modules map[string]*Module `yaml:"modules",omitempty"`
}

type WalkParams struct {
Version int `yaml:"version,omitempty"`
MaxRepetitions uint32 `yaml:"max_repetitions,omitempty"`
Retries *int `yaml:"retries,omitempty"`
Timeout time.Duration `yaml:"timeout,omitempty"`
Auth Auth `yaml:"auth,omitempty"`
UseUnconnectedUDPSocket bool `yaml:"use_unconnected_udp_socket,omitempty"`
AllowNonIncreasingOIDs bool `yaml:"allow_nonincreasing_oids,omitempty"`
}
Expand All @@ -89,43 +89,11 @@ func (c *Module) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal((*plain)(c)); err != nil {
return err
}

wp := c.WalkParams

if wp.Version < 1 || wp.Version > 3 {
return fmt.Errorf("SNMP version must be 1, 2 or 3. Got: %d", wp.Version)
}
if wp.Version == 3 {
switch wp.Auth.SecurityLevel {
case "authPriv":
if wp.Auth.PrivPassword == "" {
return fmt.Errorf("priv password is missing, required for SNMPv3 with priv")
}
if wp.Auth.PrivProtocol != "DES" && wp.Auth.PrivProtocol != "AES" && wp.Auth.PrivProtocol != "AES192" && wp.Auth.PrivProtocol != "AES192C" && wp.Auth.PrivProtocol != "AES256" && wp.Auth.PrivProtocol != "AES256C" {
return fmt.Errorf("priv protocol must be DES or AES")
}
fallthrough
case "authNoPriv":
if wp.Auth.Password == "" {
return fmt.Errorf("auth password is missing, required for SNMPv3 with auth")
}
if wp.Auth.AuthProtocol != "MD5" && wp.Auth.AuthProtocol != "SHA" && wp.Auth.AuthProtocol != "SHA224" && wp.Auth.AuthProtocol != "SHA256" && wp.Auth.AuthProtocol != "SHA384" && wp.Auth.AuthProtocol != "SHA512" {
return fmt.Errorf("auth protocol must be SHA or MD5")
}
fallthrough
case "noAuthNoPriv":
if wp.Auth.Username == "" {
return fmt.Errorf("auth username is missing, required for SNMPv3")
}
default:
return fmt.Errorf("security level must be one of authPriv, authNoPriv or noAuthNoPriv")
}
}
return nil
}

// ConfigureSNMP sets the various version and auth settings.
func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
func (c Auth) ConfigureSNMP(g *gosnmp.GoSNMP) {
switch c.Version {
case 1:
g.Version = gosnmp.Version1
Expand All @@ -134,16 +102,16 @@ func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
case 3:
g.Version = gosnmp.Version3
}
g.Community = string(c.Auth.Community)
g.ContextName = c.Auth.ContextName
g.Community = string(c.Community)
g.ContextName = c.ContextName

// v3 security settings.
g.SecurityModel = gosnmp.UserSecurityModel
usm := &gosnmp.UsmSecurityParameters{
UserName: c.Auth.Username,
UserName: c.Username,
}
auth, priv := false, false
switch c.Auth.SecurityLevel {
switch c.SecurityLevel {
case "noAuthNoPriv":
g.MsgFlags = gosnmp.NoAuthNoPriv
case "authNoPriv":
Expand All @@ -155,8 +123,8 @@ func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
priv = true
}
if auth {
usm.AuthenticationPassphrase = string(c.Auth.Password)
switch c.Auth.AuthProtocol {
usm.AuthenticationPassphrase = string(c.Password)
switch c.AuthProtocol {
case "SHA":
usm.AuthenticationProtocol = gosnmp.SHA
case "SHA224":
Expand All @@ -172,8 +140,8 @@ func (c WalkParams) ConfigureSNMP(g *gosnmp.GoSNMP) {
}
}
if priv {
usm.PrivacyPassphrase = string(c.Auth.PrivPassword)
switch c.Auth.PrivProtocol {
usm.PrivacyPassphrase = string(c.PrivPassword)
switch c.PrivProtocol {
case "DES":
usm.PrivacyProtocol = gosnmp.DES
case "AES":
Expand Down Expand Up @@ -245,6 +213,46 @@ type Auth struct {
PrivProtocol string `yaml:"priv_protocol,omitempty"`
PrivPassword Secret `yaml:"priv_password,omitempty"`
ContextName string `yaml:"context_name,omitempty"`
Version int `yaml:"version,omitempty"`
}

func (c *Auth) UnmarshalYAML(unmarshal func(interface{}) error) error {
*c = DefaultAuth
type plain Auth
if err := unmarshal((*plain)(c)); err != nil {
return err
}

if c.Version < 1 || c.Version > 3 {
return fmt.Errorf("SNMP version must be 1, 2 or 3. Got: %d", c.Version)
}
if c.Version == 3 {
switch c.SecurityLevel {
case "authPriv":
if c.PrivPassword == "" {
return fmt.Errorf("priv password is missing, required for SNMPv3 with priv")
}
if c.PrivProtocol != "DES" && c.PrivProtocol != "AES" && c.PrivProtocol != "AES192" && c.PrivProtocol != "AES192C" && c.PrivProtocol != "AES256" && c.PrivProtocol != "AES256C" {
return fmt.Errorf("priv protocol must be DES or AES")
}
fallthrough
case "authNoPriv":
if c.Password == "" {
return fmt.Errorf("auth password is missing, required for SNMPv3 with auth")
}
if c.AuthProtocol != "MD5" && c.AuthProtocol != "SHA" && c.AuthProtocol != "SHA224" && c.AuthProtocol != "SHA256" && c.AuthProtocol != "SHA384" && c.AuthProtocol != "SHA512" {
return fmt.Errorf("auth protocol must be SHA or MD5")
}
fallthrough
case "noAuthNoPriv":
if c.Username == "" {
return fmt.Errorf("auth username is missing, required for SNMPv3")
}
default:
return fmt.Errorf("security level must be one of authPriv, authNoPriv or noAuthNoPriv")
}
}
return nil
}

type RegexpExtract struct {
Expand Down
1 change: 1 addition & 0 deletions generator/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

// The generator config.
type Config struct {
Auths map[string]*config.Auth `yaml:"auths"`
Modules map[string]*ModuleConfig `yaml:"modules"`
}

Expand Down
12 changes: 6 additions & 6 deletions generator/generator.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
auths:
public:
version: 2
public_v1:
version: 1

modules:
# Default IF-MIB interfaces table with ifIndex.
if_mib:
Expand Down Expand Up @@ -60,7 +66,6 @@ modules:
# Guide: http://www.apc.com/salestools/ASTE-6Z5QEY/ASTE-6Z5QEY_R0_EN.pdf
# Download site: http://www.apc.com/us/en/tools/download/index.cfm
apcups:
version: 1
walk:
- sysUpTime
- interfaces
Expand Down Expand Up @@ -315,7 +320,6 @@ modules:
# https://www.ui.com/downloads/firmwares/airfiber5X/v4.0.5/UBNT-MIB.txt
#
ubiquiti_airfiber:
version: 1
walk:
- sysUpTime
- interfaces
Expand All @@ -330,7 +334,6 @@ modules:
# https://dl.ubnt.com/firmwares/airos-ubnt-mib/ubnt-mib.zip
#
ubiquiti_airmax:
version: 1
walk:
- sysUpTime
- interfaces
Expand Down Expand Up @@ -536,8 +539,6 @@ modules:
overrides:
outletOperationalState:
type: EnumAsStateSet
auth:
community: raritan_public

# Wiener Power Supply Module MPod
#
Expand Down Expand Up @@ -662,7 +663,6 @@ modules:
#
# https://www.cyberpowersystems.com/product/software/mib-files/mib-v29/
cyberpower:
version: 1
walk:
- 1.3.6.1.4.1.3808.1.1.1 # ups
- 1.3.6.1.4.1.3808.1.1.4 # environmentSensor
Expand Down
8 changes: 5 additions & 3 deletions generator/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger)
}

outputConfig := config.Config{}
outputConfig.Auths = cfg.Auths
outputConfig.Modules = make(map[string]*config.Module, len(cfg.Modules))
for name, m := range cfg.Modules {
level.Info(logger).Log("msg", "Generating config for module", "module", name)
// Give each module a copy of the tree so that it can be modified.
Expand All @@ -61,9 +63,9 @@ func generateConfig(nodes *Node, nameToNode map[string]*Node, logger log.Logger)
if err != nil {
return err
}
outputConfig[name] = out
outputConfig[name].WalkParams = m.WalkParams
level.Info(logger).Log("msg", "Generated metrics", "module", name, "metrics", len(outputConfig[name].Metrics))
outputConfig.Modules[name] = out
outputConfig.Modules[name].WalkParams = m.WalkParams
level.Info(logger).Log("msg", "Generated metrics", "module", name, "metrics", len(outputConfig.Modules[name].Metrics))
}

config.DoNotHideSecrets = true
Expand Down
Loading

0 comments on commit 3fb0b34

Please sign in to comment.