# 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 | | `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.