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
+20
-12
@@ -147,13 +147,14 @@ func NewSessionID() (id, csrf string) {
|
||||
const (
|
||||
ResConfig = "config" // the firewall configuration, its history and imports
|
||||
ResSets = "sets" // dynamic sets' entries (a grant may name the sets)
|
||||
ResBans = "bans" // ban lists' bans (a grant may name the lists)
|
||||
ResLog = "log" // counters, the drop log and the status
|
||||
ResAudit = "audit" // the audit log
|
||||
ResAll = "*"
|
||||
)
|
||||
|
||||
// Resources lists them in display order.
|
||||
var Resources = []string{ResConfig, ResSets, ResLog, ResAudit}
|
||||
var Resources = []string{ResConfig, ResSets, ResBans, ResLog, ResAudit}
|
||||
|
||||
// Access levels.
|
||||
const (
|
||||
@@ -163,8 +164,8 @@ const (
|
||||
|
||||
// Roles are ready-made sets of grants.
|
||||
var Roles = map[string][]model.Grant{
|
||||
"viewer": {{Resource: ResConfig, Access: Read}, {Resource: ResSets, Access: Read}, {Resource: ResLog, Access: Read}},
|
||||
"operator": {{Resource: ResConfig, Access: Write}, {Resource: ResSets, Access: Write}, {Resource: ResLog, Access: Read}, {Resource: ResAudit, Access: Read}},
|
||||
"viewer": {{Resource: ResConfig, Access: Read}, {Resource: ResSets, Access: Read}, {Resource: ResBans, Access: Read}, {Resource: ResLog, Access: Read}},
|
||||
"operator": {{Resource: ResConfig, Access: Write}, {Resource: ResSets, Access: Write}, {Resource: ResBans, Access: Write}, {Resource: ResLog, Access: Read}, {Resource: ResAudit, Access: Read}},
|
||||
"log": {{Resource: ResLog, Access: Read}},
|
||||
}
|
||||
|
||||
@@ -179,12 +180,13 @@ func RoleNames() []string {
|
||||
}
|
||||
|
||||
// ParseGrant reads "resource:access" ("log:read", "*:write"), and for
|
||||
// sets optionally the sets it is limited to ("sets:write:guest,portal").
|
||||
// sets and bans optionally the sets or ban lists it is limited to
|
||||
// ("sets:write:guest,portal", "bans:write:web").
|
||||
func ParseGrant(s string) (model.Grant, error) {
|
||||
parts := strings.SplitN(strings.TrimSpace(s), ":", 3)
|
||||
g := model.Grant{Resource: strings.ToLower(parts[0])}
|
||||
if len(parts) < 2 {
|
||||
return g, fmt.Errorf("grant %q: write it as resource:access, e.g. log:read, or sets:write:SET1,SET2", s)
|
||||
return g, fmt.Errorf("grant %q: write it as resource:access, e.g. log:read, sets:write:SET1,SET2 or bans:write:LIST", s)
|
||||
}
|
||||
g.Access = strings.ToLower(parts[1])
|
||||
if len(parts) == 3 {
|
||||
@@ -211,8 +213,8 @@ func ValidateGrant(g *model.Grant) error {
|
||||
if g.Access != Read && g.Access != Write {
|
||||
return fmt.Errorf("access must be read or write, not %q", g.Access)
|
||||
}
|
||||
if len(g.Sets) > 0 && g.Resource != ResSets {
|
||||
return fmt.Errorf("only a sets grant can name sets (sets:write:%s)", strings.Join(g.Sets, ","))
|
||||
if len(g.Sets) > 0 && g.Resource != ResSets && g.Resource != ResBans {
|
||||
return fmt.Errorf("only a sets or bans grant can name sets or ban lists (sets:write:%s)", strings.Join(g.Sets, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -272,20 +274,26 @@ func covers(gs []model.Grant, resource string, write bool) bool {
|
||||
|
||||
// CanSet reports whether the principal may read or change the entries of
|
||||
// a dynamic set: a sets grant covers it if it names no sets, or names it.
|
||||
func (p *Principal) CanSet(name string, write bool) bool {
|
||||
func (p *Principal) CanSet(name string, write bool) bool { return p.canNamed(ResSets, name, write) }
|
||||
|
||||
// CanBan reports whether the principal may read or change the bans of a
|
||||
// ban list: a bans grant covers it if it names no lists, or names it.
|
||||
func (p *Principal) CanBan(list string, write bool) bool { return p.canNamed(ResBans, list, write) }
|
||||
|
||||
func (p *Principal) canNamed(resource, name string, write bool) bool {
|
||||
if p == nil || p.User == nil {
|
||||
return false
|
||||
}
|
||||
allowed := p.User.Admin || coversSet(p.User.Grants, name, write)
|
||||
allowed := p.User.Admin || coversNamed(p.User.Grants, resource, name, write)
|
||||
if p.Token != nil && p.Token.Grants != nil {
|
||||
allowed = allowed && coversSet(p.Token.Grants, name, write)
|
||||
allowed = allowed && coversNamed(p.Token.Grants, resource, name, write)
|
||||
}
|
||||
return allowed
|
||||
}
|
||||
|
||||
func coversSet(gs []model.Grant, name string, write bool) bool {
|
||||
func coversNamed(gs []model.Grant, resource, name string, write bool) bool {
|
||||
for _, g := range gs {
|
||||
if (g.Resource != ResAll && g.Resource != ResSets) || (write && g.Access != Write) {
|
||||
if (g.Resource != ResAll && g.Resource != resource) || (write && g.Access != Write) {
|
||||
continue
|
||||
}
|
||||
if len(g.Sets) == 0 {
|
||||
|
||||
Reference in new issue
Block a user