2020-11-30 15:15:07 +00:00
|
|
|
package apiclient
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2024-03-14 09:43:02 +00:00
|
|
|
"net"
|
2020-11-30 15:15:07 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"net/url"
|
2024-03-14 09:43:02 +00:00
|
|
|
"path"
|
2022-05-17 10:14:59 +00:00
|
|
|
"runtime"
|
2024-03-14 09:43:02 +00:00
|
|
|
"strings"
|
2020-11-30 15:15:07 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
log "github.com/sirupsen/logrus"
|
2023-06-22 13:01:34 +00:00
|
|
|
"github.com/stretchr/testify/assert"
|
2024-01-04 16:10:36 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2023-05-23 08:52:47 +00:00
|
|
|
|
2024-01-04 16:10:36 +00:00
|
|
|
"github.com/crowdsecurity/go-cs-lib/cstest"
|
2023-07-28 14:35:08 +00:00
|
|
|
"github.com/crowdsecurity/go-cs-lib/version"
|
2020-11-30 15:15:07 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
/*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
|
|
|
|
*/
|
|
|
|
|
2024-01-04 16:10:36 +00:00
|
|
|
func setup() (*http.ServeMux, string, func()) {
|
2023-02-06 13:06:14 +00:00
|
|
|
return setupWithPrefix("v1")
|
|
|
|
}
|
|
|
|
|
2024-01-04 16:10:36 +00:00
|
|
|
func setupWithPrefix(urlPrefix string) (*http.ServeMux, string, func()) {
|
2020-11-30 15:15:07 +00:00
|
|
|
// mux is the HTTP request multiplexer used with the test server.
|
2024-01-04 16:10:36 +00:00
|
|
|
mux := http.NewServeMux()
|
2023-02-06 13:06:14 +00:00
|
|
|
baseURLPath := "/" + urlPrefix
|
2020-11-30 15:15:07 +00:00
|
|
|
|
|
|
|
apiHandler := http.NewServeMux()
|
|
|
|
apiHandler.Handle(baseURLPath+"/", http.StripPrefix(baseURLPath, mux))
|
|
|
|
|
|
|
|
server := httptest.NewServer(apiHandler)
|
|
|
|
|
|
|
|
return mux, server.URL, server.Close
|
|
|
|
}
|
|
|
|
|
2024-03-14 09:43:02 +00:00
|
|
|
// toUNCPath converts a Windows file path to a UNC path.
|
|
|
|
// This is necessary because the Go http package does not support Windows file paths.
|
|
|
|
func toUNCPath(path string) (string, error) {
|
|
|
|
colonIdx := strings.Index(path, ":")
|
|
|
|
if colonIdx == -1 {
|
|
|
|
return "", fmt.Errorf("invalid path format, missing drive letter: %s", path)
|
|
|
|
}
|
|
|
|
|
|
|
|
// URL parsing does not like backslashes
|
|
|
|
remaining := strings.ReplaceAll(path[colonIdx+1:], "\\", "/")
|
|
|
|
uncPath := "//localhost/" + path[:colonIdx] + "$" + remaining
|
|
|
|
|
|
|
|
return uncPath, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupUnixSocketWithPrefix(socket string, urlPrefix string) (mux *http.ServeMux, serverURL string, teardown func()) {
|
|
|
|
var err error
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
socket, err = toUNCPath(socket)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("converting to UNC path: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mux = http.NewServeMux()
|
|
|
|
baseURLPath := "/" + urlPrefix
|
|
|
|
|
|
|
|
apiHandler := http.NewServeMux()
|
|
|
|
apiHandler.Handle(baseURLPath+"/", http.StripPrefix(baseURLPath, mux))
|
|
|
|
|
|
|
|
server := httptest.NewUnstartedServer(apiHandler)
|
|
|
|
l, _ := net.Listen("unix", socket)
|
|
|
|
_ = server.Listener.Close()
|
|
|
|
server.Listener = l
|
|
|
|
server.Start()
|
|
|
|
|
|
|
|
return mux, socket, server.Close
|
|
|
|
}
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
func testMethod(t *testing.T, r *http.Request, want string) {
|
|
|
|
t.Helper()
|
2024-01-04 16:10:36 +00:00
|
|
|
assert.Equal(t, want, r.Method)
|
2020-11-30 15:15:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestNewClientOk(t *testing.T) {
|
|
|
|
mux, urlx, teardown := setup()
|
|
|
|
defer teardown()
|
2024-01-04 16:10:36 +00:00
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
apiURL, err := url.Parse(urlx + "/")
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
client, err := NewClient(&Config{
|
|
|
|
MachineID: "test_login",
|
|
|
|
Password: "test_password",
|
2023-05-23 08:52:47 +00:00
|
|
|
UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
|
2020-11-30 15:15:07 +00:00
|
|
|
URL: apiURL,
|
|
|
|
VersionPrefix: "v1",
|
|
|
|
})
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
/*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{})
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, http.StatusOK, resp.Response.StatusCode)
|
2020-11-30 15:15:07 +00:00
|
|
|
}
|
|
|
|
|
2024-03-14 09:43:02 +00:00
|
|
|
func TestNewClientOk_UnixSocket(t *testing.T) {
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
socket := path.Join(tmpDir, "socket")
|
|
|
|
|
|
|
|
mux, urlx, teardown := setupUnixSocketWithPrefix(socket, "v1")
|
|
|
|
defer teardown()
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
/*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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
func TestNewClientKo(t *testing.T) {
|
|
|
|
mux, urlx, teardown := setup()
|
|
|
|
defer teardown()
|
2024-01-04 16:10:36 +00:00
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
apiURL, err := url.Parse(urlx + "/")
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
client, err := NewClient(&Config{
|
|
|
|
MachineID: "test_login",
|
|
|
|
Password: "test_password",
|
2023-05-23 08:52:47 +00:00
|
|
|
UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
|
2020-11-30 15:15:07 +00:00
|
|
|
URL: apiURL,
|
|
|
|
VersionPrefix: "v1",
|
|
|
|
})
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
/*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{})
|
2024-01-04 16:10:36 +00:00
|
|
|
cstest.RequireErrorContains(t, err, `API error: bad login/password`)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
log.Printf("err-> %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNewDefaultClient(t *testing.T) {
|
|
|
|
mux, urlx, teardown := setup()
|
|
|
|
defer teardown()
|
2024-01-04 16:10:36 +00:00
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
apiURL, err := url.Parse(urlx + "/")
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
client, err := NewDefaultClient(apiURL, "/v1", "", nil)
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
2023-12-14 13:54:11 +00:00
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
mux.HandleFunc("/alerts", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
|
|
w.Write([]byte(`{"code": 401, "message" : "brr"}`))
|
|
|
|
})
|
2024-01-04 16:10:36 +00:00
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
_, _, err = client.Alerts.List(context.Background(), AlertsListOpts{})
|
2024-01-04 16:10:36 +00:00
|
|
|
cstest.RequireErrorMessage(t, err, "performing request: API error: brr")
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
log.Printf("err-> %s", err)
|
|
|
|
}
|
|
|
|
|
2024-03-14 09:43:02 +00:00
|
|
|
func TestNewDefaultClient_UnixSocket(t *testing.T) {
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
socket := path.Join(tmpDir, "socket")
|
|
|
|
|
|
|
|
mux, urlx, teardown := setupUnixSocketWithPrefix(socket, "v1")
|
|
|
|
defer teardown()
|
|
|
|
|
|
|
|
apiURL, err := url.Parse(urlx)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("parsing api url: %s", apiURL)
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := NewDefaultClient(apiURL, "/v1", "", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("new api client: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
func TestNewClientRegisterKO(t *testing.T) {
|
|
|
|
apiURL, err := url.Parse("http://127.0.0.1:4242/")
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
_, err = RegisterClient(&Config{
|
|
|
|
MachineID: "test_login",
|
|
|
|
Password: "test_password",
|
2023-05-23 08:52:47 +00:00
|
|
|
UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
|
2020-11-30 15:15:07 +00:00
|
|
|
URL: apiURL,
|
|
|
|
VersionPrefix: "v1",
|
|
|
|
}, &http.Client{})
|
2024-01-04 16:10:36 +00:00
|
|
|
|
2024-03-14 09:43:02 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
2024-01-04 16:10:36 +00:00
|
|
|
cstest.RequireErrorContains(t, err, " No connection could be made because the target machine actively refused it.")
|
2024-03-14 09:43:02 +00:00
|
|
|
} else {
|
|
|
|
cstest.RequireErrorContains(t, err, "dial tcp 127.0.0.1:4242: connect: connection refused")
|
2022-05-17 10:14:59 +00:00
|
|
|
}
|
2020-11-30 15:15:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestNewClientRegisterOK(t *testing.T) {
|
|
|
|
log.SetLevel(log.TraceLevel)
|
2023-12-14 13:54:11 +00:00
|
|
|
|
2024-01-04 16:10:36 +00:00
|
|
|
mux, urlx, teardown := setup()
|
2020-11-30 15:15:07 +00:00
|
|
|
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 + "/")
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
client, err := RegisterClient(&Config{
|
|
|
|
MachineID: "test_login",
|
|
|
|
Password: "test_password",
|
2023-05-23 08:52:47 +00:00
|
|
|
UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
|
2020-11-30 15:15:07 +00:00
|
|
|
URL: apiURL,
|
|
|
|
VersionPrefix: "v1",
|
|
|
|
}, &http.Client{})
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
2023-12-14 13:54:11 +00:00
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
log.Printf("->%T", client)
|
|
|
|
}
|
|
|
|
|
2024-03-14 09:43:02 +00:00
|
|
|
func TestNewClientRegisterOK_UnixSocket(t *testing.T) {
|
|
|
|
log.SetLevel(log.TraceLevel)
|
|
|
|
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
socket := path.Join(tmpDir, "socket")
|
|
|
|
|
|
|
|
mux, urlx, teardown := setupUnixSocketWithPrefix(socket, "v1")
|
|
|
|
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", version.String()),
|
|
|
|
URL: apiURL,
|
|
|
|
VersionPrefix: "v1",
|
|
|
|
}, &http.Client{})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("while registering client : %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("->%T", client)
|
|
|
|
}
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
func TestNewClientBadAnswer(t *testing.T) {
|
|
|
|
log.SetLevel(log.TraceLevel)
|
2023-12-14 13:54:11 +00:00
|
|
|
|
2024-01-04 16:10:36 +00:00
|
|
|
mux, urlx, teardown := setup()
|
2020-11-30 15:15:07 +00:00
|
|
|
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`))
|
|
|
|
})
|
2024-01-04 16:10:36 +00:00
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
apiURL, err := url.Parse(urlx + "/")
|
2024-01-04 16:10:36 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2020-11-30 15:15:07 +00:00
|
|
|
_, err = RegisterClient(&Config{
|
|
|
|
MachineID: "test_login",
|
|
|
|
Password: "test_password",
|
2023-05-23 08:52:47 +00:00
|
|
|
UserAgent: fmt.Sprintf("crowdsec/%s", version.String()),
|
2020-11-30 15:15:07 +00:00
|
|
|
URL: apiURL,
|
|
|
|
VersionPrefix: "v1",
|
|
|
|
}, &http.Client{})
|
2024-01-04 16:10:36 +00:00
|
|
|
cstest.RequireErrorContains(t, err, "invalid body: invalid character 'b' looking for beginning of value")
|
2020-11-30 15:15:07 +00:00
|
|
|
}
|