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
@@ -61,6 +61,9 @@ func (s *Server) applyBatch(w http.ResponseWriter, r *http.Request, p *auth.Prin
|
||||
if in.Force {
|
||||
q.Set("force", "1")
|
||||
}
|
||||
if in.AcceptRisk {
|
||||
q.Set("accept_risk", "1")
|
||||
}
|
||||
r.URL.RawQuery = q.Encode()
|
||||
return s.mutate(w, r, p, in.Description, toRequests(in.Ops), nil)
|
||||
}
|
||||
@@ -106,7 +109,7 @@ func (s *Server) createChangeset(w http.ResponseWriter, r *http.Request, p *auth
|
||||
}
|
||||
cs = res.Changeset
|
||||
if in.Apply {
|
||||
out, err := s.eng.Apply(r.Context(), cs.ID, p, engine.ApplyOptions{DryRun: in.DryRun, Force: in.Force})
|
||||
out, err := s.eng.Apply(r.Context(), cs.ID, p, engine.ApplyOptions{DryRun: in.DryRun, Force: in.Force, AcceptRisk: in.AcceptRisk})
|
||||
if err != nil {
|
||||
s.writeErr(w, r, err, out)
|
||||
return nil
|
||||
@@ -214,8 +217,9 @@ func (s *Server) applyChangeset(w http.ResponseWriter, r *http.Request, p *auth.
|
||||
return err
|
||||
}
|
||||
var in struct {
|
||||
DryRun bool `json:"dry_run"`
|
||||
Force bool `json:"force"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
Force bool `json:"force"`
|
||||
AcceptRisk bool `json:"accept_risk"`
|
||||
}
|
||||
if body, err := readBody(r); err != nil {
|
||||
return err
|
||||
@@ -224,7 +228,8 @@ func (s *Server) applyChangeset(w http.ResponseWriter, r *http.Request, p *auth.
|
||||
return badRequest("invalid JSON: %v", err)
|
||||
}
|
||||
}
|
||||
res, err := s.eng.Apply(r.Context(), id, p, engine.ApplyOptions{DryRun: in.DryRun || queryBool(r, "dry_run"), Force: in.Force || queryBool(r, "force")})
|
||||
res, err := s.eng.Apply(r.Context(), id, p, engine.ApplyOptions{DryRun: in.DryRun || queryBool(r, "dry_run"), Force: in.Force || queryBool(r, "force"),
|
||||
AcceptRisk: in.AcceptRisk || queryBool(r, "accept_risk")})
|
||||
if err != nil {
|
||||
s.writeErr(w, r, err, res)
|
||||
return nil
|
||||
|
||||
Reference in new issue
Block a user