config: a metrics username without a password stops startup (closes #268)
check / check (push) Canceled after 0s
check / check (push) Canceled after 0s
DNSWATCHER_METRICS_USERNAME turns on /metrics, and its Basic Auth used DNSWATCHER_METRICS_PASSWORD as it was, empty included, so the username with an empty password got in. config.New now refuses a username without a password with ErrNoMetricsPassword, whose message names both variables. The README's variable table says the password is required with the username. Judgement call: the check is in New, after buildConfig, since it compares two settings buildConfig has already read; buildConfig is also at the linter's length limit. Model: opus-5-5
This commit was merged in pull request #277.
This commit is contained in:
@@ -437,7 +437,7 @@ following precedence (highest to lowest):
|
||||
| `DNSWATCHER_SENTRY_DSN` | Sentry DSN for error reporting | `""` |
|
||||
| `DNSWATCHER_MAINTENANCE_MODE` | Only sets `maintenanceMode` in the health check response; changes nothing else | `false` |
|
||||
| `DNSWATCHER_METRICS_USERNAME` | Basic auth username for /metrics, which is served only when this is set | `""` |
|
||||
| `DNSWATCHER_METRICS_PASSWORD` | Basic auth password for /metrics | `""` |
|
||||
| `DNSWATCHER_METRICS_PASSWORD` | Basic auth password for /metrics; required when `DNSWATCHER_METRICS_USERNAME` is set, and startup stops without it | `""` |
|
||||
| `DNSWATCHER_SEND_TEST_NOTIFICATION` | Send a test notification after first scan completes | `false` |
|
||||
|
||||
**`DNSWATCHER_TARGETS` is required.** dnswatcher will refuse to start if no
|
||||
|
||||
@@ -19,6 +19,8 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-13: `DNSWATCHER_METRICS_USERNAME` set without
|
||||
`DNSWATCHER_METRICS_PASSWORD` stops startup (closes #268).
|
||||
- 2026-10-13: a save syncs the state file to disk before renaming it into place,
|
||||
so a crash cannot leave it empty and stop the next start (closes #264).
|
||||
- 2026-10-13: a `PORT` that cannot be bound stops startup with an error naming
|
||||
|
||||
@@ -61,6 +61,13 @@ var ErrInvalidTLSExpiryWarning = errors.New(
|
||||
// unset and means the default.
|
||||
var ErrInvalidBool = errors.New("value must be true or false")
|
||||
|
||||
// ErrNoMetricsPassword is returned when DNSWATCHER_METRICS_USERNAME is set and
|
||||
// DNSWATCHER_METRICS_PASSWORD is empty. Basic Auth on /metrics would otherwise
|
||||
// let in anyone who sends the username with an empty password.
|
||||
var ErrNoMetricsPassword = errors.New(
|
||||
"DNSWATCHER_METRICS_PASSWORD must be set when DNSWATCHER_METRICS_USERNAME is set",
|
||||
)
|
||||
|
||||
// Params contains dependencies for Config.
|
||||
type Params struct {
|
||||
fx.In
|
||||
@@ -108,6 +115,10 @@ func New(_ fx.Lifecycle, params Params) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.MetricsUsername != "" && cfg.MetricsPassword == "" {
|
||||
return nil, ErrNoMetricsPassword
|
||||
}
|
||||
|
||||
configureDebugLogging(cfg, params)
|
||||
|
||||
return cfg, nil
|
||||
|
||||
@@ -354,6 +354,24 @@ func TestNew_SkipRecordNotificationsNotInTargetsStopsStartup(t *testing.T) {
|
||||
require.ErrorContains(t, err, `"www.example.net"`)
|
||||
}
|
||||
|
||||
// TestNew_MetricsUsernameWithoutPasswordStopsStartup checks an unset and an
|
||||
// empty password; TestNew_EnvironmentOverrides checks a username with one.
|
||||
func TestNew_MetricsUsernameWithoutPasswordStopsStartup(t *testing.T) {
|
||||
viper.Reset()
|
||||
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
||||
t.Setenv("DNSWATCHER_METRICS_USERNAME", "prom")
|
||||
|
||||
_, err := config.New(nil, newTestParams(t))
|
||||
require.ErrorIs(t, err, config.ErrNoMetricsPassword, "password unset")
|
||||
require.ErrorContains(t, err, "DNSWATCHER_METRICS_PASSWORD")
|
||||
|
||||
viper.Reset()
|
||||
t.Setenv("DNSWATCHER_METRICS_PASSWORD", "")
|
||||
|
||||
_, err = config.New(nil, newTestParams(t))
|
||||
require.ErrorIs(t, err, config.ErrNoMetricsPassword, "password empty")
|
||||
}
|
||||
|
||||
func TestNew_CustomDNSIntervalDuration(t *testing.T) {
|
||||
viper.Reset()
|
||||
t.Setenv("DNSWATCHER_TARGETS", "example.com")
|
||||
|
||||
Reference in New Issue
Block a user