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
@@ -765,9 +765,41 @@ func setViewFields(v *model.View, in *ViewInput) error {
|
||||
if in.Position != nil {
|
||||
v.Position = *in.Position
|
||||
}
|
||||
if in.Exposure != nil {
|
||||
e, err := normalizeExposure(*in.Exposure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Exposure = e
|
||||
}
|
||||
if in.Acknowledged != nil {
|
||||
v.Acknowledged = cleanAcks(*in.Acknowledged)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeExposure checks an exposure override.
|
||||
func normalizeExposure(s string) (string, error) {
|
||||
switch e := strings.ToLower(strings.TrimSpace(s)); e {
|
||||
case "", "auto":
|
||||
return model.ExposureAuto, nil
|
||||
case model.ExposurePublic, model.ExposurePrivate:
|
||||
return e, nil
|
||||
}
|
||||
return "", errf(KindBadRequest, "exposure must be auto, public or private, not %q", s)
|
||||
}
|
||||
|
||||
// cleanAcks drops empty acknowledgements (nil for none).
|
||||
func cleanAcks(acks []model.Acknowledgement) []model.Acknowledgement {
|
||||
var out []model.Acknowledgement
|
||||
for _, a := range acks {
|
||||
if a.Code != "" {
|
||||
out = append(out, a)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func applyViewCreate(st *model.State, op model.Op, c *applyCtx) error {
|
||||
var in ViewInput
|
||||
if err := decode(op.Data, &in); err != nil {
|
||||
@@ -1329,6 +1361,16 @@ func applySettings(st *model.State, op model.Op, c *applyCtx) error {
|
||||
if in.SerialPolicy != nil {
|
||||
s.SerialPolicy = strings.ToLower(*in.SerialPolicy)
|
||||
}
|
||||
if in.Exposure != nil {
|
||||
e, err := normalizeExposure(*in.Exposure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Exposure = e
|
||||
}
|
||||
if in.Acknowledged != nil {
|
||||
s.Acknowledged = cleanAcks(*in.Acknowledged)
|
||||
}
|
||||
s.Version++
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user