diff --git a/README.md b/README.md index 9866664..f35a386 100644 --- a/README.md +++ b/README.md @@ -96,26 +96,28 @@ make test # tests ## Rules -| Code | Description | Severity | -|------------------------------------------------------|---------------------------------------------------------------------------------------------------|---------------------| -| `dnssec_zone_signed` | Detects a zone advertised as signed at the parent (DS) but no DNSKEY served at the apex. | CRITICAL | -| `dnssec_dnskey_consistent` | Verifies that every authoritative server returns the same DNSKEY RRset. | CRITICAL | -| `dnssec_dnskey_query_ok` | Verifies that every authoritative server answered the DNSKEY query. | CRITICAL | -| `dnssec_algorithm_allowed` | Rejects DNSKEYs that use a forbidden algorithm or are not in the allowed list. | CRITICAL | -| `dnssec_algorithm_modern` | Recommends ECDSAP256SHA256 (13) or Ed25519 (15) over RSA. | WARNING | -| `dnssec_rsa_keysize` | Verifies RSA DNSKEYs reach a minimum modulus size (default 2048 bits). | CRITICAL | -| `dnssec_ksk_present` | Verifies at least one DNSKEY has the SEP bit (KSK). | CRITICAL | -| `dnssec_dnskey_count` | Warns when too many DNSKEYs are published, inflating responses and amplification potential. | WARNING | -| `dnssec_rrsig_present_dnskey` | Ensures the DNSKEY RRset is signed. | CRITICAL | -| `dnssec_rrsig_present_soa` | Ensures the SOA RRset is signed. | CRITICAL | -| `dnssec_rrsig_validity_window` | Verifies that every observed RRSIG is currently within [Inception, Expiration]. | CRITICAL | -| `dnssec_rrsig_freshness` | Warns when RRSIGs are close to expiring; preemptive alert for stuck signers. | CRITICAL | -| `dnssec_denial_uses_nsec3` | Warns when the zone uses NSEC for negative answers, which makes the zone walkable (RFC 5155 / RFC 7129). | WARNING | -| `dnssec_nsec3_iterations` | Verifies that NSEC3PARAM.Iterations is at most nsec3IterationsMax (default 0, per RFC 9276 §3.1). | CRITICAL | -| `dnssec_nsec3_salt_empty` | Verifies that NSEC3PARAM.SaltLength is 0 (RFC 9276 §3.1: a salt buys no measurable protection). | WARNING | -| `dnssec_nsec3_optout_only_when_signed_delegations` | Reports informational note when the OPT-OUT flag is set on NSEC3PARAM in a leaf zone. | INFO | -| `dnssec_denial_consistent` | Verifies that every authoritative server uses the same denial-of-existence scheme. | WARNING | -| `dnssec_dnskey_ttl_min` | Warns when the DNSKEY TTL is too short to be useful for caching. | WARNING | +Each rule emits a finding code. Severity may be affected by the options above. + +| Code | Default severity | Condition | +|------|-----------------|-----------| +| `dnssec_zone_signed` | critical | Parent DS is published but the apex serves no DNSKEY (broken chain of trust). | +| `dnssec_dnskey_consistent` | critical | Authoritative servers disagree on the apex DNSKEY RRset. | +| `dnssec_dnskey_query_ok` | warning | At least one authoritative server failed to answer the DNSKEY query. | +| `dnssec_algorithm_allowed` | critical (forbidden) / warning (not in allowlist) | A DNSKEY uses a forbidden algorithm or an algorithm not in `allowedAlgorithms`. | +| `dnssec_algorithm_modern` | warning | The zone still uses RSA-family DNSKEYs; ECDSAP256SHA256 (13) or Ed25519 (15) recommended. | +| `dnssec_rsa_keysize` | critical (<1024) / warning (<`minRSAKeySize`) | An RSA DNSKEY has a modulus below the policy threshold. | +| `dnssec_ksk_present` | critical | No DNSKEY carries the SEP (KSK) flag while `requireSEP` is enabled. | +| `dnssec_dnskey_count` | warning | Eight or more DNSKEYs are published, bloating responses and amplification factor. | +| `dnssec_rrsig_present_dnskey` | critical | The apex DNSKEY RRset is unsigned. | +| `dnssec_rrsig_present_soa` | critical | The apex SOA RRset is unsigned. | +| `dnssec_rrsig_validity_window` | critical | An observed RRSIG is outside its `[Inception, Expiration]` window. | +| `dnssec_rrsig_freshness` | warning / critical | The closest RRSIG expires in fewer than `signatureFreshness` / `signatureFreshnessCrit` days. | +| `dnssec_denial_uses_nsec3` | warning | The zone uses NSEC for denial of existence, exposing it to trivial walking (RFC 5155 / RFC 7129). | +| `dnssec_nsec3_iterations` | warning / critical (per `nsec3IterationsSeverity`) | `NSEC3PARAM.Iterations` exceeds `nsec3IterationsMax` (RFC 9276 §3.1). | +| `dnssec_nsec3_salt_empty` | warning | `NSEC3PARAM.SaltLength` is non-zero (RFC 9276 §3.1: a salt buys no measurable protection). | +| `dnssec_nsec3_optout_only_when_signed_delegations` | info | The OPT-OUT flag is set in a leaf zone, where it serves no purpose. | +| `dnssec_denial_consistent` | critical | Authoritative servers disagree on the denial-of-existence scheme (NSEC vs NSEC3, or differing parameters). | +| `dnssec_dnskey_ttl_min` | warning | The DNSKEY TTL is below `dnskeyTTLMin`, hurting cache efficiency. | ## License diff --git a/checker/collect.go b/checker/collect.go index aff9679..486e7fd 100644 --- a/checker/collect.go +++ b/checker/collect.go @@ -131,7 +131,7 @@ func collectFromServer(ctx context.Context, server, zone string) PerServerView { view.ProbeName = strings.TrimSuffix(probe, ".") if probeResp := authQuery(ctx, server, probe, dns.TypeA, &view, true); probeResp != nil { view.DenialKind, view.DenialRecords = classifyDenial(probeResp, view.NSEC3PARAM) - } else if view.UDPError == "" && view.TCPError == "" && len(view.DNSKEYs) == 0 { + } else if len(view.DNSKEYs) == 0 { view.DenialKind = DenialNone }