milestone 13 discrepancies: redact credentials from urls in logs, metrics and cli output

This commit is contained in:
2026-08-07 00:45:17 +02:00
parent 1ff727feb8
commit 8c3328562e
39 changed files with 5734 additions and 510 deletions
+105 -42
View File
@@ -23,21 +23,24 @@ Serves DNS until SIGINT or SIGTERM.
| `--config FILE` | Seed configuration file (default `/etc/nxdns/config.zon`). Read only when the database has never been configured. |
| `--web-dev DIR` | Serve the web interface from DIR instead of the embedded assets, with no cache headers. Development only. |
Seeding failures are not treated as configuration faults here: a seed file that
is unparseable, oversized or invalid prints its diagnostics and exits 1, not 2.
See [exit codes](#exit-codes).
A seed file that is unparseable, oversized or invalid prints its diagnostics and
exits 2 — the same code `check` and `import` give for the same file. See
[exit codes](#exit-codes).
## `check`
Validates the configuration and probes the upstreams. Exit 0 when clean, 2 when
it found problems. It always reports every problem, not just the first. What it
checks, in order:
Validates the configuration and probes the upstreams. Exit 0 when it found no
failures, 2 when it found one. It always reports every problem, not just the
first. What it checks, in order:
1. Which source to check (see [source selection](#source-selection)).
2. Full validation — the same rules `import` enforces.
3. For each enabled DoH/DoT listener: certificate and key are readable (FAIL if
not), and the key's permissions are owner-only (WARN if any group or other
bit is set; the WARN does not change the exit code).
3. For each enabled DoH/DoT listener: both PEM files are read and the key is
tested against the certificate, through the same `CertStore.init` the
listeners boot with. A file that is missing, unreadable, too large or
unparseable, and a key that does not belong to the certificate, are all FAIL.
The key's permissions are a separate finding: WARN when any group or other
bit is set, which does not change the exit code.
4. A live probe: one real A query for `example.com` through every enabled
upstream, driving the same pool and failover machinery the server uses, with
`upstream.total_timeout_ms` as the per-attempt deadline. A FAIL line names
@@ -50,13 +53,28 @@ checks, in order:
| `--data-dir DIR` | Data directory to look for `config.db` in. |
| `--config FILE` | Check this file instead of the database. |
### Failures and warnings
Every finding carries a severity. `FAIL` is a problem that sets exit 2. `WARN`
is legal configuration that is almost certainly not what was meant — a blocklist
source no group links to, a TLS key readable beyond its owner — and never
changes an exit code, because the service starts either way.
The last line is a summary, and it never contradicts the lines above it:
| What was found | Last line | Exit |
| --- | --- | --- |
| Nothing | `OK: no problems found` | 0 |
| Warnings only | `OK: no failures found, 2 warnings``1 warning` in the singular | 0 |
| At least one failure | No summary line; the FAIL lines are the report | 2 |
### Source selection
- `--config FILE` given explicitly: check that file, nothing else.
- Otherwise, if `<data-dir>/config.db` exists: check the database — the right
default, since the database is the truth on a configured server. Pending
schema migrations are applied first, so a `check` immediately after an upgrade
works.
default, since the database is the truth on a configured server. It is opened
immutable, so `check` writes nothing to it; see
[what `check` does not do](#what-check-does-not-do).
- Otherwise, if the default config file path exists: check it.
- Otherwise: "nothing to check", exit 2.
@@ -64,10 +82,42 @@ The first line of output always names which source was checked. A file larger
than 4 MiB fails with `larger than 4194304 bytes`; a ZON syntax error is
reported with its line and column.
A missing file is treated differently depending on how the path was reached. An
explicit `--config FILE` that does not exist is an I/O failure: `check failed:
FileNotFound` on stderr, exit 1. A default path that does not exist is one of
the cases above and produces "nothing to check", exit 2.
A named file that is missing or unreadable is a finding like any other, not an
I/O failure that escapes the run: `FAIL <path>: no such file` or
`FAIL <path>: not readable`, exit 2. That is the same code the implicit path
gives when there is nothing to check, so naming the file does not change what a
missing file costs.
### What `check` does not do
`check` never writes to `config.db`. It opens the file immutable, which means
SQLite refuses every statement that would write and builds no write-ahead log,
so no `config.db-wal` and no `config.db-shm` appear beside it. It does not chmod
the file and it does not migrate the schema.
Two consequences are worth knowing before you read a FAIL line as damage:
- A database behind this binary's schema is reported, not upgraded. Start the
service to migrate it.
```
FAIL <db>: schema version <n>, this nxdns expects <m>; `nxdns run` migrates it, `check` will not
```
- A database with an unapplied write-ahead log cannot be graded without writing,
because the newest settings are in the log and the main file holds older ones.
`check` says so rather than reading the stale values:
```
FAIL <db>: uncheckpointed changes are waiting in <db>-wal, and reading without writing would answer from the older settings in the main file; `nxdns run` applies them. A running nxdns normally holds this log, which is the usual reason to see this line.
```
A running nxdns is the usual holder of that log, so this is a common answer
when checking a live server rather than a sign of damage. It depends on what
is in the log, not on whether a server is up: a configuration write that has
not been checkpointed puts bytes there, and a server that has only been
answering queries leaves `config.db-wal` empty and reads normally. Check an
`export` with `--config`, or stop the service first.
## `export`
@@ -92,17 +142,24 @@ See [back up and restore](../how-to/back-up-and-restore.md).
## `import FILE`
Validates FILE and replaces the whole configuration with it in one transaction.
Prints every validation problem on failure; a failed import leaves the database
untouched. Refuses a database that already has content unless `--force` is
given (`DatabaseNotEmpty`, exit 2). Creates the data directory at mode 0700 if
it is missing.
Prints every validation problem; a failed import leaves the database untouched.
Refuses a database that already holds configuration unless `--force` is given
(`DatabaseNotEmpty`, exit 2). The check measures what an operator set, not what
the network did: client rows the DNS path materialised from traffic never
trigger the refusal on their own. Creates the data directory at mode 0700 if it
is missing.
Client history survives the replacement. An address the database already knew
keeps its first-seen and last-seen even when FILE names it; only an address it
has never seen takes the import's clock. A client FILE leaves out is removed,
history included.
`FILE` is positional and may appear before or after the flags.
| Flag | Meaning |
| --- | --- |
| `--data-dir DIR` | Data directory holding `config.db` (created if missing). |
| `--force` | Replace a database that already has content. |
| `--force` | Replace a database that already holds configuration. |
## `version`
@@ -120,31 +177,37 @@ the same. A usage error prints the same text to stderr and exits 64.
| --- | --- |
| 0 | Success. |
| 1 | Runtime failure — I/O, database, out of memory. A partial diagnostic report caused by an allocation failure is a runtime failure, not a verdict on the configuration. |
| 2 | A configuration problem the operator can fix, or a `check` that found one. Which faults qualify differs per subcommand; see below. |
| 2 | A configuration problem the operator can fix, or a `check` that found one. |
| 64 | Usage error — unknown command or flag, a flag without its value, a missing or extra argument. |
Code 2 is not a single rule shared by every subcommand. `check` and `import`
classify configuration faults; `run` maps only four errors to it and lets
everything else out as a runtime failure.
Code 2 means the same thing from every subcommand. `src/config/faults.zig`
holds the one list of errors that mean "the configuration the operator supplied
is wrong", and `run`, `check` and `import` all ask it, so a rejected file exits
2 whichever command read it. The list is every error the validator raises, plus
`ParseZon`, `ConfigTooLarge`, `NoUsableUpstreams` and `BadCertificate`. In
practice that covers a seed file with a syntax error, one larger than 4 MiB, one
with no `default` group (`MissingDefaultGroup`), one with no enabled upstream
(`NoUpstreams`), a bad bind address, a bad rate limit, an unusable certificate,
and `password` and `password_hash` set together.
`check` exits 2 when validation reported at least one problem, when a
certificate or key named by an enabled listener is unreadable, when a probed
upstream failed, when the file is larger than 4 MiB, when the file has a ZON
syntax error, and when there was nothing to check. Any other error escaping the
run — a missing explicit `--config` file, an unreadable database — prints
`check failed: <Error>` and exits 1.
When `run` exits 2 it points at the diagnosis on stderr, whether the fault came
from the seed file or from the database it loaded:
`import` exits 2 when validation recorded at least one problem, and for
`DatabaseNotEmpty`, `ConfigTooLarge`, `ParseZon` and `PasswordAndHashBothSet`.
`OutOfMemory` is 1 even when problems were recorded, because the report is then
incomplete. Every other error is 1.
```
run `nxdns check` to see the configuration in full
```
`run` exits 2 for `NoUsableUpstreams`, `BadBindAddress`, `BadRateLimit` and
`BadCertificate`, and points the operator at `nxdns check` on stderr when it
does. Nothing else is remapped. In particular, seeding the database from
`--config` on first start happens before that classification applies to it: a
seed file that is unparseable (`ParseZon`), larger than 4 MiB
(`ConfigTooLarge`), or rejected by validation prints its diagnostics to stderr
and exits 1. The same file given to `check` or `import` exits 2.
`check` exits 2 for those faults and also when a probed upstream failed, when a
named configuration file is missing or unreadable, when the database cannot be
read or is not at this binary's schema version, and when there was nothing to
check. Warnings never contribute.
`import` exits 2 for those faults and for `DatabaseNotEmpty`. That last one is
deliberately not a configuration fault — it reports the state of the database
rather than the content of a file — and `import` decides it for itself; the
answer to it is `--force`, not an edit.
`OutOfMemory` is exit 1 even when problems were recorded, because the report is
then incomplete. Every other error is 1.
Where an exit code sends you next: [troubleshoot](../how-to/troubleshoot.md).