github.com/crowdsecurity/crowdsec@v1.6.1/pkg/exprhelpers/crowdsec_cti_test.go (about)

     1  package exprhelpers
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"io"
     8  	"net/http"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/require"
    15  
    16  	"github.com/crowdsecurity/go-cs-lib/ptr"
    17  
    18  	"github.com/crowdsecurity/crowdsec/pkg/cticlient"
    19  )
    20  
    21  var sampledata = map[string]cticlient.SmokeItem{
    22  	//1.2.3.4 is a known false positive
    23  	"1.2.3.4": {
    24  		Ip: "1.2.3.4",
    25  		Classifications: cticlient.CTIClassifications{
    26  			FalsePositives: []cticlient.CTIClassification{
    27  				{
    28  					Name:  "example_false_positive",
    29  					Label: "Example False Positive",
    30  				},
    31  			},
    32  		},
    33  	},
    34  	//1.2.3.5 is a known bad-guy, and part of FIRE
    35  	"1.2.3.5": {
    36  		Ip: "1.2.3.5",
    37  		Classifications: cticlient.CTIClassifications{
    38  			Classifications: []cticlient.CTIClassification{
    39  				{
    40  					Name:        "community-blocklist",
    41  					Label:       "CrowdSec Community Blocklist",
    42  					Description: "IP belong to the CrowdSec Community Blocklist",
    43  				},
    44  			},
    45  		},
    46  	},
    47  	//1.2.3.6 is a bad guy (high bg noise), but not in FIRE
    48  	"1.2.3.6": {
    49  		Ip:                   "1.2.3.6",
    50  		BackgroundNoiseScore: new(int),
    51  		Behaviors: []*cticlient.CTIBehavior{
    52  			{Name: "ssh:bruteforce", Label: "SSH Bruteforce", Description: "SSH Bruteforce"},
    53  		},
    54  		AttackDetails: []*cticlient.CTIAttackDetails{
    55  			{Name: "crowdsecurity/ssh-bf", Label: "Example Attack"},
    56  			{Name: "crowdsecurity/ssh-slow-bf", Label: "Example Attack"},
    57  		},
    58  	},
    59  	//1.2.3.7 is a ok guy, but part of a bad range
    60  	"1.2.3.7": {},
    61  }
    62  
    63  const validApiKey = "my-api-key"
    64  
    65  type RoundTripFunc func(req *http.Request) *http.Response
    66  
    67  func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
    68  	return f(req), nil
    69  }
    70  
    71  func smokeHandler(req *http.Request) *http.Response {
    72  	apiKey := req.Header.Get("x-api-key")
    73  	if apiKey != validApiKey {
    74  		return &http.Response{
    75  			StatusCode: http.StatusForbidden,
    76  			Body:       nil,
    77  			Header:     make(http.Header),
    78  		}
    79  	}
    80  
    81  	requestedIP := strings.Split(req.URL.Path, "/")[3]
    82  
    83  	sample, ok := sampledata[requestedIP]
    84  	if !ok {
    85  		return &http.Response{
    86  			StatusCode: http.StatusNotFound,
    87  			Body:       nil,
    88  			Header:     make(http.Header),
    89  		}
    90  	}
    91  
    92  	body, err := json.Marshal(sample)
    93  	if err != nil {
    94  		return &http.Response{
    95  			StatusCode: http.StatusInternalServerError,
    96  			Body:       nil,
    97  			Header:     make(http.Header),
    98  		}
    99  	}
   100  
   101  	reader := io.NopCloser(bytes.NewReader(body))
   102  
   103  	return &http.Response{
   104  		StatusCode: http.StatusOK,
   105  		// Send response to be tested
   106  		Body:          reader,
   107  		Header:        make(http.Header),
   108  		ContentLength: 0,
   109  	}
   110  }
   111  
   112  func TestNillClient(t *testing.T) {
   113  	defer ShutdownCrowdsecCTI()
   114  
   115  	if err := InitCrowdsecCTI(ptr.Of(""), nil, nil, nil); !errors.Is(err, cticlient.ErrDisabled) {
   116  		t.Fatalf("failed to init CTI : %s", err)
   117  	}
   118  
   119  	item, err := CrowdsecCTI("1.2.3.4")
   120  	assert.Equal(t, err, cticlient.ErrDisabled)
   121  	assert.Equal(t, &cticlient.SmokeItem{}, item)
   122  }
   123  
   124  func TestInvalidAuth(t *testing.T) {
   125  	defer ShutdownCrowdsecCTI()
   126  
   127  	if err := InitCrowdsecCTI(ptr.Of("asdasd"), nil, nil, nil); err != nil {
   128  		t.Fatalf("failed to init CTI : %s", err)
   129  	}
   130  	//Replace the client created by InitCrowdsecCTI with one that uses a custom transport
   131  	ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey("asdasd"), cticlient.WithHTTPClient(&http.Client{
   132  		Transport: RoundTripFunc(smokeHandler),
   133  	}))
   134  
   135  	item, err := CrowdsecCTI("1.2.3.4")
   136  	assert.Equal(t, &cticlient.SmokeItem{}, item)
   137  	assert.False(t, CTIApiEnabled)
   138  	assert.Equal(t, err, cticlient.ErrUnauthorized)
   139  
   140  	//CTI is now disabled, all requests should return empty
   141  	ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey(validApiKey), cticlient.WithHTTPClient(&http.Client{
   142  		Transport: RoundTripFunc(smokeHandler),
   143  	}))
   144  
   145  	item, err = CrowdsecCTI("1.2.3.4")
   146  	assert.Equal(t, &cticlient.SmokeItem{}, item)
   147  	assert.False(t, CTIApiEnabled)
   148  	assert.Equal(t, err, cticlient.ErrDisabled)
   149  }
   150  
   151  func TestNoKey(t *testing.T) {
   152  	defer ShutdownCrowdsecCTI()
   153  
   154  	err := InitCrowdsecCTI(nil, nil, nil, nil)
   155  	require.ErrorIs(t, err, cticlient.ErrDisabled)
   156  	//Replace the client created by InitCrowdsecCTI with one that uses a custom transport
   157  	ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey("asdasd"), cticlient.WithHTTPClient(&http.Client{
   158  		Transport: RoundTripFunc(smokeHandler),
   159  	}))
   160  
   161  	item, err := CrowdsecCTI("1.2.3.4")
   162  	assert.Equal(t, &cticlient.SmokeItem{}, item)
   163  	assert.False(t, CTIApiEnabled)
   164  	assert.Equal(t, err, cticlient.ErrDisabled)
   165  }
   166  
   167  func TestCache(t *testing.T) {
   168  	defer ShutdownCrowdsecCTI()
   169  
   170  	cacheDuration := 1 * time.Second
   171  	if err := InitCrowdsecCTI(ptr.Of(validApiKey), &cacheDuration, nil, nil); err != nil {
   172  		t.Fatalf("failed to init CTI : %s", err)
   173  	}
   174  	//Replace the client created by InitCrowdsecCTI with one that uses a custom transport
   175  	ctiClient = cticlient.NewCrowdsecCTIClient(cticlient.WithAPIKey(validApiKey), cticlient.WithHTTPClient(&http.Client{
   176  		Transport: RoundTripFunc(smokeHandler),
   177  	}))
   178  
   179  	item, err := CrowdsecCTI("1.2.3.4")
   180  	ctiResp := item.(*cticlient.SmokeItem)
   181  	assert.Equal(t, "1.2.3.4", ctiResp.Ip)
   182  	assert.True(t, CTIApiEnabled)
   183  	assert.Equal(t, 1, CTICache.Len(true))
   184  	require.NoError(t, err)
   185  
   186  	item, err = CrowdsecCTI("1.2.3.4")
   187  	ctiResp = item.(*cticlient.SmokeItem)
   188  
   189  	assert.Equal(t, "1.2.3.4", ctiResp.Ip)
   190  	assert.True(t, CTIApiEnabled)
   191  	assert.Equal(t, 1, CTICache.Len(true))
   192  	require.NoError(t, err)
   193  
   194  	time.Sleep(2 * time.Second)
   195  
   196  	assert.Equal(t, 0, CTICache.Len(true))
   197  
   198  	item, err = CrowdsecCTI("1.2.3.4")
   199  	ctiResp = item.(*cticlient.SmokeItem)
   200  
   201  	assert.Equal(t, "1.2.3.4", ctiResp.Ip)
   202  	assert.True(t, CTIApiEnabled)
   203  	assert.Equal(t, 1, CTICache.Len(true))
   204  	require.NoError(t, err)
   205  }