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
@@ -82,6 +82,7 @@ func Check(st *model.State, env *Env) []Problem {
|
||||
for _, x := range st.Redirects {
|
||||
c.redirect(x)
|
||||
}
|
||||
c.banLists()
|
||||
return c.out
|
||||
}
|
||||
|
||||
@@ -233,9 +234,9 @@ func (c *checker) sets() {
|
||||
for _, s := range c.st.Sets {
|
||||
obj := "set " + s.Name
|
||||
c.name("set", obj, s.Name, seen)
|
||||
for _, pre := range []string{"meter_", "conn_", "quota_", "stats_"} {
|
||||
for _, pre := range []string{"meter_", "conn_", "quota_", "stats_", "ban_"} {
|
||||
if strings.HasPrefix(strings.ToLower(s.Name), pre) {
|
||||
c.add(Error, "set_name_reserved", obj, "set names starting with %s are Housecarl's own (rate limits, quotas, statistics); choose another name", pre)
|
||||
c.add(Error, "set_name_reserved", obj, "set names starting with %s are Housecarl's own (rate limits, quotas, statistics, ban lists); choose another name", pre)
|
||||
}
|
||||
}
|
||||
for _, e := range s.Entries {
|
||||
@@ -612,3 +613,48 @@ func (c *checker) redirect(x *model.Redirect) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *checker) banLists() {
|
||||
seen := map[string]string{}
|
||||
for _, b := range c.st.BanLists {
|
||||
obj := "ban list " + b.Name
|
||||
c.name("banlist", obj, b.Name, seen)
|
||||
known := false
|
||||
for _, sc := range model.BanScopes {
|
||||
known = known || b.Scope == sc
|
||||
}
|
||||
if !known {
|
||||
c.add(Error, "banlist_scope", obj, "the scope is %q; use firewall (the firewall itself), hosts (the list's hosts) or everything", b.Scope)
|
||||
}
|
||||
switch {
|
||||
case b.Scope == model.BanScopeHosts && len(b.Hosts) == 0:
|
||||
c.add(Error, "banlist_hosts", obj, "a list with scope hosts needs its hosts: the addresses, or sets, of the machines it protects")
|
||||
case b.Scope != model.BanScopeHosts && len(b.Hosts) > 0:
|
||||
c.add(Error, "banlist_hosts", obj, "only a list with scope hosts has hosts; leave them out, or use scope hosts")
|
||||
}
|
||||
refs := func(what string, list []string) {
|
||||
for _, r := range list {
|
||||
if !model.IsLiteral(r) && c.st.Set(r) == nil {
|
||||
if _, err := model.ParseAddress(r); strings.ContainsAny(r, "./:") {
|
||||
c.add(Error, "banlist_address", obj, "%s: %v", what, err)
|
||||
} else {
|
||||
c.add(Error, "unknown_set", obj, "%s: there is no set %s", what, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
refs("hosts", b.Hosts)
|
||||
refs("never", b.Never)
|
||||
if b.DefaultTime < 0 || b.MaxTime < -1 {
|
||||
c.add(Error, "banlist_time", obj, "ban times are seconds: default_time 0 (an hour) or more, max_time 0 (a week), more, or -1 for no limit")
|
||||
} else if max := b.BanMaxTime(); max > 0 && b.BanTime() > max {
|
||||
c.add(Error, "banlist_time", obj, "the default ban time (%ds) is longer than the longest (%ds)", b.BanTime(), max)
|
||||
}
|
||||
if b.MaxEntries < 0 || b.MaxEntries > 1000000 {
|
||||
c.add(Error, "banlist_entries", obj, "max_entries is %d; use 1 to 1000000, or 0 for 10000", b.MaxEntries)
|
||||
}
|
||||
if b.GroupV6 != 0 && (b.GroupV6 < 16 || b.GroupV6 > 128) {
|
||||
c.add(Error, "banlist_group", obj, "group_v6 is /%d; use 16 to 128 (64 bans what one customer usually gets, 128 one address)", b.GroupV6)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user