123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- package apiclient
- import (
- "context"
- "fmt"
- "net/http"
- "net/url"
- "reflect"
- "testing"
- "github.com/crowdsecurity/go-cs-lib/pkg/ptr"
- "github.com/crowdsecurity/go-cs-lib/pkg/version"
- "github.com/crowdsecurity/crowdsec/pkg/models"
- "github.com/crowdsecurity/crowdsec/pkg/modelscapi"
- log "github.com/sirupsen/logrus"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- func TestDecisionsList(t *testing.T) {
- log.SetLevel(log.DebugLevel)
- mux, urlx, teardown := setup()
- defer teardown()
- mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "GET")
- if r.URL.RawQuery == "ip=1.2.3.4" {
- assert.Equal(t, r.URL.RawQuery, "ip=1.2.3.4")
- assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`[{"duration":"3h59m55.756182786s","id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","type":"ban","value":"1.2.3.4"}]`))
- } else {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`null`))
- //no results
- }
- })
- apiURL, err := url.Parse(urlx + "/")
- if err != nil {
- t.Fatalf("parsing api url: %s", apiURL)
- }
- //ok answer
- auth := &APIKeyTransport{
- APIKey: "ixu",
- }
- newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- tduration := "3h59m55.756182786s"
- torigin := "cscli"
- tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
- tscope := "Ip"
- ttype := "ban"
- tvalue := "1.2.3.4"
- expected := &models.GetDecisionsResponse{
- &models.Decision{
- Duration: &tduration,
- ID: 4,
- Origin: &torigin,
- Scenario: &tscenario,
- Scope: &tscope,
- Type: &ttype,
- Value: &tvalue,
- },
- }
- //OK decisions
- decisionsFilter := DecisionsListOpts{IPEquals: new(string)}
- *decisionsFilter.IPEquals = "1.2.3.4"
- decisions, resp, err := newcli.Decisions.List(context.Background(), decisionsFilter)
- if resp.Response.StatusCode != http.StatusOK {
- t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
- }
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- if !reflect.DeepEqual(*decisions, *expected) {
- t.Fatalf("returned %+v, want %+v", resp, expected)
- }
- //Empty return
- decisionsFilter = DecisionsListOpts{IPEquals: new(string)}
- *decisionsFilter.IPEquals = "1.2.3.5"
- decisions, resp, err = newcli.Decisions.List(context.Background(), decisionsFilter)
- require.NoError(t, err)
- if resp.Response.StatusCode != http.StatusOK {
- t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
- }
- assert.Equal(t, len(*decisions), 0)
- }
- func TestDecisionsStream(t *testing.T) {
- log.SetLevel(log.DebugLevel)
- mux, urlx, teardown := setup()
- defer teardown()
- mux.HandleFunc("/decisions/stream", func(w http.ResponseWriter, r *http.Request) {
- assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
- testMethod(t, r, http.MethodGet)
- if r.Method == http.MethodGet {
- if r.URL.RawQuery == "startup=true" {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`{"deleted":null,"new":[{"duration":"3h59m55.756182786s","id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","type":"ban","value":"1.2.3.4"}]}`))
- } else {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`{"deleted":null,"new":null}`))
- }
- }
- })
- mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
- assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
- testMethod(t, r, http.MethodDelete)
- if r.Method == http.MethodDelete {
- w.WriteHeader(http.StatusOK)
- }
- })
- apiURL, err := url.Parse(urlx + "/")
- if err != nil {
- t.Fatalf("parsing api url: %s", apiURL)
- }
- //ok answer
- auth := &APIKeyTransport{
- APIKey: "ixu",
- }
- newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- tduration := "3h59m55.756182786s"
- torigin := "cscli"
- tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
- tscope := "Ip"
- ttype := "ban"
- tvalue := "1.2.3.4"
- expected := &models.DecisionsStreamResponse{
- New: models.GetDecisionsResponse{
- &models.Decision{
- Duration: &tduration,
- ID: 4,
- Origin: &torigin,
- Scenario: &tscenario,
- Scope: &tscope,
- Type: &ttype,
- Value: &tvalue,
- },
- },
- }
- decisions, resp, err := newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: true})
- require.NoError(t, err)
- if resp.Response.StatusCode != http.StatusOK {
- t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
- }
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- if !reflect.DeepEqual(*decisions, *expected) {
- t.Fatalf("returned %+v, want %+v", resp, expected)
- }
- //and second call, we get empty lists
- decisions, resp, err = newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: false})
- require.NoError(t, err)
- if resp.Response.StatusCode != http.StatusOK {
- t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
- }
- assert.Equal(t, 0, len(decisions.New))
- assert.Equal(t, 0, len(decisions.Deleted))
- //delete stream
- resp, err = newcli.Decisions.StopStream(context.Background())
- require.NoError(t, err)
- if resp.Response.StatusCode != http.StatusOK {
- t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
- }
- }
- func TestDecisionsStreamV3Compatibility(t *testing.T) {
- log.SetLevel(log.DebugLevel)
- mux, urlx, teardown := setupWithPrefix("v3")
- defer teardown()
- mux.HandleFunc("/decisions/stream", func(w http.ResponseWriter, r *http.Request) {
- assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
- testMethod(t, r, http.MethodGet)
- if r.Method == http.MethodGet {
- if r.URL.RawQuery == "startup=true" {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`{"deleted":[{"scope":"ip","decisions":["1.2.3.5"]}],"new":[{"scope":"ip", "scenario": "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'", "decisions":[{"duration":"3h59m55.756182786s","value":"1.2.3.4"}]}]}`))
- } else {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`{"deleted":null,"new":null}`))
- }
- }
- })
- apiURL, err := url.Parse(urlx + "/")
- if err != nil {
- t.Fatalf("parsing api url: %s", apiURL)
- }
- //ok answer
- auth := &APIKeyTransport{
- APIKey: "ixu",
- }
- newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- tduration := "3h59m55.756182786s"
- torigin := "CAPI"
- tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
- tscope := "ip"
- ttype := "ban"
- tvalue := "1.2.3.4"
- tvalue1 := "1.2.3.5"
- tscenarioDeleted := "deleted"
- tdurationDeleted := "1h"
- expected := &models.DecisionsStreamResponse{
- New: models.GetDecisionsResponse{
- &models.Decision{
- Duration: &tduration,
- Origin: &torigin,
- Scenario: &tscenario,
- Scope: &tscope,
- Type: &ttype,
- Value: &tvalue,
- },
- },
- Deleted: models.GetDecisionsResponse{
- &models.Decision{
- Duration: &tdurationDeleted,
- Origin: &torigin,
- Scenario: &tscenarioDeleted,
- Scope: &tscope,
- Type: &ttype,
- Value: &tvalue1,
- },
- },
- }
- // GetStream is supposed to consume v3 payload and return v2 response
- decisions, resp, err := newcli.Decisions.GetStream(context.Background(), DecisionsStreamOpts{Startup: true})
- require.NoError(t, err)
- if resp.Response.StatusCode != http.StatusOK {
- t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
- }
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- if !reflect.DeepEqual(*decisions, *expected) {
- t.Fatalf("returned %+v, want %+v", resp, expected)
- }
- }
- func TestDecisionsStreamV3(t *testing.T) {
- log.SetLevel(log.DebugLevel)
- mux, urlx, teardown := setupWithPrefix("v3")
- defer teardown()
- mux.HandleFunc("/decisions/stream", func(w http.ResponseWriter, r *http.Request) {
- assert.Equal(t, r.Header.Get("X-Api-Key"), "ixu")
- testMethod(t, r, http.MethodGet)
- if r.Method == http.MethodGet {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`{"deleted":[{"scope":"ip","decisions":["1.2.3.5"]}],
- "new":[{"scope":"ip", "scenario": "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'", "decisions":[{"duration":"3h59m55.756182786s","value":"1.2.3.4"}]}],
- "links": {"blocklists":[{"name":"blocklist1","url":"/v3/blocklist","scope":"ip","remediation":"ban","duration":"24h"}]}}`))
- }
- })
- apiURL, err := url.Parse(urlx + "/")
- if err != nil {
- t.Fatalf("parsing api url: %s", apiURL)
- }
- //ok answer
- auth := &APIKeyTransport{
- APIKey: "ixu",
- }
- newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- tduration := "3h59m55.756182786s"
- tscenario := "manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'"
- tscope := "ip"
- tvalue := "1.2.3.4"
- tvalue1 := "1.2.3.5"
- tdurationBlocklist := "24h"
- tnameBlocklist := "blocklist1"
- tremediationBlocklist := "ban"
- tscopeBlocklist := "ip"
- turlBlocklist := "/v3/blocklist"
- expected := &modelscapi.GetDecisionsStreamResponse{
- New: modelscapi.GetDecisionsStreamResponseNew{
- &modelscapi.GetDecisionsStreamResponseNewItem{
- Decisions: []*modelscapi.GetDecisionsStreamResponseNewItemDecisionsItems0{
- {
- Duration: &tduration,
- Value: &tvalue,
- },
- },
- Scenario: &tscenario,
- Scope: &tscope,
- },
- },
- Deleted: modelscapi.GetDecisionsStreamResponseDeleted{
- &modelscapi.GetDecisionsStreamResponseDeletedItem{
- Scope: &tscope,
- Decisions: []string{
- tvalue1,
- },
- },
- },
- Links: &modelscapi.GetDecisionsStreamResponseLinks{
- Blocklists: []*modelscapi.BlocklistLink{
- {
- Duration: &tdurationBlocklist,
- Name: &tnameBlocklist,
- Remediation: &tremediationBlocklist,
- Scope: &tscopeBlocklist,
- URL: &turlBlocklist,
- },
- },
- },
- }
- // GetStream is supposed to consume v3 payload and return v2 response
- decisions, resp, err := newcli.Decisions.GetStreamV3(context.Background(), DecisionsStreamOpts{Startup: true})
- require.NoError(t, err)
- if resp.Response.StatusCode != http.StatusOK {
- t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
- }
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- if !reflect.DeepEqual(*decisions, *expected) {
- t.Fatalf("returned %+v, want %+v", resp, expected)
- }
- }
- func TestDecisionsFromBlocklist(t *testing.T) {
- log.SetLevel(log.DebugLevel)
- mux, urlx, teardown := setupWithPrefix("v3")
- defer teardown()
- mux.HandleFunc("/blocklist", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, http.MethodGet)
- if r.Header.Get("If-Modified-Since") == "Sun, 01 Jan 2023 01:01:01 GMT" {
- w.WriteHeader(http.StatusNotModified)
- return
- }
- if r.Method == http.MethodGet {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte("1.2.3.4\r\n1.2.3.5"))
- }
- })
- apiURL, err := url.Parse(urlx + "/")
- if err != nil {
- t.Fatalf("parsing api url: %s", apiURL)
- }
- //ok answer
- auth := &APIKeyTransport{
- APIKey: "ixu",
- }
- newcli, err := NewDefaultClient(apiURL, "v3", "toto", auth.Client())
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- tvalue1 := "1.2.3.4"
- tvalue2 := "1.2.3.5"
- tdurationBlocklist := "24h"
- tnameBlocklist := "blocklist1"
- tremediationBlocklist := "ban"
- tscopeBlocklist := "ip"
- turlBlocklist := urlx + "/v3/blocklist"
- torigin := "lists"
- expected := []*models.Decision{
- {
- Duration: &tdurationBlocklist,
- Value: &tvalue1,
- Scenario: &tnameBlocklist,
- Scope: &tscopeBlocklist,
- Type: &tremediationBlocklist,
- Origin: &torigin,
- },
- {
- Duration: &tdurationBlocklist,
- Value: &tvalue2,
- Scenario: &tnameBlocklist,
- Scope: &tscopeBlocklist,
- Type: &tremediationBlocklist,
- Origin: &torigin,
- },
- }
- decisions, isModified, err := newcli.Decisions.GetDecisionsFromBlocklist(context.Background(), &modelscapi.BlocklistLink{
- URL: &turlBlocklist,
- Scope: &tscopeBlocklist,
- Remediation: &tremediationBlocklist,
- Name: &tnameBlocklist,
- Duration: &tdurationBlocklist,
- }, nil)
- require.NoError(t, err)
- assert.True(t, isModified)
- log.Infof("decision1: %+v", decisions[0])
- log.Infof("expected1: %+v", expected[0])
- log.Infof("decisions: %s, %s, %s, %s, %s, %s", *decisions[0].Value, *decisions[0].Duration, *decisions[0].Scenario, *decisions[0].Scope, *decisions[0].Type, *decisions[0].Origin)
- log.Infof("expected : %s, %s, %s, %s, %s", *expected[0].Value, *expected[0].Duration, *expected[0].Scenario, *expected[0].Scope, *expected[0].Type)
- log.Infof("decisions: %s, %s, %s, %s, %s", *decisions[1].Value, *decisions[1].Duration, *decisions[1].Scenario, *decisions[1].Scope, *decisions[1].Type)
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- if !reflect.DeepEqual(decisions, expected) {
- t.Fatalf("returned %+v, want %+v", decisions, expected)
- }
- // test cache control
- _, isModified, err = newcli.Decisions.GetDecisionsFromBlocklist(context.Background(), &modelscapi.BlocklistLink{
- URL: &turlBlocklist,
- Scope: &tscopeBlocklist,
- Remediation: &tremediationBlocklist,
- Name: &tnameBlocklist,
- Duration: &tdurationBlocklist,
- }, ptr.Of("Sun, 01 Jan 2023 01:01:01 GMT"))
- require.NoError(t, err)
- assert.False(t, isModified)
- _, isModified, err = newcli.Decisions.GetDecisionsFromBlocklist(context.Background(), &modelscapi.BlocklistLink{
- URL: &turlBlocklist,
- Scope: &tscopeBlocklist,
- Remediation: &tremediationBlocklist,
- Name: &tnameBlocklist,
- Duration: &tdurationBlocklist,
- }, ptr.Of("Mon, 02 Jan 2023 01:01:01 GMT"))
- require.NoError(t, err)
- assert.True(t, isModified)
- }
- func TestDeleteDecisions(t *testing.T) {
- mux, urlx, teardown := setup()
- mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
- })
- mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
- testMethod(t, r, "DELETE")
- assert.Equal(t, r.URL.RawQuery, "ip=1.2.3.4")
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(`{"nbDeleted":"1"}`))
- //w.Write([]byte(`{"message":"0 deleted alerts"}`))
- })
- log.Printf("URL is %s", urlx)
- apiURL, err := url.Parse(urlx + "/")
- if err != nil {
- t.Fatalf("parsing api url: %s", apiURL)
- }
- client, err := NewClient(&Config{
- MachineID: "test_login",
- Password: "test_password",
- UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
- URL: apiURL,
- VersionPrefix: "v1",
- })
- if err != nil {
- t.Fatalf("new api client: %s", err)
- }
- filters := DecisionsDeleteOpts{IPEquals: new(string)}
- *filters.IPEquals = "1.2.3.4"
- deleted, _, err := client.Decisions.Delete(context.Background(), filters)
- if err != nil {
- t.Fatalf("unexpected err : %s", err)
- }
- assert.Equal(t, "1", deleted.NbDeleted)
- defer teardown()
- }
- func TestDecisionsStreamOpts_addQueryParamsToURL(t *testing.T) {
- baseURLString := "http://localhost:8080/v1/decisions/stream"
- type fields struct {
- Startup bool
- Scopes string
- ScenariosContaining string
- ScenariosNotContaining string
- }
- tests := []struct {
- name string
- fields fields
- want string
- wantErr bool
- }{
- {
- name: "no filter",
- want: baseURLString + "?",
- },
- {
- name: "startup=true",
- fields: fields{
- Startup: true,
- },
- want: baseURLString + "?startup=true",
- },
- {
- name: "set all params",
- fields: fields{
- Startup: true,
- Scopes: "ip,range",
- ScenariosContaining: "ssh",
- ScenariosNotContaining: "bf",
- },
- want: baseURLString + "?scenarios_containing=ssh&scenarios_not_containing=bf&scopes=ip%2Crange&startup=true",
- },
- }
- for _, tt := range tests {
- tt := tt
- t.Run(tt.name, func(t *testing.T) {
- o := &DecisionsStreamOpts{
- Startup: tt.fields.Startup,
- Scopes: tt.fields.Scopes,
- ScenariosContaining: tt.fields.ScenariosContaining,
- ScenariosNotContaining: tt.fields.ScenariosNotContaining,
- }
- got, err := o.addQueryParamsToURL(baseURLString)
- if (err != nil) != tt.wantErr {
- t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() error = %v, wantErr %v", err, tt.wantErr)
- return
- }
- gotURL, err := url.Parse(got)
- if err != nil {
- t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() got error while parsing URL: %s", err)
- }
- expectedURL, err := url.Parse(tt.want)
- if err != nil {
- t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() got error while parsing URL: %s", err)
- }
- if *gotURL != *expectedURL {
- t.Errorf("DecisionsStreamOpts.addQueryParamsToURL() = %v, want %v", *gotURL, *expectedURL)
- }
- })
- }
- }
- // func TestDeleteOneDecision(t *testing.T) {
- // mux, urlx, teardown := setup()
- // mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
- // w.WriteHeader(http.StatusOK)
- // w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
- // })
- // mux.HandleFunc("/decisions/1", func(w http.ResponseWriter, r *http.Request) {
- // testMethod(t, r, "DELETE")
- // w.WriteHeader(http.StatusOK)
- // w.Write([]byte(`{"nbDeleted":"1"}`))
- // })
- // log.Printf("URL is %s", urlx)
- // apiURL, err := url.Parse(urlx + "/")
- // if err != nil {
- // t.Fatalf("parsing api url: %s", apiURL)
- // }
- // client, err := NewClient(&Config{
- // MachineID: "test_login",
- // Password: "test_password",
- // UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
- // URL: apiURL,
- // VersionPrefix: "v1",
- // })
- // if err != nil {
- // t.Fatalf("new api client: %s", err)
- // }
- // filters := DecisionsDeleteOpts{IPEquals: new(string)}
- // *filters.IPEquals = "1.2.3.4"
- // deleted, _, err := client.Decisions.Delete(context.Background(), filters)
- // if err != nil {
- // t.Fatalf("unexpected err : %s", err)
- // }
- // assert.Equal(t, "1", deleted.NbDeleted)
- // defer teardown()
- // }
|