Admin endpoints for bans and clients on the single listener (closes #27)
check / check (push) Successful in 4m26s
check / check (push) Successful in 4m26s
SWWAF_ADMIN_TOKEN, or its _FILE form, opens GET and POST /_smallwebwaf/bans, DELETE /_smallwebwaf/bans/<client> and GET /_smallwebwaf/clients/<ip>. Unset, they answer 404; a missing or wrong token gets 401, in observe mode too. They go through every check, as the metrics do. POST takes a netblock, not IPv4-mapped and without a zone, or a client's address, a duration or permanent, and a reason, and makes an admin ban even while another lasts. DELETE lifts every active ban covering the address, kept and marked lifted. Bans come back as bans.json entries; a client as clients.json holds it, with its bans. Judgement call: answers leave out bans.json's version field. Judgement call: DELETE takes an address, not a netblock. Rule suppressed: gosec G304 on a test reading bans.json. Model: opus-5-5
This commit was merged in pull request #92.
This commit is contained in:
+20
-13
@@ -92,13 +92,14 @@ type Files struct {
|
||||
// bansFile is bans.json, indented for an admin to read and edit.
|
||||
type bansFile struct {
|
||||
Version int `json:"version"`
|
||||
Bans []banEntry `json:"bans"`
|
||||
Bans []BanEntry `json:"bans"`
|
||||
}
|
||||
|
||||
// banEntry is a ban as bans.json holds it: a permanent ban's expires is
|
||||
// BanEntry is a ban as bans.json holds it: a permanent ban's expires is
|
||||
// null, a ban an admin added may have no cause, which makes it an
|
||||
// admin's, and lifted is left out until an admin lifts the ban.
|
||||
type banEntry struct {
|
||||
// admin's, and lifted is left out until an admin lifts the ban. The ban
|
||||
// endpoints answer with bans in this form too.
|
||||
type BanEntry struct {
|
||||
Netblock netip.Prefix `json:"netblock"`
|
||||
Start time.Time `json:"start"`
|
||||
Expires *time.Time `json:"expires"`
|
||||
@@ -434,12 +435,7 @@ func (f *Files) setAside(name string, parseErr error) error {
|
||||
func (f *Files) encode(name string) ([]byte, error) {
|
||||
switch name {
|
||||
case bansJSON:
|
||||
held := f.params.Ledger.Snapshot()
|
||||
|
||||
file := bansFile{Version: version, Bans: make([]banEntry, 0, len(held))}
|
||||
for _, ban := range held {
|
||||
file.Bans = append(file.Bans, newBanEntry(ban))
|
||||
}
|
||||
file := bansFile{Version: version, Bans: BanEntries(f.params.Ledger.Snapshot())}
|
||||
|
||||
data, err := json.MarshalIndent(file, "", " ")
|
||||
if err != nil {
|
||||
@@ -454,9 +450,20 @@ func (f *Files) encode(name string) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// BanEntries returns held as bans.json lists them, an empty list for
|
||||
// none.
|
||||
func BanEntries(held []bans.Ban) []BanEntry {
|
||||
entries := make([]BanEntry, 0, len(held))
|
||||
for _, ban := range held {
|
||||
entries = append(entries, newBanEntry(ban))
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// newBanEntry returns ban as bans.json holds it.
|
||||
func newBanEntry(ban bans.Ban) banEntry {
|
||||
entry := banEntry{
|
||||
func newBanEntry(ban bans.Ban) BanEntry {
|
||||
entry := BanEntry{
|
||||
Netblock: ban.Netblock, Start: ban.Start, Cause: ban.Cause, Reason: ban.Reason,
|
||||
Notes: ban.Notes,
|
||||
}
|
||||
@@ -472,7 +479,7 @@ func newBanEntry(ban bans.Ban) banEntry {
|
||||
}
|
||||
|
||||
// ban returns the ban an entry of bans.json holds.
|
||||
func (e banEntry) ban() bans.Ban {
|
||||
func (e BanEntry) ban() bans.Ban {
|
||||
ban := bans.Ban{
|
||||
Netblock: e.Netblock, Start: e.Start, Cause: e.Cause, Reason: e.Reason,
|
||||
Notes: e.Notes,
|
||||
|
||||
Reference in New Issue
Block a user