Ban lists: where Guard and fail2ban ban addresses
A program bans an address (every port, or a protocol's ports) for a time; the ban list says what it is kept from: the firewall, some hosts behind it, or everything. Bans drop ahead of every rule, close the address's connections, group IPv6 by /64, are kept in the database across restarts and audited. Never banned: the inside, the firewall's own addresses, the caller, the list's never. bans grant (bans:write:LIST), hc banlist/bans/ban/unban, Traffic -> Bans, a fail2ban action in contrib/, and failed logins and refused tokens logged for Guard and fail2ban. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6f56eefae2
commit
500bfc7bdd
44 files changed
+2541
-39
No files matched your search
+48
-1
@@ -190,6 +190,30 @@ func (r *Runner) DeleteElement(ctx context.Context, set, addr string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// AddBan puts a ban in force: added, or if it is there already, given
|
||||
// its new time (nftables keeps an element's old timeout when it is added
|
||||
// again, so it is added, deleted and added in one transaction).
|
||||
func (r *Runner) AddBan(ctx context.Context, b render.Ban) error {
|
||||
set := render.BanSet(b.List, b.Addr.Addr().Is6(), b.Proto != "")
|
||||
e := render.BanElement(b)
|
||||
t := ""
|
||||
if b.Timeout > 0 {
|
||||
t = fmt.Sprintf(" timeout %ds", b.Timeout)
|
||||
}
|
||||
script := fmt.Sprintf("add element inet %[1]s %[2]s { %[3]s }\ndelete element inet %[1]s %[2]s { %[3]s }\nadd element inet %[1]s %[2]s { %[3]s%[4]s }\n", render.Table, set, e, t)
|
||||
_, err := r.run(ctx, script, "-f", "/dev/stdin")
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteBan lifts a ban. One already gone (it ran out) isn't an error.
|
||||
func (r *Runner) DeleteBan(ctx context.Context, b render.Ban) error {
|
||||
set := render.BanSet(b.List, b.Addr.Addr().Is6(), b.Proto != "")
|
||||
e := render.BanElement(b)
|
||||
script := fmt.Sprintf("add element inet %[1]s %[2]s { %[3]s }\ndelete element inet %[1]s %[2]s { %[3]s }\n", render.Table, set, e)
|
||||
_, err := r.run(ctx, script, "-f", "/dev/stdin")
|
||||
return err
|
||||
}
|
||||
|
||||
// ---- reading JSON
|
||||
|
||||
// Table is what Housecarl reads back from its table: counters by mark
|
||||
@@ -367,7 +391,7 @@ func ParseTable(data []byte) (*Table, error) {
|
||||
continue
|
||||
}
|
||||
name := strings.TrimSuffix(strings.TrimSuffix(s.Name, "_v4"), "_v6")
|
||||
if name == s.Name || strings.HasPrefix(s.Name, "meter_") || strings.HasPrefix(s.Name, "conn_") {
|
||||
if name == s.Name || strings.HasPrefix(s.Name, "meter_") || strings.HasPrefix(s.Name, "conn_") || render.IsBanSet(s.Name) {
|
||||
continue
|
||||
}
|
||||
for _, e := range s.Elem {
|
||||
@@ -428,6 +452,9 @@ type Firewall struct {
|
||||
// quotaRules are the rules with a quota in the ruleset in force, by
|
||||
// ID: what their usage was counted by (nil before the first apply).
|
||||
quotaRules map[int64]*model.Rule
|
||||
// Bans returns the active bans, which every apply puts back (the
|
||||
// database keeps them; nil: none).
|
||||
Bans func() []render.Ban
|
||||
// flowDevs are the flowtable's devices in the ruleset in force, by
|
||||
// name, with their interface index then (0: left out, missing).
|
||||
flowDevs map[string]int
|
||||
@@ -654,6 +681,9 @@ func (f *Firewall) Apply(ctx context.Context, st *model.State) error {
|
||||
usage.apply(f.adjust)
|
||||
f.adjust = nil
|
||||
opt.Quotas, opt.QuotaSets = usage.Quotas, usage.QuotaSets
|
||||
if f.Bans != nil {
|
||||
opt.Bans = f.Bans()
|
||||
}
|
||||
if old != nil {
|
||||
opt.DeviceSets = old.DeviceSets
|
||||
}
|
||||
@@ -708,7 +738,16 @@ func pruneCounters(base map[string]Counter, st *model.State) {
|
||||
for _, x := range st.Redirects {
|
||||
ids[fmt.Sprintf("d%d", x.ID)] = true
|
||||
}
|
||||
for _, x := range st.BanLists {
|
||||
ids[render.BanMark(x.ID)] = true
|
||||
}
|
||||
for mark := range base {
|
||||
if strings.HasPrefix(mark, "ban") && strings.Trim(mark[3:], "0123456789") == "" {
|
||||
if !ids[mark] {
|
||||
delete(base, mark)
|
||||
}
|
||||
continue
|
||||
}
|
||||
obj, _, _ := strings.Cut(mark, "-") // f3-hairpin, f3-6 are f3's
|
||||
if len(obj) < 2 || !strings.ContainsRune("rfsd", rune(obj[0])) || strings.Trim(obj[1:], "0123456789") != "" {
|
||||
continue // b-established, p-input
|
||||
@@ -791,6 +830,14 @@ func (f *Firewall) AddElement(ctx context.Context, set, addr string, timeout int
|
||||
return f.Runner.AddElement(ctx, set, addr, timeout)
|
||||
}
|
||||
|
||||
// AddBan puts a ban in force (Runner.AddBan).
|
||||
func (f *Firewall) AddBan(ctx context.Context, b render.Ban) error { return f.Runner.AddBan(ctx, b) }
|
||||
|
||||
// DeleteBan lifts a ban (Runner.DeleteBan).
|
||||
func (f *Firewall) DeleteBan(ctx context.Context, b render.Ban) error {
|
||||
return f.Runner.DeleteBan(ctx, b)
|
||||
}
|
||||
|
||||
// DeleteElement removes an entry from a dynamic set.
|
||||
func (f *Firewall) DeleteElement(ctx context.Context, set, addr string) error {
|
||||
return f.Runner.DeleteElement(ctx, set, addr)
|
||||
|
||||
Reference in new issue
Block a user