package v1 import ( "encoding/json" "fmt" "net/http" "strconv" "strings" "github.com/gorilla/mux" ) // CrowdSecStatus returns whether CrowdSec is running on Wild Central // and lists registered machines and bouncers. func (api *API) CrowdSecStatus(w http.ResponseWriter, r *http.Request) { status, err := api.crowdsec.GetStatus() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get CrowdSec status: %v", err)) return } respondJSON(w, http.StatusOK, status) } // CrowdSecGetSummary returns aggregated ban counts by scenario across all active decisions. // This includes CAPI community bans — can return tens of thousands of entries. func (api *API) CrowdSecGetSummary(w http.ResponseWriter, r *http.Request) { summary, err := api.crowdsec.GetBanSummary() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get ban summary: %v", err)) return } respondJSON(w, http.StatusOK, summary) } // CrowdSecGetDecisions returns active ban/captcha decisions func (api *API) CrowdSecGetDecisions(w http.ResponseWriter, r *http.Request) { decisions, err := api.crowdsec.GetDecisions() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get decisions: %v", err)) return } respondJSON(w, http.StatusOK, map[string]any{"decisions": decisions}) } // CrowdSecDeleteDecision removes a ban decision by numeric ID func (api *API) CrowdSecDeleteDecision(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, err := strconv.Atoi(vars["id"]) if err != nil { respondError(w, http.StatusBadRequest, "invalid decision ID") return } if err := api.crowdsec.DeleteDecision(id); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete decision: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{"message": "Decision deleted"}) } // CrowdSecAddDecision adds a manual ban or allow decision for an IP. // Body: { "ip": "1.2.3.4", "type": "ban", "reason": "manual", "duration": "24h" } func (api *API) CrowdSecAddDecision(w http.ResponseWriter, r *http.Request) { var req struct { IP string `json:"ip"` Type string `json:"type"` Reason string `json:"reason"` Duration string `json:"duration"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "invalid request body") return } if req.IP == "" { respondError(w, http.StatusBadRequest, "ip is required") return } if req.Type != "ban" && req.Type != "allow" { respondError(w, http.StatusBadRequest, "type must be 'ban' or 'allow'") return } if req.Reason == "" { req.Reason = "manual" } if req.Duration == "" { req.Duration = "24h" } if err := api.crowdsec.AddDecision(req.IP, req.Type, req.Reason, req.Duration); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to add decision: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{"message": fmt.Sprintf("Decision added: %s %s for %s", req.Type, req.IP, req.Duration)}) } // CrowdSecDeleteDecisionByIP removes all decisions for a specific IP address func (api *API) CrowdSecDeleteDecisionByIP(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) ip := vars["ip"] if ip == "" { respondError(w, http.StatusBadRequest, "ip is required") return } if err := api.crowdsec.DeleteDecisionByIP(ip); err != nil { // cscli exits non-zero when there's nothing to delete — treat as success if strings.Contains(err.Error(), "no decision") || strings.Contains(err.Error(), "0 decision") { respondJSON(w, http.StatusOK, map[string]string{"message": "No active decision for " + ip}) return } respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete decision: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{"message": "Decisions removed for " + ip}) } // CrowdSecGetAlerts returns recent detection events from registered agents. // Query param: limit (default 50) func (api *API) CrowdSecGetAlerts(w http.ResponseWriter, r *http.Request) { limit := 50 if l := r.URL.Query().Get("limit"); l != "" { if n, err := strconv.Atoi(l); err == nil && n > 0 && n <= 200 { limit = n } } alerts, err := api.crowdsec.GetAlerts(limit) if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get alerts: %v", err)) return } respondJSON(w, http.StatusOK, map[string]any{"alerts": alerts}) } // CrowdSecGetMachines returns all registered CrowdSec agent machines func (api *API) CrowdSecGetMachines(w http.ResponseWriter, r *http.Request) { machines, err := api.crowdsec.GetMachines() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get machines: %v", err)) return } respondJSON(w, http.StatusOK, map[string]any{"machines": machines}) } // CrowdSecDeleteMachine removes a registered machine func (api *API) CrowdSecDeleteMachine(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) name := vars["name"] if err := api.crowdsec.DeleteMachine(name); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete machine: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{"message": "Machine deleted"}) } // CrowdSecGetBouncers returns all registered bouncers func (api *API) CrowdSecGetBouncers(w http.ResponseWriter, r *http.Request) { bouncers, err := api.crowdsec.GetBouncers() if err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get bouncers: %v", err)) return } respondJSON(w, http.StatusOK, map[string]any{"bouncers": bouncers}) } // CrowdSecDeleteBouncer removes a registered bouncer func (api *API) CrowdSecDeleteBouncer(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) name := vars["name"] if err := api.crowdsec.DeleteBouncer(name); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete bouncer: %v", err)) return } respondJSON(w, http.StatusOK, map[string]string{"message": "Bouncer deleted"}) } // CrowdSecAddMachine registers a new CrowdSec agent machine with pre-generated credentials. // Body: { "name": "...", "password": "..." } func (api *API) CrowdSecAddMachine(w http.ResponseWriter, r *http.Request) { var req struct { Name string `json:"name"` Password string `json:"password"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "invalid request body") return } if req.Name == "" || req.Password == "" { respondError(w, http.StatusBadRequest, "name and password are required") return } if err := api.crowdsec.AddMachine(req.Name, req.Password); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to add machine: %v", err)) return } respondJSON(w, http.StatusCreated, map[string]string{"message": fmt.Sprintf("Machine %s registered", req.Name)}) } // CrowdSecAddBouncer registers a new bouncer with a pre-generated API key. // Body: { "name": "...", "apiKey": "..." } func (api *API) CrowdSecAddBouncer(w http.ResponseWriter, r *http.Request) { var req struct { Name string `json:"name"` APIKey string `json:"apiKey"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { respondError(w, http.StatusBadRequest, "invalid request body") return } if req.Name == "" || req.APIKey == "" { respondError(w, http.StatusBadRequest, "name and apiKey are required") return } if err := api.crowdsec.AddBouncer(req.Name, req.APIKey); err != nil { respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to add bouncer: %v", err)) return } respondJSON(w, http.StatusCreated, map[string]string{"message": fmt.Sprintf("Bouncer %s registered", req.Name)}) }