Add bulk import for global firewall allow/block lists
Paste-in import: one CIDR/IP per line, optional comment after '#', blank/comment-only lines ignored - matches the format used by common public blocklist feeds (Spamhaus DROP, blocklist.de, etc.) so those can mostly be pasted in directly. Whole batch gets one list_type (allow or block). Reuses the existing single-entry validation, skips duplicates (by list_type+CIDR, including within the same paste), caps at 5000 lines, and reports imported/skipped/invalid counts plus per-line errors. New route: POST /firewall-lists/entries/import (admin-only). UI: a collapsible "Bulk import" section on the Global Firewall Lists page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
eb1913d400
commit
77ba2b4799
@@ -1843,6 +1843,105 @@ func DeleteIPListEntryHandler(db store.IStore) echo.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// maxBulkImportLines caps the number of lines accepted by
|
||||
// BulkImportIPListEntries, to avoid pathological input.
|
||||
const maxBulkImportLines = 5000
|
||||
|
||||
// BulkImportIPListEntries imports many allow/block list entries at once from
|
||||
// a pasted block of text, one CIDR/IP per line. Lines may have an optional
|
||||
// "# comment" suffix. Blank lines and lines starting with '#' are ignored.
|
||||
// All imported entries share the same list_type for the whole batch.
|
||||
func BulkImportIPListEntries(db store.IStore) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
var payload struct {
|
||||
ListType string `json:"list_type"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
if err := c.Bind(&payload); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "Bad post data"})
|
||||
}
|
||||
if payload.ListType != "allow" && payload.ListType != "block" {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false, "list_type must be 'allow' or 'block'"})
|
||||
}
|
||||
|
||||
lines := strings.Split(payload.Text, "\n")
|
||||
if len(lines) > maxBulkImportLines {
|
||||
return c.JSON(http.StatusBadRequest, jsonHTTPResponse{false,
|
||||
fmt.Sprintf("Too many lines: %d (max %d)", len(lines), maxBulkImportLines)})
|
||||
}
|
||||
|
||||
existing, err := db.GetIPListEntries()
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, jsonHTTPResponse{false, err.Error()})
|
||||
}
|
||||
existingSet := make(map[string]bool, len(existing))
|
||||
for _, e := range existing {
|
||||
existingSet[e.ListType+"|"+e.CIDR] = true
|
||||
}
|
||||
|
||||
type invalidLine struct {
|
||||
Line int `json:"line"`
|
||||
Text string `json:"text"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
imported := 0
|
||||
skippedDuplicates := 0
|
||||
var invalidLines []invalidLine
|
||||
|
||||
for i, raw := range lines {
|
||||
lineNum := i + 1
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
cidr := trimmed
|
||||
comment := ""
|
||||
if idx := strings.Index(trimmed, "#"); idx >= 0 {
|
||||
cidr = strings.TrimSpace(trimmed[:idx])
|
||||
comment = strings.TrimSpace(trimmed[idx+1:])
|
||||
}
|
||||
if cidr == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
entry := model.IPListEntry{
|
||||
ListType: payload.ListType,
|
||||
CIDR: cidr,
|
||||
Comment: comment,
|
||||
}
|
||||
if err := validateIPListEntry(entry); err != nil {
|
||||
invalidLines = append(invalidLines, invalidLine{Line: lineNum, Text: raw, Error: err.Error()})
|
||||
continue
|
||||
}
|
||||
|
||||
key := entry.ListType + "|" + entry.CIDR
|
||||
if existingSet[key] {
|
||||
skippedDuplicates++
|
||||
continue
|
||||
}
|
||||
|
||||
entry.ID = xid.New().String()
|
||||
entry.CreatedAt = time.Now().UTC()
|
||||
if err := db.CreateIPListEntry(entry); err != nil {
|
||||
invalidLines = append(invalidLines, invalidLine{Line: lineNum, Text: raw, Error: err.Error()})
|
||||
continue
|
||||
}
|
||||
existingSet[key] = true
|
||||
imported++
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"success": true,
|
||||
"imported": imported,
|
||||
"skipped_duplicates": skippedDuplicates,
|
||||
"invalid_count": len(invalidLines),
|
||||
"invalid_lines": invalidLines,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobalFirewallPreview returns the generated host-wide allow/block list
|
||||
// ruleset as plain text. Preview only.
|
||||
func GetGlobalFirewallPreview(db store.IStore) echo.HandlerFunc {
|
||||
|
||||
Reference in New Issue
Block a user