github.com/crowdsecurity/crowdsec@v1.6.1/pkg/cticlient/client.go (about)

     1  package cticlient
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  )
    13  
    14  const (
    15  	CTIBaseUrl    = "https://cti.api.crowdsec.net/v2"
    16  	smokeEndpoint = "/smoke"
    17  	fireEndpoint  = "/fire"
    18  )
    19  
    20  var (
    21  	ErrUnauthorized = errors.New("unauthorized")
    22  	ErrLimit        = errors.New("request quota exceeded, please reduce your request rate")
    23  	ErrNotFound     = errors.New("ip not found")
    24  	ErrDisabled     = errors.New("cti is disabled")
    25  	ErrUnknown      = errors.New("unknown error")
    26  )
    27  
    28  type CrowdsecCTIClient struct {
    29  	httpClient *http.Client
    30  	apiKey     string
    31  	Logger     *log.Entry
    32  }
    33  
    34  func (c *CrowdsecCTIClient) doRequest(method string, endpoint string, params map[string]string) ([]byte, error) {
    35  	url := CTIBaseUrl + endpoint
    36  	if len(params) > 0 {
    37  		url += "?"
    38  		for k, v := range params {
    39  			url += fmt.Sprintf("%s=%s&", k, v)
    40  		}
    41  	}
    42  	req, err := http.NewRequest(method, url, nil)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  	req.Header.Set("x-api-key", c.apiKey)
    47  	resp, err := c.httpClient.Do(req)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  	defer resp.Body.Close()
    52  	if resp.StatusCode != http.StatusOK {
    53  		if resp.StatusCode == http.StatusForbidden {
    54  			return nil, ErrUnauthorized
    55  		}
    56  		if resp.StatusCode == http.StatusTooManyRequests {
    57  			return nil, ErrLimit
    58  		}
    59  		if resp.StatusCode == http.StatusNotFound {
    60  			return nil, ErrNotFound
    61  		}
    62  		return nil, fmt.Errorf("unexpected http code : %s", resp.Status)
    63  	}
    64  	respBody, err := io.ReadAll(resp.Body)
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  	return respBody, nil
    69  }
    70  
    71  func (c *CrowdsecCTIClient) GetIPInfo(ip string) (*SmokeItem, error) {
    72  	body, err := c.doRequest(http.MethodGet, smokeEndpoint+"/"+ip, nil)
    73  	if err != nil {
    74  		if errors.Is(err, ErrNotFound) {
    75  			return &SmokeItem{}, nil
    76  		}
    77  		return nil, err
    78  	}
    79  	item := SmokeItem{}
    80  	err = json.Unmarshal(body, &item)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  	return &item, nil
    85  }
    86  
    87  func (c *CrowdsecCTIClient) SearchIPs(ips []string) (*SearchIPResponse, error) {
    88  	params := make(map[string]string)
    89  	params["ips"] = strings.Join(ips, ",")
    90  	body, err := c.doRequest(http.MethodGet, smokeEndpoint, params)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	searchIPResponse := SearchIPResponse{}
    95  	err = json.Unmarshal(body, &searchIPResponse)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	return &searchIPResponse, nil
   100  }
   101  
   102  func (c *CrowdsecCTIClient) Fire(params FireParams) (*FireResponse, error) {
   103  	paramsMap := make(map[string]string)
   104  	if params.Page != nil {
   105  		paramsMap["page"] = fmt.Sprintf("%d", *params.Page)
   106  	}
   107  	if params.Since != nil {
   108  		paramsMap["since"] = *params.Since
   109  	}
   110  	if params.Limit != nil {
   111  		paramsMap["limit"] = fmt.Sprintf("%d", *params.Limit)
   112  	}
   113  
   114  	body, err := c.doRequest(http.MethodGet, fireEndpoint, paramsMap)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  	fireResponse := FireResponse{}
   119  	err = json.Unmarshal(body, &fireResponse)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	return &fireResponse, nil
   124  }
   125  
   126  func NewCrowdsecCTIClient(options ...func(*CrowdsecCTIClient)) *CrowdsecCTIClient {
   127  	client := &CrowdsecCTIClient{}
   128  	for _, option := range options {
   129  		option(client)
   130  	}
   131  	if client.httpClient == nil {
   132  		client.httpClient = &http.Client{}
   133  	}
   134  	// we cannot return with a ni logger, so we set a default one
   135  	if client.Logger == nil {
   136  		client.Logger = log.NewEntry(log.New())
   137  	}
   138  	return client
   139  }
   140  
   141  func WithLogger(logger *log.Entry) func(*CrowdsecCTIClient) {
   142  	return func(c *CrowdsecCTIClient) {
   143  		c.Logger = logger
   144  	}
   145  }
   146  
   147  func WithHTTPClient(httpClient *http.Client) func(*CrowdsecCTIClient) {
   148  	return func(c *CrowdsecCTIClient) {
   149  		c.httpClient = httpClient
   150  	}
   151  }
   152  
   153  func WithAPIKey(apiKey string) func(*CrowdsecCTIClient) {
   154  	return func(c *CrowdsecCTIClient) {
   155  		c.apiKey = apiKey
   156  	}
   157  }