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
+41
-1
@@ -50,9 +50,43 @@ type View struct {
|
||||
MatchDestinations []string `json:"match_destinations"`
|
||||
MatchRecursiveOnly *bool `json:"match_recursive_only,omitempty"`
|
||||
Comment string `json:"comment"`
|
||||
// Exposure overrides whether the view counts as reachable from the
|
||||
// internet (docs/exposure.md): "" works it out, ExposurePublic or
|
||||
// ExposurePrivate say so.
|
||||
Exposure string `json:"exposure,omitempty"`
|
||||
// Acknowledged are exposure findings accepted on purpose; they are
|
||||
// shown as info instead of warnings.
|
||||
Acknowledged []Acknowledgement `json:"acknowledged,omitempty"`
|
||||
Meta
|
||||
}
|
||||
|
||||
// Exposure overrides.
|
||||
const (
|
||||
ExposureAuto = ""
|
||||
ExposurePublic = "public"
|
||||
ExposurePrivate = "private"
|
||||
)
|
||||
|
||||
// Acknowledgement accepts one exposure finding (by its code) for a view,
|
||||
// or for the server: the finding stays, as info, with who accepted it,
|
||||
// when and why.
|
||||
type Acknowledgement struct {
|
||||
Code string `json:"code"`
|
||||
Reason string `json:"reason"`
|
||||
By string `json:"by"`
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
// FindAck returns the acknowledgement of code in acks, if any.
|
||||
func FindAck(acks []Acknowledgement, code string) *Acknowledgement {
|
||||
for i := range acks {
|
||||
if acks[i].Code == code {
|
||||
return &acks[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ACL is a named address match list.
|
||||
type ACL struct {
|
||||
ID int64 `json:"id"`
|
||||
@@ -217,7 +251,11 @@ type Settings struct {
|
||||
BlockingPausedUntil *time.Time `json:"blocking_paused_until,omitempty"`
|
||||
// BlockingPausedIndefinitely pauses blocking until someone resumes it.
|
||||
BlockingPausedIndefinitely bool `json:"blocking_paused_indefinitely,omitempty"`
|
||||
Version int `json:"version"`
|
||||
// Exposure and Acknowledged are the server's own (without views, and
|
||||
// for server-wide findings such as a visible version): see View.
|
||||
Exposure string `json:"exposure,omitempty"`
|
||||
Acknowledged []Acknowledgement `json:"acknowledged,omitempty"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
// BlockingPaused reports whether blocking is paused, for a time or until
|
||||
@@ -289,10 +327,12 @@ func (s *State) Clone() *State {
|
||||
ClientGroups: make(map[int64]*ClientGroup, len(s.ClientGroups)),
|
||||
}
|
||||
c.Settings.Nameservers = slices.Clone(s.Settings.Nameservers)
|
||||
c.Settings.Acknowledged = slices.Clone(s.Settings.Acknowledged)
|
||||
for id, v := range s.Views {
|
||||
n := *v
|
||||
n.MatchClients = slices.Clone(v.MatchClients)
|
||||
n.MatchDestinations = slices.Clone(v.MatchDestinations)
|
||||
n.Acknowledged = slices.Clone(v.Acknowledged)
|
||||
if v.MatchRecursiveOnly != nil {
|
||||
b := *v.MatchRecursiveOnly
|
||||
n.MatchRecursiveOnly = &b
|
||||
|
||||
Reference in new issue
Block a user