|
@@ -0,0 +1,303 @@
|
|
|
|
+package cticlient
|
|
|
|
+
|
|
|
|
+// import (
|
|
|
|
+// "encoding/json"
|
|
|
|
+// "net/http"
|
|
|
|
+// "net/http/httptest"
|
|
|
|
+// "net/url"
|
|
|
|
+// "strings"
|
|
|
|
+// "testing"
|
|
|
|
+// "time"
|
|
|
|
+
|
|
|
|
+// "github.com/stretchr/testify/assert"
|
|
|
|
+// )
|
|
|
|
+
|
|
|
|
+// var sampledata = map[string]CTIResponse{
|
|
|
|
+// //1.2.3.4 is a known false positive
|
|
|
|
+// "1.2.3.4": {
|
|
|
|
+// Ip: "1.2.3.4",
|
|
|
|
+// Classifications: CTIClassifications{
|
|
|
|
+// FalsePositives: []CTIClassification{
|
|
|
|
+// {
|
|
|
|
+// Name: "example_false_positive",
|
|
|
|
+// Label: "Example False Positive",
|
|
|
|
+// },
|
|
|
|
+// },
|
|
|
|
+// },
|
|
|
|
+// },
|
|
|
|
+// //1.2.3.5 is a known bad-guy, and part of FIRE
|
|
|
|
+// "1.2.3.5": {
|
|
|
|
+// Ip: "1.2.3.5",
|
|
|
|
+// Classifications: CTIClassifications{
|
|
|
|
+// Classifications: []CTIClassification{
|
|
|
|
+// {
|
|
|
|
+// Name: "community-blocklist",
|
|
|
|
+// Label: "CrowdSec Community Blocklist",
|
|
|
|
+// Description: "IP belong to the CrowdSec Community Blocklist",
|
|
|
|
+// },
|
|
|
|
+// },
|
|
|
|
+// },
|
|
|
|
+// },
|
|
|
|
+// //1.2.3.6 is a bad guy (high bg noise), but not in FIRE
|
|
|
|
+// "1.2.3.6": {
|
|
|
|
+// Ip: "1.2.3.6",
|
|
|
|
+// BackgroundNoiseScore: new(int),
|
|
|
|
+// Behaviors: []*CTIBehavior{
|
|
|
|
+// {Name: "ssh:bruteforce", Label: "SSH Bruteforce", Description: "SSH Bruteforce"},
|
|
|
|
+// },
|
|
|
|
+// AttackDetails: []*CTIAttackDetails{
|
|
|
|
+// {Name: "crowdsecurity/ssh-bf", Label: "Example Attack"},
|
|
|
|
+// {Name: "crowdsecurity/ssh-slow-bf", Label: "Example Attack"},
|
|
|
|
+// },
|
|
|
|
+// },
|
|
|
|
+// //1.2.3.7 is a ok guy, but part of a bad range
|
|
|
|
+// "1.2.3.7": CTIResponse{},
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// func EmptyCTIResponse(ip string) CTIResponse {
|
|
|
|
+// return CTIResponse{
|
|
|
|
+// IpRangeScore: 0,
|
|
|
|
+// Ip: ip,
|
|
|
|
+// Location: CTILocationInfo{},
|
|
|
|
+// }
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// /*
|
|
|
|
+// TBD : Simulate correctly quotas exhaustion
|
|
|
|
+// */
|
|
|
|
+// func setup() (Router *http.ServeMux, serverURL string, teardown func()) {
|
|
|
|
+
|
|
|
|
+// //set static values
|
|
|
|
+// *sampledata["1.2.3.6"].BackgroundNoiseScore = 10
|
|
|
|
+
|
|
|
|
+// // mux is the HTTP request multiplexer used with the test server.
|
|
|
|
+// Router = http.NewServeMux()
|
|
|
|
+// baseURLPath := "/v2"
|
|
|
|
+
|
|
|
|
+// apiHandler := http.NewServeMux()
|
|
|
|
+// apiHandler.Handle(baseURLPath+"/", http.StripPrefix(baseURLPath, Router))
|
|
|
|
+
|
|
|
|
+// // server is a test HTTP server used to provide mock API responses.
|
|
|
|
+// server := httptest.NewServer(apiHandler)
|
|
|
|
+
|
|
|
|
+// // let's mock the API endpoints
|
|
|
|
+// Router.HandleFunc("/smoke/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
+// //testMethod(t, r, "GET")
|
|
|
|
+// if r.Header.Get("X-Api-Key") != "EXAMPLE_API_KEY" {
|
|
|
|
+// w.WriteHeader(http.StatusForbidden)
|
|
|
|
+// w.Write([]byte(`{"message":"Forbidden"}`))
|
|
|
|
+// return
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// frags := strings.Split(r.RequestURI, "/")
|
|
|
|
+// //[empty] [smoke] [v2] [actual_ip]
|
|
|
|
+// if len(frags) != 4 {
|
|
|
|
+// w.WriteHeader(http.StatusBadRequest)
|
|
|
|
+// w.Write([]byte(`{"message":"Bad Request"}`))
|
|
|
|
+// return
|
|
|
|
+// }
|
|
|
|
+// ip := frags[3]
|
|
|
|
+
|
|
|
|
+// if ip == "" {
|
|
|
|
+// //to be fixed to stick w/ real behavior
|
|
|
|
+// panic("empty ip")
|
|
|
|
+
|
|
|
|
+// }
|
|
|
|
+// // vars := mux.Vars(r)
|
|
|
|
+// if v, ok := sampledata[ip]; ok {
|
|
|
|
+// data, err := json.Marshal(v)
|
|
|
|
+// if err != nil {
|
|
|
|
+// panic("unable to marshal")
|
|
|
|
+// }
|
|
|
|
+// w.WriteHeader(http.StatusOK)
|
|
|
|
+// w.Write(data)
|
|
|
|
+// return
|
|
|
|
+// }
|
|
|
|
+// w.WriteHeader(http.StatusOK)
|
|
|
|
+// data, err := json.Marshal(EmptyCTIResponse(ip))
|
|
|
|
+// if err != nil {
|
|
|
|
+// panic("unable to marshal")
|
|
|
|
+// }
|
|
|
|
+// w.Write(data)
|
|
|
|
+// return
|
|
|
|
+// })
|
|
|
|
+// return Router, server.URL, server.Close
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// func TestCTIAuthKO(t *testing.T) {
|
|
|
|
+
|
|
|
|
+// _, urlx, teardown := setup()
|
|
|
|
+// apiURL, err := url.Parse(urlx + "/")
|
|
|
|
+// if err != nil {
|
|
|
|
+// t.Fatalf("parsing api url: %s", apiURL)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// defer teardown()
|
|
|
|
+// defer ShutdownCTI()
|
|
|
|
+// CTIUrl = urlx
|
|
|
|
+// key := "BAD_KEY"
|
|
|
|
+// if err := InitCTI(&key, nil, nil); err != nil {
|
|
|
|
+// t.Fatalf("InitCTI failed: %s", err)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// ret := IpCTI("1.2.3.4")
|
|
|
|
+// assert.Equal(t, false, ret.Ok(), "should be ko")
|
|
|
|
+// assert.Equal(t, CTIResponse{}, ret, "auth failed, empty answer")
|
|
|
|
+// assert.Equal(t, CTIApiEnabled, false, "auth failed, api disabled")
|
|
|
|
+// //auth is disabled, we should always receive empty object
|
|
|
|
+// ret = IpCTI("1.2.3.4")
|
|
|
|
+// assert.Equal(t, false, ret.Ok(), "should be ko")
|
|
|
|
+// assert.Equal(t, CTIResponse{}, ret, "auth failed, empty answer")
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// func TestCTINoKey(t *testing.T) {
|
|
|
|
+
|
|
|
|
+// _, urlx, teardown := setup()
|
|
|
|
+// apiURL, err := url.Parse(urlx + "/")
|
|
|
|
+// if err != nil {
|
|
|
|
+// t.Fatalf("parsing api url: %s", apiURL)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// defer teardown()
|
|
|
|
+// defer ShutdownCTI()
|
|
|
|
+// CTIUrl = urlx
|
|
|
|
+// //key := ""
|
|
|
|
+// err = InitCTI(nil, nil, nil)
|
|
|
|
+// assert.NotEqual(t, err, nil, "InitCTI should fail")
|
|
|
|
+// ret := IpCTI("1.2.3.4")
|
|
|
|
+// assert.Equal(t, false, ret.Ok(), "should be ko")
|
|
|
|
+// assert.Equal(t, CTIResponse{}, ret, "auth failed, empty answer")
|
|
|
|
+// assert.Equal(t, CTIApiEnabled, false, "auth failed, api disabled")
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// func TestCTIAuthOK(t *testing.T) {
|
|
|
|
+
|
|
|
|
+// _, urlx, teardown := setup()
|
|
|
|
+// apiURL, err := url.Parse(urlx + "/")
|
|
|
|
+// if err != nil {
|
|
|
|
+// t.Fatalf("parsing api url: %s", apiURL)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// defer teardown()
|
|
|
|
+// defer ShutdownCTI()
|
|
|
|
+
|
|
|
|
+// CTIUrl = urlx
|
|
|
|
+// key := "EXAMPLE_API_KEY"
|
|
|
|
+// if err := InitCTI(&key, nil, nil); err != nil {
|
|
|
|
+// t.Fatalf("InitCTI failed: %s", err)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// ret := IpCTI("1.2.3.4")
|
|
|
|
+// assert.Equal(t, true, ret.Ok(), "should be ok")
|
|
|
|
+// assert.Equal(t, "1.2.3.4", ret.Ip, "auth failed, empty answer")
|
|
|
|
+// assert.Equal(t, CTIApiEnabled, true, "auth failed, api disabled")
|
|
|
|
+// }
|
|
|
|
+// func TestCTIKnownFP(t *testing.T) {
|
|
|
|
+// _, urlx, teardown := setup()
|
|
|
|
+// apiURL, err := url.Parse(urlx + "/")
|
|
|
|
+// if err != nil {
|
|
|
|
+// t.Fatalf("parsing api url: %s", apiURL)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// defer teardown()
|
|
|
|
+// defer ShutdownCTI()
|
|
|
|
+
|
|
|
|
+// CTIUrl = urlx
|
|
|
|
+// key := "EXAMPLE_API_KEY"
|
|
|
|
+// if err := InitCTI(&key, nil, nil); err != nil {
|
|
|
|
+// t.Fatalf("InitCTI failed: %s", err)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// ret := IpCTI("1.2.3.4")
|
|
|
|
+// assert.Equal(t, true, ret.Ok(), "should be ok")
|
|
|
|
+// assert.Equal(t, "1.2.3.4", ret.Ip, "auth failed, empty answer")
|
|
|
|
+// assert.Equal(t, ret.IsFalsePositive(), true, "1.2.3.4 is a known false positive")
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// func TestCTIBelongsToFire(t *testing.T) {
|
|
|
|
+// _, urlx, teardown := setup()
|
|
|
|
+// apiURL, err := url.Parse(urlx + "/")
|
|
|
|
+// if err != nil {
|
|
|
|
+// t.Fatalf("parsing api url: %s", apiURL)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// defer teardown()
|
|
|
|
+// defer ShutdownCTI()
|
|
|
|
+
|
|
|
|
+// CTIUrl = urlx
|
|
|
|
+// key := "EXAMPLE_API_KEY"
|
|
|
|
+// if err := InitCTI(&key, nil, nil); err != nil {
|
|
|
|
+// t.Fatalf("InitCTI failed: %s", err)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// ret := IpCTI("1.2.3.5")
|
|
|
|
+// assert.Equal(t, true, ret.Ok(), "should be ok")
|
|
|
|
+// assert.Equal(t, "1.2.3.5", ret.Ip, "auth failed, empty answer")
|
|
|
|
+// assert.Equal(t, ret.IsPartOfCommunityBlocklist(), true, "1.2.3.5 is a known false positive")
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// func TestCTIBehaviors(t *testing.T) {
|
|
|
|
+// _, urlx, teardown := setup()
|
|
|
|
+// apiURL, err := url.Parse(urlx + "/")
|
|
|
|
+// if err != nil {
|
|
|
|
+// t.Fatalf("parsing api url: %s", apiURL)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// defer teardown()
|
|
|
|
+// defer ShutdownCTI()
|
|
|
|
+
|
|
|
|
+// CTIUrl = urlx
|
|
|
|
+// key := "EXAMPLE_API_KEY"
|
|
|
|
+// if err := InitCTI(&key, nil, nil); err != nil {
|
|
|
|
+// t.Fatalf("InitCTI failed: %s", err)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// ret := IpCTI("1.2.3.6")
|
|
|
|
+// assert.Equal(t, true, ret.Ok(), "should be ok")
|
|
|
|
+// assert.Equal(t, ret.Ip, "1.2.3.6", "auth failed, empty answer")
|
|
|
|
+// //ssh:bruteforce
|
|
|
|
+// assert.Equal(t, []string{"ssh:bruteforce"}, ret.GetBehaviors(), "error matching behaviors")
|
|
|
|
+// assert.Equal(t, []string{"crowdsecurity/ssh-bf", "crowdsecurity/ssh-slow-bf"}, ret.GetAttackDetails(), "error matching behaviors")
|
|
|
|
+// assert.Equal(t, 10, ret.GetBackgroundNoiseScore(), "error matching bg noise")
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// func TestCacheFetch(t *testing.T) {
|
|
|
|
+// _, urlx, teardown := setup()
|
|
|
|
+// apiURL, err := url.Parse(urlx + "/")
|
|
|
|
+// if err != nil {
|
|
|
|
+// t.Fatalf("parsing api url: %s", apiURL)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// defer teardown()
|
|
|
|
+// defer ShutdownCTI()
|
|
|
|
+
|
|
|
|
+// CTIUrl = urlx
|
|
|
|
+// key := "EXAMPLE_API_KEY"
|
|
|
|
+// ttl := 1 * time.Second
|
|
|
|
+// if err := InitCTI(&key, &ttl, nil); err != nil {
|
|
|
|
+// t.Fatalf("InitCTI failed: %s", err)
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// ret := IpCTI("1.2.3.6")
|
|
|
|
+// assert.Equal(t, true, ret.Ok(), "should be ok")
|
|
|
|
+// assert.Equal(t, "1.2.3.6", ret.Ip, "initial fetch : bad item")
|
|
|
|
+// assert.Equal(t, 1, CTICache.Len(true), "initial fetch : bad cache size")
|
|
|
|
+// assert.Equal(t, "1.2.3.6", CTICache.Keys(true)[0].(string), "initial fetch : bad cache keys")
|
|
|
|
+// //get it a second time before it expires
|
|
|
|
+// ret = IpCTI("1.2.3.6")
|
|
|
|
+// assert.Equal(t, true, ret.Ok(), "should be ok")
|
|
|
|
+// assert.Equal(t, "1.2.3.6", ret.Ip, "initial fetch : bad item")
|
|
|
|
+// assert.Equal(t, 1, CTICache.Len(true), "initial fetch : bad cache size")
|
|
|
|
+// assert.Equal(t, "1.2.3.6", CTICache.Keys(true)[0].(string), "initial fetch : bad cache keys")
|
|
|
|
+// //let data expire
|
|
|
|
+// time.Sleep(1 * time.Second)
|
|
|
|
+// assert.Equal(t, 0, CTICache.Len(true), "after ttl : bad cache size")
|
|
|
|
+// //fetch again
|
|
|
|
+// ret = IpCTI("1.2.3.6")
|
|
|
|
+// assert.Equal(t, true, ret.Ok(), "should be ok")
|
|
|
|
+// assert.Equal(t, "1.2.3.6", ret.Ip, "second fetch : bad item")
|
|
|
|
+// assert.Equal(t, 1, CTICache.Len(true), "second fetch : bad cache size")
|
|
|
|
+// assert.Equal(t, "1.2.3.6", CTICache.Keys(true)[0].(string), "initial fetch : bad cache keys")
|
|
|
|
+// }
|
|
|
|
+
|
|
|
|
+// //GetMaliciousnessScore
|