Apiclient tests (#484)
Co-authored-by: AlteredCoder Co-authored-by: erenJag
This commit is contained in:
parent
000fec27df
commit
71ac0d2fce
28 changed files with 1385 additions and 74 deletions
|
@ -1,30 +0,0 @@
|
||||||
# Contributing
|
|
||||||
|
|
||||||
You have an idea, a suggestion or you spotted a mistake ?
|
|
||||||
Help us improve the software and the user experience, to make the internet a safer place together !
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing to the documentation
|
|
||||||
|
|
||||||
If you spotted some mistakes in the documentation or have improvement suggestions, you can :
|
|
||||||
|
|
||||||
- open a {{v0X.doc.new_issue}} if you are comfortable with github
|
|
||||||
- let us know on {{v0X.doc.discourse}} if you want to discuss about it
|
|
||||||
|
|
||||||
Let us as well know if you have some improvement suggestions !
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing to the code
|
|
||||||
|
|
||||||
- If you want to report a bug, you can use [the github bugtracker]({{v0X.crowdsec.bugreport}})
|
|
||||||
- If you want to suggest an improvement you can use either [the github bugtracker]({{v0X.crowdsec.bugreport}}) or the {{v0X.doc.discourse}} if you want to discuss
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing to the parsers/scenarios
|
|
||||||
|
|
||||||
If you want to contribute your parser or scenario to the community and have them appear on the {{v0X.hub.htmlname}}, you should [open a merge request](https://github.com/crowdsecurity/hub/pulls) on the hub.
|
|
||||||
|
|
||||||
We are currently working on a proper [CI](https://en.wikipedia.org/wiki/Continuous_integration) for the {{v0X.hub.htmlname}}, so for now all contribution are subject to peer-review, please bear with us !
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ nav:
|
||||||
- Expressions: write_configurations/expressions.md
|
- Expressions: write_configurations/expressions.md
|
||||||
- bouncers: bouncers/index.md
|
- bouncers: bouncers/index.md
|
||||||
- Contributing:
|
- Contributing:
|
||||||
- General: contributing.md
|
|
||||||
- Writing Output Plugins: references/plugins_api.md
|
- Writing Output Plugins: references/plugins_api.md
|
||||||
- Cscli commands:
|
- Cscli commands:
|
||||||
- Cscli: cscli/cscli.md
|
- Cscli: cscli/cscli.md
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Crowdsec
|
|
||||||
|
|
||||||
{{macros_info() }}
|
|
|
@ -75,10 +75,5 @@ nav:
|
||||||
- Admin Guide:
|
- Admin Guide:
|
||||||
- Services Configuration: admin_guide/services_configuration.md
|
- Services Configuration: admin_guide/services_configuration.md
|
||||||
- Architecture: admin_guide/architecture.md
|
- Architecture: admin_guide/architecture.md
|
||||||
- Contributing:
|
|
||||||
- General: contributing.md
|
|
||||||
- Reporting bugs: contributing.md
|
|
||||||
- Asking questions: contributing.md
|
|
||||||
- Publishing parsers & scenarios: contributing.md
|
|
||||||
- Upgrade V0.X to V1.X: migration.md
|
- Upgrade V0.X to V1.X: migration.md
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ nav:
|
||||||
- Developers : https://crowdsecurity.github.io/api_doc/index.html?urls.primaryName=LAPI" target="_blank
|
- Developers : https://crowdsecurity.github.io/api_doc/index.html?urls.primaryName=LAPI" target="_blank
|
||||||
- Hub : https://hub.crowdsec.net/" target="_blank
|
- Hub : https://hub.crowdsec.net/" target="_blank
|
||||||
- Releases : https://github.com/crowdsecurity/crowdsec/releases" target="_blank
|
- Releases : https://github.com/crowdsecurity/crowdsec/releases" target="_blank
|
||||||
|
- Contributing: contributing.md
|
||||||
- FAQ: faq.md
|
- FAQ: faq.md
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ google_analytics:
|
||||||
- auto
|
- auto
|
||||||
|
|
||||||
extra:
|
extra:
|
||||||
swagger_url: "https://raw.githubusercontent.com/crowdsecurity/crowdsec/wip_lapi/pkg/models/localapi_swagger.yaml"
|
swagger_url: "https://raw.githubusercontent.com/crowdsecurity/crowdsec/master/pkg/models/localapi_swagger.yaml"
|
||||||
v0X:
|
v0X:
|
||||||
doc:
|
doc:
|
||||||
new_issue: "[new documentation issue](https://github.com/crowdsecurity/crowdsec/issues/new)"
|
new_issue: "[new documentation issue](https://github.com/crowdsecurity/crowdsec/issues/new)"
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
|
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/models"
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
qs "github.com/google/go-querystring/query"
|
qs "github.com/google/go-querystring/query"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// type ApiAlerts service
|
// type ApiAlerts service
|
||||||
|
@ -65,7 +66,7 @@ func (s *AlertsService) List(ctx context.Context, opts AlertsListOpts) (*models.
|
||||||
u := fmt.Sprintf("%s/alerts", s.client.URLPrefix)
|
u := fmt.Sprintf("%s/alerts", s.client.URLPrefix)
|
||||||
params, err := qs.Values(opts)
|
params, err := qs.Values(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, errors.Wrap(err, "building query")
|
||||||
}
|
}
|
||||||
if len(params) > 0 {
|
if len(params) > 0 {
|
||||||
URI = fmt.Sprintf("%s?%s", u, params.Encode())
|
URI = fmt.Sprintf("%s?%s", u, params.Encode())
|
||||||
|
@ -75,12 +76,12 @@ func (s *AlertsService) List(ctx context.Context, opts AlertsListOpts) (*models.
|
||||||
|
|
||||||
req, err := s.client.NewRequest("GET", URI, nil)
|
req, err := s.client.NewRequest("GET", URI, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, errors.Wrap(err, "building request")
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := s.client.Do(ctx, req, &alerts)
|
resp, err := s.client.Do(ctx, req, &alerts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, resp, err
|
return nil, resp, errors.Wrap(err, "performing request")
|
||||||
}
|
}
|
||||||
return &alerts, resp, nil
|
return &alerts, resp, nil
|
||||||
}
|
}
|
||||||
|
@ -117,7 +118,7 @@ func (s *AlertsService) GetByID(ctx context.Context, alertID int) (*models.Alert
|
||||||
|
|
||||||
resp, err := s.client.Do(ctx, req, &alert)
|
resp, err := s.client.Do(ctx, req, &alert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, resp, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return &alert, resp, nil
|
return &alert, resp, nil
|
||||||
}
|
}
|
||||||
|
|
496
pkg/apiclient/alerts_service_test.go
Normal file
496
pkg/apiclient/alerts_service_test.go
Normal file
|
@ -0,0 +1,496 @@
|
||||||
|
package apiclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAlertsListAsMachine(t *testing.T) {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
|
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"}`))
|
||||||
|
})
|
||||||
|
log.Printf("URL is %s", urlx)
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.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 {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
if r.URL.RawQuery == "ip=1.2.3.4" {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, `null`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `[
|
||||||
|
{"capacity":5,"created_at":"2020-11-28T10:20:47+01:00",
|
||||||
|
"decisions":[
|
||||||
|
{"duration":"59m49.264032632s",
|
||||||
|
"end_ip":16843180,
|
||||||
|
"id":1,
|
||||||
|
"origin":"crowdsec",
|
||||||
|
"scenario":"crowdsecurity/ssh-bf",
|
||||||
|
"scope":"Ip",
|
||||||
|
"simulated":false,
|
||||||
|
"start_ip":16843180,
|
||||||
|
"type":"ban",
|
||||||
|
"value":"1.1.1.172"}
|
||||||
|
],
|
||||||
|
"events":[
|
||||||
|
{"meta":[
|
||||||
|
{"key":"target_user","value":"netflix"},
|
||||||
|
{"key":"service","value":"ssh"}
|
||||||
|
],
|
||||||
|
"timestamp":"2020-11-28 10:20:46 +0000 UTC"},
|
||||||
|
{"meta":[
|
||||||
|
{"key":"target_user","value":"netflix"},
|
||||||
|
{"key":"service","value":"ssh"}
|
||||||
|
],
|
||||||
|
"timestamp":"2020-11-28 10:20:46 +0000 UTC"}
|
||||||
|
],
|
||||||
|
"events_count":6,
|
||||||
|
"id":1,
|
||||||
|
"labels":null,
|
||||||
|
"leakspeed":"10s",
|
||||||
|
"machine_id":"test",
|
||||||
|
"message":"Ip 1.1.1.172 performed 'crowdsecurity/ssh-bf' (6 events over 2.920062ms) at 2020-11-28 10:20:46.845619968 +0100 CET m=+5.903899761",
|
||||||
|
"scenario":"crowdsecurity/ssh-bf",
|
||||||
|
"scenario_hash":"4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f",
|
||||||
|
"scenario_version":"0.1",
|
||||||
|
"simulated":false,
|
||||||
|
"source":{
|
||||||
|
"as_name":"Cloudflare Inc",
|
||||||
|
"cn":"AU",
|
||||||
|
"ip":"1.1.1.172",
|
||||||
|
"latitude":-37.7,
|
||||||
|
"longitude":145.1833,
|
||||||
|
"range":"1.1.1.0/24",
|
||||||
|
"scope":"Ip",
|
||||||
|
"value":"1.1.1.172"
|
||||||
|
},
|
||||||
|
"start_at":"2020-11-28 10:20:46.842701127 +0100 +0100",
|
||||||
|
"stop_at":"2020-11-28 10:20:46.845621385 +0100 +0100"
|
||||||
|
}
|
||||||
|
]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
tcapacity := int32(5)
|
||||||
|
tduration := "59m49.264032632s"
|
||||||
|
torigin := "crowdsec"
|
||||||
|
tscenario := "crowdsecurity/ssh-bf"
|
||||||
|
tscope := "Ip"
|
||||||
|
ttype := "ban"
|
||||||
|
tvalue := "1.1.1.172"
|
||||||
|
ttimestamp := "2020-11-28 10:20:46 +0000 UTC"
|
||||||
|
teventscount := int32(6)
|
||||||
|
tleakspeed := "10s"
|
||||||
|
tmessage := "Ip 1.1.1.172 performed 'crowdsecurity/ssh-bf' (6 events over 2.920062ms) at 2020-11-28 10:20:46.845619968 +0100 CET m=+5.903899761"
|
||||||
|
tscenariohash := "4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f"
|
||||||
|
tscenarioversion := "0.1"
|
||||||
|
tstartat := "2020-11-28 10:20:46.842701127 +0100 +0100"
|
||||||
|
tstopat := "2020-11-28 10:20:46.845621385 +0100 +0100"
|
||||||
|
|
||||||
|
expected := models.GetAlertsResponse{
|
||||||
|
&models.Alert{
|
||||||
|
Capacity: &tcapacity,
|
||||||
|
CreatedAt: "2020-11-28T10:20:47+01:00",
|
||||||
|
Decisions: []*models.Decision{
|
||||||
|
&models.Decision{
|
||||||
|
Duration: &tduration,
|
||||||
|
EndIP: 16843180,
|
||||||
|
ID: 1,
|
||||||
|
Origin: &torigin,
|
||||||
|
Scenario: &tscenario,
|
||||||
|
|
||||||
|
Scope: &tscope,
|
||||||
|
Simulated: new(bool), //false,
|
||||||
|
StartIP: 16843180,
|
||||||
|
Type: &ttype,
|
||||||
|
Value: &tvalue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Events: []*models.Event{
|
||||||
|
&models.Event{
|
||||||
|
Meta: models.Meta{
|
||||||
|
&models.MetaItems0{
|
||||||
|
Key: "target_user",
|
||||||
|
Value: "netflix",
|
||||||
|
},
|
||||||
|
&models.MetaItems0{
|
||||||
|
Key: "service",
|
||||||
|
Value: "ssh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timestamp: &ttimestamp,
|
||||||
|
},
|
||||||
|
&models.Event{
|
||||||
|
Meta: models.Meta{
|
||||||
|
&models.MetaItems0{
|
||||||
|
Key: "target_user",
|
||||||
|
Value: "netflix",
|
||||||
|
},
|
||||||
|
&models.MetaItems0{
|
||||||
|
Key: "service",
|
||||||
|
Value: "ssh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timestamp: &ttimestamp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventsCount: &teventscount,
|
||||||
|
ID: 1,
|
||||||
|
Leakspeed: &tleakspeed,
|
||||||
|
MachineID: "test",
|
||||||
|
Message: &tmessage,
|
||||||
|
Remediation: false,
|
||||||
|
Scenario: &tscenario,
|
||||||
|
ScenarioHash: &tscenariohash,
|
||||||
|
ScenarioVersion: &tscenarioversion,
|
||||||
|
Simulated: new(bool), //(false),
|
||||||
|
Source: &models.Source{
|
||||||
|
AsName: "Cloudflare Inc",
|
||||||
|
AsNumber: "",
|
||||||
|
Cn: "AU",
|
||||||
|
IP: "1.1.1.172",
|
||||||
|
Latitude: -37.7,
|
||||||
|
Longitude: 145.1833,
|
||||||
|
Range: "1.1.1.0/24",
|
||||||
|
Scope: &tscope,
|
||||||
|
Value: &tvalue,
|
||||||
|
},
|
||||||
|
StartAt: &tstartat,
|
||||||
|
StopAt: &tstopat,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Debugf("data : -> %s", spew.Sdump(alerts))
|
||||||
|
//log.Debugf("resp : -> %s", spew.Sdump(resp))
|
||||||
|
//log.Debugf("expected : -> %s", spew.Sdump(expected))
|
||||||
|
//first one returns data
|
||||||
|
alerts, resp, err := client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("test Unable to list alerts : %+v", err)
|
||||||
|
}
|
||||||
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(*alerts, expected) {
|
||||||
|
t.Errorf("client.Alerts.List returned %+v, want %+v", resp, expected)
|
||||||
|
}
|
||||||
|
//this one doesn't
|
||||||
|
filter := AlertsListOpts{IPEquals: new(string)}
|
||||||
|
*filter.IPEquals = "1.2.3.4"
|
||||||
|
alerts, resp, err = client.Alerts.List(context.Background(), filter)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("test Unable to list alerts : %+v", 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(*alerts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertsGetAsMachine(t *testing.T) {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
|
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"}`))
|
||||||
|
})
|
||||||
|
log.Printf("URL is %s", urlx)
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.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 {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer teardown()
|
||||||
|
mux.HandleFunc("/alerts/2", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
fmt.Fprintf(w, `{"message":"object not found"}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/alerts/1", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, `{"capacity":5,"created_at":"2020-11-28T10:20:47+01:00",
|
||||||
|
"decisions":[
|
||||||
|
{"duration":"59m49.264032632s",
|
||||||
|
"end_ip":16843180,
|
||||||
|
"id":1,
|
||||||
|
"origin":"crowdsec",
|
||||||
|
"scenario":"crowdsecurity/ssh-bf",
|
||||||
|
"scope":"Ip",
|
||||||
|
"simulated":false,
|
||||||
|
"start_ip":16843180,
|
||||||
|
"type":"ban",
|
||||||
|
"value":"1.1.1.172"}
|
||||||
|
],
|
||||||
|
"events":[
|
||||||
|
{"meta":[
|
||||||
|
{"key":"target_user","value":"netflix"},
|
||||||
|
{"key":"service","value":"ssh"}
|
||||||
|
],
|
||||||
|
"timestamp":"2020-11-28 10:20:46 +0000 UTC"},
|
||||||
|
{"meta":[
|
||||||
|
{"key":"target_user","value":"netflix"},
|
||||||
|
{"key":"service","value":"ssh"}
|
||||||
|
],
|
||||||
|
"timestamp":"2020-11-28 10:20:46 +0000 UTC"}
|
||||||
|
],
|
||||||
|
"events_count":6,
|
||||||
|
"id":1,
|
||||||
|
"labels":null,
|
||||||
|
"leakspeed":"10s",
|
||||||
|
"machine_id":"test",
|
||||||
|
"message":"Ip 1.1.1.172 performed 'crowdsecurity/ssh-bf' (6 events over 2.920062ms) at 2020-11-28 10:20:46.845619968 +0100 CET m=+5.903899761",
|
||||||
|
"scenario":"crowdsecurity/ssh-bf",
|
||||||
|
"scenario_hash":"4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f",
|
||||||
|
"scenario_version":"0.1",
|
||||||
|
"simulated":false,
|
||||||
|
"source":{
|
||||||
|
"as_name":"Cloudflare Inc",
|
||||||
|
"cn":"AU",
|
||||||
|
"ip":"1.1.1.172",
|
||||||
|
"latitude":-37.7,
|
||||||
|
"longitude":145.1833,
|
||||||
|
"range":"1.1.1.0/24",
|
||||||
|
"scope":"Ip",
|
||||||
|
"value":"1.1.1.172"
|
||||||
|
},
|
||||||
|
"start_at":"2020-11-28 10:20:46.842701127 +0100 +0100",
|
||||||
|
"stop_at":"2020-11-28 10:20:46.845621385 +0100 +0100"
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
tcapacity := int32(5)
|
||||||
|
tduration := "59m49.264032632s"
|
||||||
|
torigin := "crowdsec"
|
||||||
|
tscenario := "crowdsecurity/ssh-bf"
|
||||||
|
tscope := "Ip"
|
||||||
|
ttype := "ban"
|
||||||
|
tvalue := "1.1.1.172"
|
||||||
|
ttimestamp := "2020-11-28 10:20:46 +0000 UTC"
|
||||||
|
teventscount := int32(6)
|
||||||
|
tleakspeed := "10s"
|
||||||
|
tmessage := "Ip 1.1.1.172 performed 'crowdsecurity/ssh-bf' (6 events over 2.920062ms) at 2020-11-28 10:20:46.845619968 +0100 CET m=+5.903899761"
|
||||||
|
tscenariohash := "4441dcff07020f6690d998b7101e642359ba405c2abb83565bbbdcee36de280f"
|
||||||
|
tscenarioversion := "0.1"
|
||||||
|
tstartat := "2020-11-28 10:20:46.842701127 +0100 +0100"
|
||||||
|
tstopat := "2020-11-28 10:20:46.845621385 +0100 +0100"
|
||||||
|
|
||||||
|
expected := &models.Alert{
|
||||||
|
Capacity: &tcapacity,
|
||||||
|
CreatedAt: "2020-11-28T10:20:47+01:00",
|
||||||
|
Decisions: []*models.Decision{
|
||||||
|
&models.Decision{
|
||||||
|
Duration: &tduration,
|
||||||
|
EndIP: 16843180,
|
||||||
|
ID: 1,
|
||||||
|
Origin: &torigin,
|
||||||
|
Scenario: &tscenario,
|
||||||
|
|
||||||
|
Scope: &tscope,
|
||||||
|
Simulated: new(bool), //false,
|
||||||
|
StartIP: 16843180,
|
||||||
|
Type: &ttype,
|
||||||
|
Value: &tvalue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Events: []*models.Event{
|
||||||
|
&models.Event{
|
||||||
|
Meta: models.Meta{
|
||||||
|
&models.MetaItems0{
|
||||||
|
Key: "target_user",
|
||||||
|
Value: "netflix",
|
||||||
|
},
|
||||||
|
&models.MetaItems0{
|
||||||
|
Key: "service",
|
||||||
|
Value: "ssh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timestamp: &ttimestamp,
|
||||||
|
},
|
||||||
|
&models.Event{
|
||||||
|
Meta: models.Meta{
|
||||||
|
&models.MetaItems0{
|
||||||
|
Key: "target_user",
|
||||||
|
Value: "netflix",
|
||||||
|
},
|
||||||
|
&models.MetaItems0{
|
||||||
|
Key: "service",
|
||||||
|
Value: "ssh",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Timestamp: &ttimestamp,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EventsCount: &teventscount,
|
||||||
|
ID: 1,
|
||||||
|
Leakspeed: &tleakspeed,
|
||||||
|
MachineID: "test",
|
||||||
|
Message: &tmessage,
|
||||||
|
Remediation: false,
|
||||||
|
Scenario: &tscenario,
|
||||||
|
ScenarioHash: &tscenariohash,
|
||||||
|
ScenarioVersion: &tscenarioversion,
|
||||||
|
Simulated: new(bool), //(false),
|
||||||
|
Source: &models.Source{
|
||||||
|
AsName: "Cloudflare Inc",
|
||||||
|
AsNumber: "",
|
||||||
|
Cn: "AU",
|
||||||
|
IP: "1.1.1.172",
|
||||||
|
Latitude: -37.7,
|
||||||
|
Longitude: 145.1833,
|
||||||
|
Range: "1.1.1.0/24",
|
||||||
|
Scope: &tscope,
|
||||||
|
Value: &tvalue,
|
||||||
|
},
|
||||||
|
StartAt: &tstartat,
|
||||||
|
StopAt: &tstopat,
|
||||||
|
}
|
||||||
|
|
||||||
|
alerts, resp, err := client.Alerts.GetByID(context.Background(), 1)
|
||||||
|
|
||||||
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(*alerts, *expected) {
|
||||||
|
t.Errorf("client.Alerts.List returned %+v, want %+v", resp, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
//fail
|
||||||
|
alerts, resp, err = client.Alerts.GetByID(context.Background(), 2)
|
||||||
|
assert.Contains(t, fmt.Sprintf("%s", err), "API error: object not found")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertsCreateAsMachine(t *testing.T) {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
|
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("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "POST")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`["3"]`))
|
||||||
|
})
|
||||||
|
log.Printf("URL is %s", urlx)
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.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 {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer teardown()
|
||||||
|
alert := models.AddAlertsRequest{}
|
||||||
|
alerts, resp, err := client.Alerts.Add(context.Background(), alert)
|
||||||
|
expected := &models.AddAlertsResponse{"3"}
|
||||||
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*alerts, *expected) {
|
||||||
|
t.Errorf("client.Alerts.List returned %+v, want %+v", resp, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlertsDeleteAsMachine(t *testing.T) {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
|
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("/alerts", 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(`{"message":"0 deleted alerts"}`))
|
||||||
|
})
|
||||||
|
log.Printf("URL is %s", urlx)
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.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 {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer teardown()
|
||||||
|
alert := AlertsDeleteOpts{IPEquals: new(string)}
|
||||||
|
*alert.IPEquals = "1.2.3.4"
|
||||||
|
alerts, resp, err := client.Alerts.Delete(context.Background(), alert)
|
||||||
|
expected := &models.DeleteAlertsResponse{""}
|
||||||
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(*alerts, *expected) {
|
||||||
|
t.Errorf("client.Alerts.List returned %+v, want %+v", resp, expected)
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,7 @@ type APIKeyTransport struct {
|
||||||
// RoundTrip implements the RoundTripper interface.
|
// RoundTrip implements the RoundTripper interface.
|
||||||
func (t *APIKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
func (t *APIKeyTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
if t.APIKey == "" {
|
if t.APIKey == "" {
|
||||||
return nil, errors.New("t.APIKey is empty")
|
return nil, errors.New("APIKey is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must make a copy of the Request so
|
// We must make a copy of the Request so
|
||||||
|
@ -97,7 +97,7 @@ func (t *JWTTransport) refreshJwtToken() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't update scenario list: %s", err)
|
return fmt.Errorf("can't update scenario list: %s", err)
|
||||||
}
|
}
|
||||||
log.Infof("scenarios liste updated for '%s'", *t.MachineID)
|
log.Infof("scenarios list updated for '%s'", *t.MachineID)
|
||||||
}
|
}
|
||||||
|
|
||||||
var auth = models.WatcherAuthRequest{
|
var auth = models.WatcherAuthRequest{
|
||||||
|
|
181
pkg/apiclient/auth_service_test.go
Normal file
181
pkg/apiclient/auth_service_test.go
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
package apiclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWatcherAuth(t *testing.T) {
|
||||||
|
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
//body: models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password}
|
||||||
|
|
||||||
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "POST")
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
_, _ = buf.ReadFrom(r.Body)
|
||||||
|
newStr := buf.String()
|
||||||
|
log.Printf("--> %s", newStr)
|
||||||
|
if newStr == `{"machine_id":"test_login","password":"test_password","scenarios":["crowdsecurity/test"]}
|
||||||
|
` {
|
||||||
|
log.Printf("ok cool")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, `{"code":200,"expire":"2029-11-30T14:14:24+01:00","token":"toto"}`)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
log.Printf("badbad")
|
||||||
|
fmt.Fprintf(w, `{"message":"access forbidden"}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
log.Printf("URL is %s", urlx)
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ok auth
|
||||||
|
mycfg := &Config{
|
||||||
|
MachineID: "test_login",
|
||||||
|
Password: "test_password",
|
||||||
|
UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||||
|
URL: apiURL,
|
||||||
|
VersionPrefix: "v1",
|
||||||
|
Scenarios: []string{"crowdsecurity/test"},
|
||||||
|
}
|
||||||
|
client, err := NewClient(mycfg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{
|
||||||
|
MachineID: &mycfg.MachineID,
|
||||||
|
Password: &mycfg.Password,
|
||||||
|
Scenarios: mycfg.Scenarios,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpect auth err 0: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//bad auth
|
||||||
|
mycfg = &Config{
|
||||||
|
MachineID: "BADtest_login",
|
||||||
|
Password: "BADtest_password",
|
||||||
|
UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||||
|
URL: apiURL,
|
||||||
|
VersionPrefix: "v1",
|
||||||
|
Scenarios: []string{"crowdsecurity/test"},
|
||||||
|
}
|
||||||
|
client, err = NewClient(mycfg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Auth.AuthenticateWatcher(context.Background(), models.WatcherAuthRequest{
|
||||||
|
MachineID: &mycfg.MachineID,
|
||||||
|
Password: &mycfg.Password,
|
||||||
|
})
|
||||||
|
assert.Contains(t, err.Error(), "403 Forbidden")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatcherRegister(t *testing.T) {
|
||||||
|
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
//body: models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password}
|
||||||
|
|
||||||
|
mux.HandleFunc("/watchers", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "POST")
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
_, _ = buf.ReadFrom(r.Body)
|
||||||
|
newStr := buf.String()
|
||||||
|
assert.Equal(t, newStr, `{"machine_id":"test_login","password":"test_password"}
|
||||||
|
`)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
log.Printf("URL is %s", urlx)
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
client, err := RegisterClient(&Config{
|
||||||
|
MachineID: "test_login",
|
||||||
|
Password: "test_password",
|
||||||
|
UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||||
|
URL: apiURL,
|
||||||
|
VersionPrefix: "v1",
|
||||||
|
}, &http.Client{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("while registering client : %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("->%T", client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWatcherUnregister(t *testing.T) {
|
||||||
|
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
//body: models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password}
|
||||||
|
|
||||||
|
mux.HandleFunc("/watchers", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "DELETE")
|
||||||
|
assert.Equal(t, r.ContentLength, int64(0))
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "POST")
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
_, _ = buf.ReadFrom(r.Body)
|
||||||
|
newStr := buf.String()
|
||||||
|
if newStr == `{"machine_id":"test_login","password":"test_password","scenarios":["crowdsecurity/test"]}
|
||||||
|
` {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprintf(w, `{"code":200,"expire":"2029-11-30T14:14:24+01:00","token":"toto"}`)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
fmt.Fprintf(w, `{"message":"access forbidden"}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("URL is %s", urlx)
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
mycfg := &Config{
|
||||||
|
MachineID: "test_login",
|
||||||
|
Password: "test_password",
|
||||||
|
UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||||
|
URL: apiURL,
|
||||||
|
VersionPrefix: "v1",
|
||||||
|
Scenarios: []string{"crowdsecurity/test"},
|
||||||
|
}
|
||||||
|
client, err := NewClient(mycfg)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
_, err = client.Auth.UnregisterWatcher(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("while registering client : %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("->%T", client)
|
||||||
|
}
|
83
pkg/apiclient/auth_test.go
Normal file
83
pkg/apiclient/auth_test.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package apiclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestApiAuth(t *testing.T) {
|
||||||
|
log.SetLevel(log.TraceLevel)
|
||||||
|
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
mux.HandleFunc("/decisions", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
if r.Header.Get("X-Api-Key") == "ixu" {
|
||||||
|
assert.Equal(t, r.URL.RawQuery, "ip=1.2.3.4")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`null`))
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
w.Write([]byte(`{"message":"access forbidden"}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
log.Printf("URL is %s", urlx)
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
//ok no answer
|
||||||
|
auth := &APIKeyTransport{
|
||||||
|
APIKey: "ixu",
|
||||||
|
}
|
||||||
|
|
||||||
|
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
alert := DecisionsListOpts{IPEquals: new(string)}
|
||||||
|
*alert.IPEquals = "1.2.3.4"
|
||||||
|
_, resp, err := newcli.Decisions.List(context.Background(), alert)
|
||||||
|
|
||||||
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ko bad token
|
||||||
|
auth = &APIKeyTransport{
|
||||||
|
APIKey: "bad",
|
||||||
|
}
|
||||||
|
|
||||||
|
newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp, err = newcli.Decisions.List(context.Background(), alert)
|
||||||
|
|
||||||
|
log.Infof("--> %s", err)
|
||||||
|
if resp.Response.StatusCode != http.StatusForbidden {
|
||||||
|
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
assert.Contains(t, err.Error(), "API error: access forbidden")
|
||||||
|
//ko empty token
|
||||||
|
auth = &APIKeyTransport{}
|
||||||
|
newcli, err = NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, resp, err = newcli.Decisions.List(context.Background(), alert)
|
||||||
|
|
||||||
|
log.Infof("--> %s", err)
|
||||||
|
assert.Contains(t, err.Error(), "APIKey is empty")
|
||||||
|
|
||||||
|
}
|
|
@ -86,11 +86,14 @@ func RegisterClient(config *Config, client *http.Client) (*ApiClient, error) {
|
||||||
c.Alerts = (*AlertsService)(&c.common)
|
c.Alerts = (*AlertsService)(&c.common)
|
||||||
c.Auth = (*AuthService)(&c.common)
|
c.Auth = (*AuthService)(&c.common)
|
||||||
|
|
||||||
_, err := c.Auth.RegisterWatcher(context.Background(), models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password})
|
resp, err := c.Auth.RegisterWatcher(context.Background(), models.WatcherRegistrationRequest{MachineID: &config.MachineID, Password: &config.Password})
|
||||||
|
/*if we have http status, return it*/
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c, errors.Wrapf(err, "api register (%s): %s", c.BaseURL, err)
|
if resp != nil && resp.Response != nil {
|
||||||
|
return nil, errors.Wrapf(err, "api register (%s) http %s : %s", c.BaseURL, resp.Response.Status, err)
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(err, "api register (%s) : %s", c.BaseURL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -107,7 +110,11 @@ type ErrorResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ErrorResponse) Error() string {
|
func (e *ErrorResponse) Error() string {
|
||||||
return fmt.Sprintf("API error (%s) : %s", *e.Message, e.Errors)
|
err := fmt.Sprintf("API error: %s", *e.Message)
|
||||||
|
if len(e.Errors) > 0 {
|
||||||
|
err += fmt.Sprintf(" (%s)", e.Errors)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResponse(r *http.Response) *Response {
|
func newResponse(r *http.Response) *Response {
|
||||||
|
@ -123,7 +130,13 @@ func CheckResponse(r *http.Response) error {
|
||||||
errorResponse := &ErrorResponse{}
|
errorResponse := &ErrorResponse{}
|
||||||
data, err := ioutil.ReadAll(r.Body)
|
data, err := ioutil.ReadAll(r.Body)
|
||||||
if err == nil && data != nil {
|
if err == nil && data != nil {
|
||||||
json.Unmarshal(data, errorResponse)
|
err := json.Unmarshal(data, errorResponse)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "http code %d, invalid body", r.StatusCode)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorResponse.Message = new(string)
|
||||||
|
*errorResponse.Message = fmt.Sprintf("http code %d, no error message", r.StatusCode)
|
||||||
}
|
}
|
||||||
return errorResponse
|
return errorResponse
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,11 +71,12 @@ func (c *ApiClient) Do(ctx context.Context, req *http.Request, v interface{}) (*
|
||||||
if e, ok := err.(*url.Error); ok {
|
if e, ok := err.(*url.Error); ok {
|
||||||
if url, err := url.Parse(e.URL); err == nil {
|
if url, err := url.Parse(e.URL); err == nil {
|
||||||
e.URL = url.String()
|
e.URL = url.String()
|
||||||
return nil, e
|
return newResponse(resp), e
|
||||||
|
} else {
|
||||||
|
return newResponse(resp), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return newResponse(resp), err
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response := newResponse(resp)
|
response := newResponse(resp)
|
||||||
|
@ -98,6 +99,5 @@ func (c *ApiClient) Do(ctx context.Context, req *http.Request, v interface{}) (*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, err
|
return response, err
|
||||||
}
|
}
|
||||||
|
|
77
pkg/apiclient/client_http_test.go
Normal file
77
pkg/apiclient/client_http_test.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package apiclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewRequestInvalid(t *testing.T) {
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
//missing slash in uri
|
||||||
|
apiURL, err := url.Parse(urlx)
|
||||||
|
if err != nil {
|
||||||
|
log.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.Error())
|
||||||
|
}
|
||||||
|
/*mock login*/
|
||||||
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write([]byte(`{"code": 401, "message" : "bad login/password"}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
|
assert.Contains(t, err.Error(), `building request: BaseURL must have a trailing slash, but `)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRequestTimeout(t *testing.T) {
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
//missing slash in uri
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.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.Error())
|
||||||
|
}
|
||||||
|
/*mock login*/
|
||||||
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, _, err = client.Alerts.List(ctx, AlertsListOpts{})
|
||||||
|
assert.Contains(t, err.Error(), `performing request: context deadline exceeded`)
|
||||||
|
}
|
200
pkg/apiclient/client_test.go
Normal file
200
pkg/apiclient/client_test.go
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
package apiclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*this is a ripoff of google/go-github approach :
|
||||||
|
- setup a test http server along with a client that is configured to talk to test server
|
||||||
|
- each test will then bind handler for the method(s) they want to try
|
||||||
|
*/
|
||||||
|
|
||||||
|
func setup() (mux *http.ServeMux, serverURL string, teardown func()) {
|
||||||
|
// mux is the HTTP request multiplexer used with the test server.
|
||||||
|
mux = http.NewServeMux()
|
||||||
|
baseURLPath := "/v1"
|
||||||
|
|
||||||
|
apiHandler := http.NewServeMux()
|
||||||
|
apiHandler.Handle(baseURLPath+"/", http.StripPrefix(baseURLPath, mux))
|
||||||
|
|
||||||
|
// server is a test HTTP server used to provide mock API responses.
|
||||||
|
server := httptest.NewServer(apiHandler)
|
||||||
|
|
||||||
|
return mux, server.URL, server.Close
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMethod(t *testing.T, r *http.Request, want string) {
|
||||||
|
t.Helper()
|
||||||
|
if got := r.Method; got != want {
|
||||||
|
t.Errorf("Request method: %v, want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClientOk(t *testing.T) {
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.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.Error())
|
||||||
|
}
|
||||||
|
/*mock login*/
|
||||||
|
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("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, resp, err := client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("test Unable to list alerts : %+v", err)
|
||||||
|
}
|
||||||
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusCreated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClientKo(t *testing.T) {
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.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.Error())
|
||||||
|
}
|
||||||
|
/*mock login*/
|
||||||
|
mux.HandleFunc("/watchers/login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write([]byte(`{"code": 401, "message" : "bad login/password"}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "GET")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
|
assert.Contains(t, err.Error(), `received response status "401 Unauthorized"`)
|
||||||
|
log.Printf("err-> %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDefaultClient(t *testing.T) {
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
client, err := NewDefaultClient(apiURL, "/v1", "", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write([]byte(`{"code": 401, "message" : "brr"}`))
|
||||||
|
})
|
||||||
|
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
||||||
|
assert.Contains(t, err.Error(), `performing request: API error: brr`)
|
||||||
|
log.Printf("err-> %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClientRegisterKO(t *testing.T) {
|
||||||
|
apiURL, err := url.Parse("http://127.0.0.1:4242/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
_, err = RegisterClient(&Config{
|
||||||
|
MachineID: "test_login",
|
||||||
|
Password: "test_password",
|
||||||
|
UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||||
|
URL: apiURL,
|
||||||
|
VersionPrefix: "v1",
|
||||||
|
}, &http.Client{})
|
||||||
|
assert.Contains(t, fmt.Sprintf("%s", err), "dial tcp 127.0.0.1:4242: connect: connection refused")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClientRegisterOK(t *testing.T) {
|
||||||
|
log.SetLevel(log.TraceLevel)
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
/*mock login*/
|
||||||
|
mux.HandleFunc("/watchers", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "POST")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"code": 200, "expire": "2030-01-02T15:04:05Z", "token": "oklol"}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
client, err := RegisterClient(&Config{
|
||||||
|
MachineID: "test_login",
|
||||||
|
Password: "test_password",
|
||||||
|
UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||||
|
URL: apiURL,
|
||||||
|
VersionPrefix: "v1",
|
||||||
|
}, &http.Client{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("while registering client : %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("->%T", client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClientBadAnswer(t *testing.T) {
|
||||||
|
log.SetLevel(log.TraceLevel)
|
||||||
|
mux, urlx, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
/*mock login*/
|
||||||
|
mux.HandleFunc("/watchers", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
testMethod(t, r, "POST")
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
w.Write([]byte(`bad`))
|
||||||
|
})
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
_, err = RegisterClient(&Config{
|
||||||
|
MachineID: "test_login",
|
||||||
|
Password: "test_password",
|
||||||
|
UserAgent: fmt.Sprintf("crowdsec/%s", cwversion.VersionStr()),
|
||||||
|
URL: apiURL,
|
||||||
|
VersionPrefix: "v1",
|
||||||
|
}, &http.Client{})
|
||||||
|
assert.Contains(t, fmt.Sprintf("%s", err), `invalid body: invalid character 'b' looking for beginning of value`)
|
||||||
|
}
|
|
@ -35,7 +35,7 @@ func (s *DecisionsService) List(ctx context.Context, opts DecisionsListOpts) (*m
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
u := fmt.Sprintf("%s/decisions/?%s", s.client.URLPrefix, params.Encode())
|
u := fmt.Sprintf("%s/decisions?%s", s.client.URLPrefix, params.Encode())
|
||||||
|
|
||||||
req, err := s.client.NewRequest("GET", u, nil)
|
req, err := s.client.NewRequest("GET", u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
273
pkg/apiclient/decisions_service_test.go
Normal file
273
pkg/apiclient/decisions_service_test.go
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
package apiclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/cwversion"
|
||||||
|
"github.com/crowdsecurity/crowdsec/pkg/models"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
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","end_ip":16909060,"id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","start_ip":16909060,"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 {
|
||||||
|
log.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ok answer
|
||||||
|
auth := &APIKeyTransport{
|
||||||
|
APIKey: "ixu",
|
||||||
|
}
|
||||||
|
|
||||||
|
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
EndIP: 16909060,
|
||||||
|
ID: 4,
|
||||||
|
Origin: &torigin,
|
||||||
|
Scenario: &tscenario,
|
||||||
|
Scope: &tscope,
|
||||||
|
StartIP: 16909060,
|
||||||
|
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 {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
|
||||||
|
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, "GET")
|
||||||
|
if r.Method == "GET" {
|
||||||
|
|
||||||
|
if r.URL.RawQuery == "startup=true" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.Write([]byte(`{"deleted":null,"new":[{"duration":"3h59m55.756182786s","end_ip":16909060,"id":4,"origin":"cscli","scenario":"manual 'ban' from '82929df7ee394b73b81252fe3b4e50203yaT2u6nXiaN7Ix9'","scope":"Ip","start_ip":16909060,"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, "DELETE")
|
||||||
|
if r.Method == "DELETE" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
apiURL, err := url.Parse(urlx + "/")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("parsing api url: %s", apiURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ok answer
|
||||||
|
auth := &APIKeyTransport{
|
||||||
|
APIKey: "ixu",
|
||||||
|
}
|
||||||
|
|
||||||
|
newcli, err := NewDefaultClient(apiURL, "v1", "toto", auth.Client())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
EndIP: 16909060,
|
||||||
|
ID: 4,
|
||||||
|
Origin: &torigin,
|
||||||
|
Scenario: &tscenario,
|
||||||
|
Scope: &tscope,
|
||||||
|
StartIP: 16909060,
|
||||||
|
Type: &ttype,
|
||||||
|
Value: &tvalue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
decisions, resp, err := newcli.Decisions.GetStream(context.Background(), true)
|
||||||
|
|
||||||
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
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(), false)
|
||||||
|
|
||||||
|
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())
|
||||||
|
|
||||||
|
if resp.Response.StatusCode != http.StatusOK {
|
||||||
|
t.Errorf("Alerts.List returned status: %d, want %d", resp.Response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
log.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 {
|
||||||
|
log.Fatalf("new api client: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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 {
|
||||||
|
// log.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 {
|
||||||
|
// log.Fatalf("new api client: %s", err.Error())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
// }
|
|
@ -130,7 +130,7 @@ func TestCreateAlert(t *testing.T) {
|
||||||
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", loginResp.Token))
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, "[\"1\"]", w.Body.String())
|
assert.Equal(t, "[\"1\"]", w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -538,5 +538,5 @@ func TestDeleteAlert(t *testing.T) {
|
||||||
req.RemoteAddr = "127.0.0.1:4242"
|
req.RemoteAddr = "127.0.0.1:4242"
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 200, w.Code)
|
||||||
assert.Equal(t, `{"message":"1 deleted alerts"}`, w.Body.String())
|
assert.Equal(t, `{"nbDeleted":"1"}`, w.Body.String())
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,5 @@ func TestUnknownPath(t *testing.T) {
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 404, w.Code)
|
assert.Equal(t, 404, w.Code)
|
||||||
assert.Equal(t, "{\"message\":\"Page or Method not found\"}", w.Body.String())
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
v1 "github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers/v1"
|
v1 "github.com/crowdsecurity/crowdsec/pkg/apiserver/controllers/v1"
|
||||||
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
|
||||||
|
@ -43,6 +44,14 @@ func (c *Controller) NewV1() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Router.Use(v1.PrometheusMiddleware())
|
c.Router.Use(v1.PrometheusMiddleware())
|
||||||
|
c.Router.HandleMethodNotAllowed = true
|
||||||
|
c.Router.NoRoute(func(ctx *gin.Context) {
|
||||||
|
ctx.AbortWithStatus(http.StatusNotFound)
|
||||||
|
})
|
||||||
|
c.Router.NoMethod(func(ctx *gin.Context) {
|
||||||
|
ctx.AbortWithStatus(http.StatusMethodNotAllowed)
|
||||||
|
})
|
||||||
|
|
||||||
groupV1 := c.Router.Group("/v1")
|
groupV1 := c.Router.Group("/v1")
|
||||||
groupV1.POST("/watchers", handlerV1.CreateMachine)
|
groupV1.POST("/watchers", handlerV1.CreateMachine)
|
||||||
groupV1.POST("/watchers/login", handlerV1.Middlewares.JWT.Middleware.LoginHandler)
|
groupV1.POST("/watchers/login", handlerV1.Middlewares.JWT.Middleware.LoginHandler)
|
||||||
|
|
|
@ -146,7 +146,7 @@ func (c *Controller) CreateAlert(gctx *gin.Context) {
|
||||||
log.Warningf("Cannot send alert to Central API channel")
|
log.Warningf("Cannot send alert to Central API channel")
|
||||||
}
|
}
|
||||||
|
|
||||||
gctx.JSON(http.StatusOK, alerts)
|
gctx.JSON(http.StatusCreated, alerts)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,11 +202,15 @@ func (c *Controller) DeleteAlerts(gctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
deleted, err := c.DBClient.DeleteAlertWithFilter(gctx.Request.URL.Query())
|
nbDeleted, err := c.DBClient.DeleteAlertWithFilter(gctx.Request.URL.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.HandleDBErrors(gctx, err)
|
c.HandleDBErrors(gctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
gctx.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("%d deleted alerts", len(deleted))})
|
deleteAlertsResp := models.DeleteAlertsResponse{
|
||||||
|
NbDeleted: strconv.Itoa(nbDeleted),
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx.JSON(http.StatusOK, deleteAlertsResp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,9 @@ import (
|
||||||
|
|
||||||
func (c *Controller) HandleDBErrors(gctx *gin.Context, err error) {
|
func (c *Controller) HandleDBErrors(gctx *gin.Context, err error) {
|
||||||
switch errors.Cause(err) {
|
switch errors.Cause(err) {
|
||||||
|
case database.ItemNotFound:
|
||||||
|
gctx.JSON(http.StatusNotFound, gin.H{"message": err.Error()})
|
||||||
|
return
|
||||||
case database.UserExists:
|
case database.UserExists:
|
||||||
gctx.JSON(http.StatusForbidden, gin.H{"message": err.Error()})
|
gctx.JSON(http.StatusForbidden, gin.H{"message": err.Error()})
|
||||||
return
|
return
|
||||||
|
|
|
@ -28,6 +28,6 @@ func (c *Controller) CreateMachine(gctx *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gctx.Status(http.StatusOK)
|
gctx.Status(http.StatusCreated)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ func TestCreateMachine(t *testing.T) {
|
||||||
req.Header.Add("User-Agent", UserAgent)
|
req.Header.Add("User-Agent", UserAgent)
|
||||||
router.ServeHTTP(w, req)
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, 200, w.Code)
|
assert.Equal(t, 201, w.Code)
|
||||||
assert.Equal(t, "", w.Body.String())
|
assert.Equal(t, "", w.Body.String())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -491,7 +491,7 @@ func (c *Client) DeleteAlertGraph(alertItem *ent.Alert) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) DeleteAlertWithFilter(filter map[string][]string) ([]*ent.Alert, error) {
|
func (c *Client) DeleteAlertWithFilter(filter map[string][]string) (int, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Get all the alerts that match the filter
|
// Get all the alerts that match the filter
|
||||||
|
@ -501,10 +501,10 @@ func (c *Client) DeleteAlertWithFilter(filter map[string][]string) ([]*ent.Alert
|
||||||
err = c.DeleteAlertGraph(alertItem)
|
err = c.DeleteAlertGraph(alertItem)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("DeleteAlertWithFilter : %s", err)
|
log.Warningf("DeleteAlertWithFilter : %s", err)
|
||||||
return []*ent.Alert{}, errors.Wrapf(DeleteFail, "event with alert ID '%d'", alertItem.ID)
|
return 0, errors.Wrapf(DeleteFail, "event with alert ID '%d'", alertItem.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return alertsToDelete, nil
|
return len(alertsToDelete), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FlushAlerts(MaxAge string, MaxItems int) error {
|
func (c *Client) FlushAlerts(MaxAge string, MaxItems int) error {
|
||||||
|
@ -521,12 +521,12 @@ func (c *Client) FlushAlerts(MaxAge string, MaxItems int) error {
|
||||||
filter := map[string][]string{
|
filter := map[string][]string{
|
||||||
"created_before": {MaxAge},
|
"created_before": {MaxAge},
|
||||||
}
|
}
|
||||||
deleted, err := c.DeleteAlertWithFilter(filter)
|
nbDeleted, err := c.DeleteAlertWithFilter(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("FlushAlerts (max age) : %s", err)
|
log.Warningf("FlushAlerts (max age) : %s", err)
|
||||||
return errors.Wrapf(err, "unable to flush alerts with filter until: %s", MaxAge)
|
return errors.Wrapf(err, "unable to flush alerts with filter until: %s", MaxAge)
|
||||||
}
|
}
|
||||||
deletedByAge = len(deleted)
|
deletedByAge = nbDeleted
|
||||||
}
|
}
|
||||||
if MaxItems > 0 {
|
if MaxItems > 0 {
|
||||||
if totalAlerts > MaxItems {
|
if totalAlerts > MaxItems {
|
||||||
|
@ -563,8 +563,13 @@ func (c *Client) FlushAlerts(MaxAge string, MaxItems int) error {
|
||||||
func (c *Client) GetAlertByID(alertID int) (*ent.Alert, error) {
|
func (c *Client) GetAlertByID(alertID int) (*ent.Alert, error) {
|
||||||
alert, err := c.Ent.Alert.Query().Where(alert.IDEQ(alertID)).WithDecisions().WithEvents().WithMetas().WithOwner().First(c.CTX)
|
alert, err := c.Ent.Alert.Query().Where(alert.IDEQ(alertID)).WithDecisions().WithEvents().WithMetas().WithOwner().First(c.CTX)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
/*record not found, 404*/
|
||||||
|
if ent.IsNotFound(err) {
|
||||||
|
log.Warningf("GetAlertByID (not found): %s", err)
|
||||||
|
return &ent.Alert{}, ItemNotFound
|
||||||
|
}
|
||||||
log.Warningf("GetAlertByID : %s", err)
|
log.Warningf("GetAlertByID : %s", err)
|
||||||
return &ent.Alert{}, errors.Wrapf(QueryFail, "alert id '%d'", alertID)
|
return &ent.Alert{}, QueryFail
|
||||||
}
|
}
|
||||||
return alert, nil
|
return alert, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -278,5 +278,9 @@ func (c *Client) SoftDeleteDecisionByID(decisionID int) error {
|
||||||
log.Warningf("SoftDeleteDecisionByID : %v (nb soft deleted: %d)", err, nbUpdated)
|
log.Warningf("SoftDeleteDecisionByID : %v (nb soft deleted: %d)", err, nbUpdated)
|
||||||
return errors.Wrapf(DeleteFail, "decision with id '%d' doesn't exist", decisionID)
|
return errors.Wrapf(DeleteFail, "decision with id '%d' doesn't exist", decisionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if nbUpdated == 0 {
|
||||||
|
return ItemNotFound
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ var (
|
||||||
QueryFail = errors.New("unable to query")
|
QueryFail = errors.New("unable to query")
|
||||||
UpdateFail = errors.New("unable to update")
|
UpdateFail = errors.New("unable to update")
|
||||||
DeleteFail = errors.New("unable to delete")
|
DeleteFail = errors.New("unable to delete")
|
||||||
|
ItemNotFound = errors.New("object not found")
|
||||||
ParseTimeFail = errors.New("unable to parse time")
|
ParseTimeFail = errors.New("unable to parse time")
|
||||||
ParseDurationFail = errors.New("unable to parse duration")
|
ParseDurationFail = errors.New("unable to parse duration")
|
||||||
MarshalFail = errors.New("unable to marshal")
|
MarshalFail = errors.New("unable to marshal")
|
||||||
|
|
|
@ -267,8 +267,8 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/WatcherRegistrationRequest'
|
$ref: '#/definitions/WatcherRegistrationRequest'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'201':
|
||||||
description: Watcher registered
|
description: Watcher Created
|
||||||
headers: {}
|
headers: {}
|
||||||
'400':
|
'400':
|
||||||
description: "400 response"
|
description: "400 response"
|
||||||
|
@ -322,8 +322,8 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/AddAlertsRequest'
|
$ref: '#/definitions/AddAlertsRequest'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'201':
|
||||||
description: successful operation
|
description: Alert(s) created
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/AddAlertsResponse'
|
$ref: '#/definitions/AddAlertsResponse'
|
||||||
headers: {}
|
headers: {}
|
||||||
|
|
Loading…
Reference in a new issue