Files
jarl/docs/validation.md
T
abrown38andClaude Opus 5.5 3c73afe0e1 Fix the negative-caching warning and show warnings in the CLI
The soa_warning for negative caching fired on the SOA minimum alone and
blamed BIND's resolver cap (max-ncache-ttl), which doesn't apply to an
authoritative zone. Resolvers cache negative answers for the smaller of
the SOA minimum and the SOA record's TTL (RFC 2308 section 5), and the
SOA record uses the zone's default TTL. The warning now uses that value,
explains the effect, and suggests the fix command (with --view when the
zone is in a view).

Also:
- Problem.String shows zone names without the trailing dot (jarld check).
- jarl prints the warnings a change introduces when it is applied or
  staged, not only on dry runs.
- jarl check/resync print only named-checkconf errors, not its
  "loaded serial" lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 15:29:05 +00:00

98 lines
7.6 KiB
Markdown

# Validation
Every change is checked twice before named sees it:
1. **Jarl's validator** checks the whole resulting configuration: the DNS
rules, references between objects, and option syntax against the BIND
9.20 grammar. Its messages name the record, zone or option and explain
the rule.
2. **BIND's own checkers**: `named-checkconf -z` on the complete rendered
configuration (which also loads every zone) and `named-checkzone`. Their
messages are mapped back to the object that caused them.
Problems have a severity:
* **error** blocks the change (HTTP 422 `validation_failed`, CLI exit code 6).
* **warning** is shown but allowed: probably a mistake, or a security risk.
* **info** is a hint.
Only problems that a change *introduces* block it. Problems that already
existed are reported separately (`existing_problems`) and don't stop
unrelated changes, though `jarl status`, `jarl check` and the dashboard keep
showing them.
## Records and zones
| Code | Severity | Rule |
|------|----------|------|
| `record_invalid` | error | Record data must parse for its type (addresses, numbers, names, base64/hex fields, ...). TXT/SPF strings are split into 255-byte pieces automatically |
| `record_name_invalid` | error | Owner names must be valid. Host names in A/AAAA/MX owners follow `check-names` rules (letters, digits, hyphens; `_` only where it is normal, e.g. SRV/TXT owners) |
| `record_out_of_zone` | error | The name must be inside the zone (catches `www.example.com` without the final dot) |
| `record_duplicate` | error | The same record can't exist twice |
| `cname_conflict` | error | A CNAME must be the only record at its name (RFC 1034), so no CNAME next to A, MX, TXT... |
| `cname_multiple` | error | A name can have only one CNAME |
| `cname_apex` | error | No CNAME at the zone apex (it needs SOA and NS) |
| `cname_loop` | error | CNAMEs that point back to themselves |
| `cname_chain` | warning | CNAME to a CNAME: works, but slows resolution |
| `cname_dangling` | warning | CNAME to a name that doesn't exist in the zone |
| `dname_multiple`, `below_dname` | error | One DNAME per name, and nothing below a DNAME (RFC 6672) |
| `apex_ns_missing` | error | A zone needs NS records at its apex |
| `ns_no_address` | error | An in-zone name server needs an A or AAAA record (named refuses to load the zone otherwise) |
| `ns_cname`, `mx_cname`, `srv_cname` | error | NS, MX and SRV targets must not be CNAMEs (RFC 2181 / RFC 2782) |
| `glue_missing` | error | A delegation to a name server inside the delegated zone needs glue records |
| `target_no_address` | warning | A delegation's name server has no address in the zone |
| `occluded` | warning | Records at or below a delegation point are not served (named answers with a referral) |
| `shadowed_by_zone` | warning | Records hidden because a more specific zone exists in the same view |
| `child_not_delegated` | info | A child zone on this server isn't delegated from its parent, so other resolvers can't find it |
| `ds_not_at_delegation` | error | DS records belong at a delegation point |
| `null_mx_not_alone` | error | A null MX (`0 .`) must be the only MX |
| `rrset_ttl_mismatch` | error | All records of an RRset need the same TTL (BIND would silently use the first one) |
| `rrset_too_large`, `too_many_types` | error | BIND 9.20's limits: 100 records per RRset (`max-records-per-type`), 100 types per name (`max-types-per-name`) |
| `spf_multiple`, `dmarc_multiple`, `dmarc_name` | warning | More than one SPF or DMARC policy (receivers then ignore them), or DMARC outside `_dmarc.` |
| `wildcard_unusual` | warning | Wildcard NS records |
| `soa_invalid`, `soa_warning`, `ttl_invalid` | error / warning | SOA fields and TTLs in range and sensible. One warning covers negative caching longer than 3 hours: resolvers cache "no such name" answers for the smaller of the SOA minimum and the zone's default TTL (RFC 2308), so a new name can stay invisible that long. 1 hour (`--minimum 3600`) is a good value |
| `zone_name_invalid`, `zone_type_invalid`, `zone_duplicate` | error | Valid zone names and types. A zone exists once per view |
| `zone_outside_view` | error | Once views exist, every zone must be in one (named requires this) |
| `zone_missing_option` | error | Secondary, stub and mirror zones need `primaries` |
| `zone_file_required` / `zone_file_invalid` / `zone_file_managed` | error | Hint zones need a file. Forward zones have none. Primary zone files are Jarl's |
| `zone_inview_invalid`, `zone_inview_option` | error | `in-view` zones only inside views, and only with `forward`/`forwarders` |
| `zone_records_unmanaged` | error | Only primary zones can have records in Jarl |
| `zone_serial_policy` | error | `date`, `increment` or `unixtime` |
| `zone_no_forwarders`, `zone_hint_name`, `zone_name_slash` | info / warning | Unusual but legal setups |
## ACLs, keys, views, options
| Code | Severity | Rule |
|------|----------|------|
| `acl_element_invalid` | error | Address match elements: addresses, prefixes (host bits must be zero, like BIND requires), `!negation`, `key NAME`, ACL names, `{ nested lists }`, `any`/`none`/`localhost`/`localnets` |
| `acl_ref_missing`, `key_ref_missing` | error | Every ACL or TSIG key that is referenced must exist |
| `acl_cycle` | error | ACLs that include each other |
| `acl_duplicate`, `acl_name_invalid`, `acl_empty`, `acl_shadowed` | error / warning | Unique names, built-in names reserved, elements that can never match |
| `key_*` | error / warning | Valid key names, a supported algorithm, a base64 secret of a sensible length. hmac-md5/sha1 are flagged as weak |
| `view_duplicate`, `view_name_invalid`, `view_acl_invalid` | error | Valid, unique views and match lists |
| `view_unreachable` | warning | A view that comes after one matching every client is never used |
| `option_unknown`, `option_scope_invalid` | error | The option must exist in BIND 9.20 and be allowed where it is set (server, view, zone of this type, top level) |
| `option_syntax`, `option_value_invalid` | error | The value must parse (balanced braces, strings, the grammar's value type: yes/no, enums, numbers, durations, sizes, address match lists) |
| `option_duplicate` | error | Options that may appear once, appear once |
| `option_managed` | error | Statements Jarl manages itself can't be raw options: `zone`, `view`, `acl`, `key`, `options`, a zone's `type`/`file`/`in-view`, a view's `match-*` |
| `option_deprecated`, `option_obsolete`, `option_unavailable` | warning | Flags from the BIND 9.20 reference |
| `option_experimental` | info | |
| `settings_invalid` | error | Defaults for new zones are valid |
## Security lints
| Code | Severity | What it catches |
|------|----------|-----------------|
| `security_open_resolver` | warning | A view (or the server) where recursion is on and reachable by any client. This includes the BIND trap where `allow-recursion` is unset and inherits `allow-query { any; }`. Open resolvers are abused for DDoS amplification. Set `allow-recursion` to your own networks |
| `security_open_transfer` | warning | `allow-transfer` open to anyone. Restrict it to your secondaries or a TSIG key |
| `dynamic_update_unsupported` | error | `allow-update` / `update-policy` on a Jarl zone. named would write to files that Jarl regenerates, losing the updates. Use the API (e.g. `jarl acme`) instead |
## What only BIND checks
named-checkconf and named-checkzone run on every change, so anything Jarl's
validator doesn't model is still caught before named loads it. Option
values Jarl accepts as raw named.conf syntax, such as `logging` channels
and `rrset-order`, are the main example. Their errors come back as
`validation_failed` (HTTP 422) with the checker's messages in `check`, each
tied to the object it belongs to.