Internet exposure checks and response rate limiting
Jarl works out which views are reachable from the internet (match-clients
ending in a catch-all, not private-looking addresses: an internal view of a
global IPv6 prefix is still yours; a server without views: open allow-query
and a non-private listen address), with a per-view override (schema 9), and
checks public ones: rate limiting, open resolver, minimal-any, cache
snooping, open NOTIFY and transfers, version and host name, management
interfaces. Fixes are staged like any change, including rate limiting from
ISC's example in log-only mode with the secondaries exempt, then enforcing.
named logs what rate limiting limits (or would, in log-only mode, where its
counters don't move) as query-errors; Jarl routes those lines to its log
and counts them (rrl breakdown, ratelimited top lists, Prometheus
jarl_ratelimit_logged_total), shown on the new Exposure page.
A change that newly opens a public view up must be confirmed (accept_risk,
--accept-risk, a checkbox on the Changes page; 409 confirm_risk
otherwise). Findings can be acknowledged with a reason and stay visible as
info. GET /exposure, POST /exposure/fix|ack|unack, jarl exposure,
docs/exposure.md.
Also fixes query statistics skipping TSIG-signed queries ("IP#port/key
NAME" in named's log).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cf550bd512
commit
bdd7acfd3b
50 files changed
+2019
-52
No files matched your search
@@ -26,10 +26,15 @@ const (
|
||||
KindBlocked = "blocked" // names blocked (docs/blocking.md)
|
||||
// KindBlockedClient: clients with the most blocked queries.
|
||||
KindBlockedClient = "blocked_client"
|
||||
// KindRateLimited: client networks whose responses rate limiting
|
||||
// limited (or would have, in log-only mode); KindRateLimitedName: the
|
||||
// names and types they asked for (docs/exposure.md).
|
||||
KindRateLimited = "ratelimited"
|
||||
KindRateLimitedName = "ratelimited_name"
|
||||
)
|
||||
|
||||
// Kinds lists the top-list kinds.
|
||||
var Kinds = []string{KindName, KindClient, KindNXDomain, KindServFail, KindRefused, KindBlocked, KindBlockedClient}
|
||||
var Kinds = []string{KindName, KindClient, KindNXDomain, KindServFail, KindRefused, KindBlocked, KindBlockedClient, KindRateLimited, KindRateLimitedName}
|
||||
|
||||
// OtherZone is the zone key for names outside every local zone (answered
|
||||
// by recursion, or refused).
|
||||
@@ -337,6 +342,8 @@ func (c *Collector) handleLine(line string) {
|
||||
if !ok {
|
||||
if rw, ok := ParseRewrite(line, c.opts.Location); ok {
|
||||
c.handleRewrite(rw)
|
||||
} else if rl, ok := ParseRateLimit(line, c.opts.Location); ok {
|
||||
c.handleRateLimit(rl)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -416,6 +423,30 @@ func (c *Collector) handleRewrite(rw Rewrite) {
|
||||
hours[hour][KindBlockedClient][clientKey(rw.Client, c.opts.Anonymize)]++
|
||||
}
|
||||
|
||||
// handleRateLimit counts a response rate limiting limited, or would have:
|
||||
// "rrl:drop", "rrl:slip", "rrl:would-drop" or "rrl:would-slip", and the
|
||||
// client network and name in the hour's top lists.
|
||||
func (c *Collector) handleRateLimit(rl RateLimit) {
|
||||
for _, p := range c.opts.Exclude {
|
||||
if p.Contains(rl.Client) {
|
||||
return
|
||||
}
|
||||
}
|
||||
key := "rrl:" + rl.Action
|
||||
if rl.Would {
|
||||
key = "rrl:would-" + rl.Action
|
||||
}
|
||||
minute, hour := c.minuteOf(rl.Time), c.hourOf(rl.Time)
|
||||
c.count(minute, key, 1)
|
||||
hours := c.hours
|
||||
if hour < c.liveHour {
|
||||
hours = c.late
|
||||
}
|
||||
hours[hour] = c.hourMaps(hours, hour)
|
||||
hours[hour][KindRateLimited][rl.Prefix]++
|
||||
hours[hour][KindRateLimitedName][rl.Name+" "+rl.Type]++
|
||||
}
|
||||
|
||||
// policySource names what a rewrite came from: a blocklist's name, "rules"
|
||||
// for the rules entered in Jarl, or the policy zone for others.
|
||||
func policySource(via string) string {
|
||||
|
||||
Reference in new issue
Block a user