feat(config): add HTTP basic auth for metrics

Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
Xe Iaso
2026-04-22 23:16:20 -04:00
parent 40de0bf9f4
commit 2cbc1b0917
2 changed files with 122 additions and 16 deletions
+33
View File
@@ -12,6 +12,7 @@ import (
var ( var (
ErrInvalidMetricsConfig = errors.New("config: invalid metrics configuration") ErrInvalidMetricsConfig = errors.New("config: invalid metrics configuration")
ErrInvalidMetricsTLSConfig = errors.New("config: invalid metrics TLS configuration") ErrInvalidMetricsTLSConfig = errors.New("config: invalid metrics TLS configuration")
ErrInvalidMetricsBasicAuthConfig = errors.New("config: invalid metrics basic auth configuration")
ErrNoMetricsBind = errors.New("config.Metrics: must define bind") ErrNoMetricsBind = errors.New("config.Metrics: must define bind")
ErrNoMetricsNetwork = errors.New("config.Metrics: must define network") ErrNoMetricsNetwork = errors.New("config.Metrics: must define network")
ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets") ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets")
@@ -22,6 +23,8 @@ var (
ErrInvalidMetricsTLSKeypair = errors.New("config.Metrics.TLS: keypair is invalid") ErrInvalidMetricsTLSKeypair = errors.New("config.Metrics.TLS: keypair is invalid")
ErrInvalidMetricsCACertificate = errors.New("config.Metrics.TLS: invalid CA certificate") ErrInvalidMetricsCACertificate = errors.New("config.Metrics.TLS: invalid CA certificate")
ErrCantReadFile = errors.New("config: can't read required file") ErrCantReadFile = errors.New("config: can't read required file")
ErrNoMetricsBasicAuthUsername = errors.New("config.Metrics.BasicAuth: must define username")
ErrNoMetricsBasicAuthPassword = errors.New("config.Metrics.BasicAuth: must define password")
) )
type Metrics struct { type Metrics struct {
@@ -29,6 +32,7 @@ type Metrics struct {
Network string `json:"network" yaml:"network"` Network string `json:"network" yaml:"network"`
SocketMode string `json:"socketMode" yaml:"socketMode"` SocketMode string `json:"socketMode" yaml:"socketMode"`
TLS *MetricsTLS `json:"tls" yaml:"tls"` TLS *MetricsTLS `json:"tls" yaml:"tls"`
BasicAuth *MetricsBasicAuth `json:"basicAuth" yaml:"basicAuth"`
} }
func (m *Metrics) Valid() error { func (m *Metrics) Valid() error {
@@ -62,6 +66,12 @@ func (m *Metrics) Valid() error {
} }
} }
if m.BasicAuth != nil {
if err := m.BasicAuth.Valid(); err != nil {
errs = append(errs, err)
}
}
if len(errs) != 0 { if len(errs) != 0 {
return errors.Join(ErrInvalidMetricsConfig, errors.Join(errs...)) return errors.Join(ErrInvalidMetricsConfig, errors.Join(errs...))
} }
@@ -131,3 +141,26 @@ func canReadFile(fname string) error {
return nil return nil
} }
type MetricsBasicAuth struct {
Username string `json:"username" yaml:"username"`
Password string `json:"password" yaml:"password"`
}
func (mba *MetricsBasicAuth) Valid() error {
var errs []error
if mba.Username == "" {
errs = append(errs, ErrNoMetricsBasicAuthUsername)
}
if mba.Password == "" {
errs = append(errs, ErrNoMetricsBasicAuthPassword)
}
if len(errs) != 0 {
return errors.Join(ErrInvalidMetricsBasicAuthConfig, errors.Join(errs...))
}
return nil
}
+73
View File
@@ -157,6 +157,79 @@ func TestMetricsValid(t *testing.T) {
}, },
err: ErrInvalidMetricsCACertificate, err: ErrInvalidMetricsCACertificate,
}, },
{
name: "basic auth credentials set",
input: &Metrics{
Bind: ":9090",
Network: "tcp",
BasicAuth: &MetricsBasicAuth{
Username: "admin",
Password: "hunter2",
},
},
},
{
name: "invalid basic auth config",
input: &Metrics{
Bind: ":9090",
Network: "tcp",
BasicAuth: &MetricsBasicAuth{},
},
err: ErrInvalidMetricsBasicAuthConfig,
},
} {
t.Run(tt.name, func(t *testing.T) {
if err := tt.input.Valid(); !errors.Is(err, tt.err) {
t.Logf("wanted error: %v", tt.err)
t.Logf("got error: %v", err)
t.Error("validation failed")
}
})
}
}
func TestMetricsBasicAuthValid(t *testing.T) {
for _, tt := range []struct {
name string
input *MetricsBasicAuth
err error
}{
{
name: "both set",
input: &MetricsBasicAuth{
Username: "admin",
Password: "hunter2",
},
},
{
name: "empty username and password",
input: &MetricsBasicAuth{},
err: ErrInvalidMetricsBasicAuthConfig,
},
{
name: "missing username",
input: &MetricsBasicAuth{
Password: "hunter2",
},
err: ErrNoMetricsBasicAuthUsername,
},
{
name: "missing password",
input: &MetricsBasicAuth{
Username: "admin",
},
err: ErrNoMetricsBasicAuthPassword,
},
{
name: "missing both surfaces wrapper error",
input: &MetricsBasicAuth{},
err: ErrNoMetricsBasicAuthUsername,
},
{
name: "missing both surfaces password error",
input: &MetricsBasicAuth{},
err: ErrNoMetricsBasicAuthPassword,
},
} { } {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if err := tt.input.Valid(); !errors.Is(err, tt.err) { if err := tt.input.Valid(); !errors.Is(err, tt.err) {