4f29ce2ee7
* Add CTI API helpers in expr * Allow profiles to have an `on_error` option to profiles Co-authored-by: Sebastien Blot <sebastien@crowdsec.net>
157 lines
3.6 KiB
Go
157 lines
3.6 KiB
Go
package cticlient
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
const (
|
|
CTIBaseUrl = "https://cti.api.crowdsec.net/v2"
|
|
smokeEndpoint = "/smoke"
|
|
fireEndpoint = "/fire"
|
|
)
|
|
|
|
var (
|
|
ErrUnauthorized = errors.New("unauthorized")
|
|
ErrLimit = errors.New("request quota exceeded, please reduce your request rate")
|
|
ErrNotFound = errors.New("ip not found")
|
|
ErrDisabled = errors.New("cti is disabled")
|
|
ErrUnknown = errors.New("unknown error")
|
|
)
|
|
|
|
type CrowdsecCTIClient struct {
|
|
httpClient *http.Client
|
|
apiKey string
|
|
Logger *log.Entry
|
|
}
|
|
|
|
func (c *CrowdsecCTIClient) doRequest(method string, endpoint string, params map[string]string) ([]byte, error) {
|
|
url := CTIBaseUrl + endpoint
|
|
if len(params) > 0 {
|
|
url += "?"
|
|
for k, v := range params {
|
|
url += fmt.Sprintf("%s=%s&", k, v)
|
|
}
|
|
}
|
|
req, err := http.NewRequest(method, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("x-api-key", c.apiKey)
|
|
resp, err := c.httpClient.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
if resp.StatusCode == http.StatusForbidden {
|
|
return nil, ErrUnauthorized
|
|
}
|
|
if resp.StatusCode == http.StatusTooManyRequests {
|
|
return nil, ErrLimit
|
|
}
|
|
if resp.StatusCode == http.StatusNotFound {
|
|
return nil, ErrNotFound
|
|
}
|
|
return nil, fmt.Errorf("unexpected http code : %s", resp.Status)
|
|
}
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return respBody, nil
|
|
}
|
|
|
|
func (c *CrowdsecCTIClient) GetIPInfo(ip string) (*SmokeItem, error) {
|
|
body, err := c.doRequest(http.MethodGet, smokeEndpoint+"/"+ip, nil)
|
|
if err != nil {
|
|
if err == ErrNotFound {
|
|
return &SmokeItem{}, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
item := SmokeItem{}
|
|
err = json.Unmarshal(body, &item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &item, nil
|
|
}
|
|
|
|
func (c *CrowdsecCTIClient) SearchIPs(ips []string) (*SearchIPResponse, error) {
|
|
params := make(map[string]string)
|
|
params["ips"] = strings.Join(ips, ",")
|
|
body, err := c.doRequest(http.MethodGet, smokeEndpoint, params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
searchIPResponse := SearchIPResponse{}
|
|
err = json.Unmarshal(body, &searchIPResponse)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &searchIPResponse, nil
|
|
}
|
|
|
|
func (c *CrowdsecCTIClient) Fire(params FireParams) (*FireResponse, error) {
|
|
paramsMap := make(map[string]string)
|
|
if params.Page != nil {
|
|
paramsMap["page"] = fmt.Sprintf("%d", *params.Page)
|
|
}
|
|
if params.Since != nil {
|
|
paramsMap["since"] = *params.Since
|
|
}
|
|
if params.Limit != nil {
|
|
paramsMap["limit"] = fmt.Sprintf("%d", *params.Limit)
|
|
}
|
|
|
|
body, err := c.doRequest(http.MethodGet, fireEndpoint, paramsMap)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fireResponse := FireResponse{}
|
|
err = json.Unmarshal(body, &fireResponse)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &fireResponse, nil
|
|
}
|
|
|
|
func NewCrowdsecCTIClient(options ...func(*CrowdsecCTIClient)) *CrowdsecCTIClient {
|
|
client := &CrowdsecCTIClient{}
|
|
for _, option := range options {
|
|
option(client)
|
|
}
|
|
if client.httpClient == nil {
|
|
client.httpClient = &http.Client{}
|
|
}
|
|
// we cannot return with a ni logger, so we set a default one
|
|
if client.Logger == nil {
|
|
client.Logger = log.NewEntry(log.New())
|
|
}
|
|
return client
|
|
}
|
|
|
|
func WithLogger(logger *log.Entry) func(*CrowdsecCTIClient) {
|
|
return func(c *CrowdsecCTIClient) {
|
|
c.Logger = logger
|
|
}
|
|
}
|
|
|
|
func WithHTTPClient(httpClient *http.Client) func(*CrowdsecCTIClient) {
|
|
return func(c *CrowdsecCTIClient) {
|
|
c.httpClient = httpClient
|
|
}
|
|
}
|
|
|
|
func WithAPIKey(apiKey string) func(*CrowdsecCTIClient) {
|
|
return func(c *CrowdsecCTIClient) {
|
|
c.apiKey = apiKey
|
|
}
|
|
}
|