crowdsec/pkg/apiserver/apiserver_test.go
mmetc da746f77d5
apiserver/apiclient: compact tests (#2694)
* apiserver/apiclient: compact tests
* update golangci-lint configuration
2024-01-04 17:10:36 +01:00

420 lines
11 KiB
Go

package apiserver
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/crowdsecurity/go-cs-lib/cstest"
"github.com/crowdsecurity/go-cs-lib/ptr"
"github.com/crowdsecurity/go-cs-lib/version"
middlewares "github.com/crowdsecurity/crowdsec/pkg/apiserver/middlewares/v1"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/database"
"github.com/crowdsecurity/crowdsec/pkg/models"
"github.com/crowdsecurity/crowdsec/pkg/types"
)
var testMachineID = "test"
var testPassword = strfmt.Password("test")
var MachineTest = models.WatcherAuthRequest{
MachineID: &testMachineID,
Password: &testPassword,
}
var UserAgent = fmt.Sprintf("crowdsec-test/%s", version.Version)
var emptyBody = strings.NewReader("")
func LoadTestConfig(t *testing.T) csconfig.Config {
config := csconfig.Config{}
maxAge := "1h"
flushConfig := csconfig.FlushDBCfg{
MaxAge: &maxAge,
}
tempDir, _ := os.MkdirTemp("", "crowdsec_tests")
t.Cleanup(func() { os.RemoveAll(tempDir) })
dbconfig := csconfig.DatabaseCfg{
Type: "sqlite",
DbPath: filepath.Join(tempDir, "ent"),
Flush: &flushConfig,
}
apiServerConfig := csconfig.LocalApiServerCfg{
ListenURI: "http://127.0.0.1:8080",
DbConfig: &dbconfig,
ProfilesPath: "./tests/profiles.yaml",
ConsoleConfig: &csconfig.ConsoleConfig{
ShareManualDecisions: new(bool),
ShareTaintedScenarios: new(bool),
ShareCustomScenarios: new(bool),
},
}
apiConfig := csconfig.APICfg{
Server: &apiServerConfig,
}
config.API = &apiConfig
err := config.API.Server.LoadProfiles()
require.NoError(t, err)
return config
}
func LoadTestConfigForwardedFor(t *testing.T) csconfig.Config {
config := csconfig.Config{}
maxAge := "1h"
flushConfig := csconfig.FlushDBCfg{
MaxAge: &maxAge,
}
tempDir, _ := os.MkdirTemp("", "crowdsec_tests")
t.Cleanup(func() { os.RemoveAll(tempDir) })
dbconfig := csconfig.DatabaseCfg{
Type: "sqlite",
DbPath: filepath.Join(tempDir, "ent"),
Flush: &flushConfig,
}
apiServerConfig := csconfig.LocalApiServerCfg{
ListenURI: "http://127.0.0.1:8080",
DbConfig: &dbconfig,
ProfilesPath: "./tests/profiles.yaml",
UseForwardedForHeaders: true,
TrustedProxies: &[]string{"0.0.0.0/0"},
ConsoleConfig: &csconfig.ConsoleConfig{
ShareManualDecisions: new(bool),
ShareTaintedScenarios: new(bool),
ShareCustomScenarios: new(bool),
},
}
apiConfig := csconfig.APICfg{
Server: &apiServerConfig,
}
config.API = &apiConfig
err := config.API.Server.LoadProfiles()
require.NoError(t, err)
return config
}
func NewAPIServer(t *testing.T) (*APIServer, csconfig.Config) {
config := LoadTestConfig(t)
os.Remove("./ent")
apiServer, err := NewServer(config.API.Server)
require.NoError(t, err)
log.Printf("Creating new API server")
gin.SetMode(gin.TestMode)
return apiServer, config
}
func NewAPITest(t *testing.T) (*gin.Engine, csconfig.Config) {
apiServer, config := NewAPIServer(t)
err := apiServer.InitController()
require.NoError(t, err)
router, err := apiServer.Router()
require.NoError(t, err)
return router, config
}
func NewAPITestForwardedFor(t *testing.T) (*gin.Engine, csconfig.Config) {
config := LoadTestConfigForwardedFor(t)
os.Remove("./ent")
apiServer, err := NewServer(config.API.Server)
require.NoError(t, err)
err = apiServer.InitController()
require.NoError(t, err)
log.Printf("Creating new API server")
gin.SetMode(gin.TestMode)
router, err := apiServer.Router()
require.NoError(t, err)
return router, config
}
func ValidateMachine(t *testing.T, machineID string, config *csconfig.DatabaseCfg) {
dbClient, err := database.NewClient(config)
require.NoError(t, err)
err = dbClient.ValidateMachine(machineID)
require.NoError(t, err)
}
func GetMachineIP(t *testing.T, machineID string, config *csconfig.DatabaseCfg) string {
dbClient, err := database.NewClient(config)
require.NoError(t, err)
machines, err := dbClient.ListMachines()
require.NoError(t, err)
for _, machine := range machines {
if machine.MachineId == machineID {
return machine.IpAddress
}
}
return ""
}
func GetAlertReaderFromFile(t *testing.T, path string) *strings.Reader {
alertContentBytes, err := os.ReadFile(path)
require.NoError(t, err)
alerts := make([]*models.Alert, 0)
err = json.Unmarshal(alertContentBytes, &alerts)
require.NoError(t, err)
for _, alert := range alerts {
*alert.StartAt = time.Now().UTC().Format(time.RFC3339)
*alert.StopAt = time.Now().UTC().Format(time.RFC3339)
}
alertContent, err := json.Marshal(alerts)
require.NoError(t, err)
return strings.NewReader(string(alertContent))
}
func readDecisionsGetResp(t *testing.T, resp *httptest.ResponseRecorder) ([]*models.Decision, int) {
var response []*models.Decision
require.NotNil(t, resp)
err := json.Unmarshal(resp.Body.Bytes(), &response)
require.NoError(t, err)
return response, resp.Code
}
func readDecisionsErrorResp(t *testing.T, resp *httptest.ResponseRecorder) (map[string]string, int) {
var response map[string]string
require.NotNil(t, resp)
err := json.Unmarshal(resp.Body.Bytes(), &response)
require.NoError(t, err)
return response, resp.Code
}
func readDecisionsDeleteResp(t *testing.T, resp *httptest.ResponseRecorder) (*models.DeleteDecisionResponse, int) {
var response models.DeleteDecisionResponse
require.NotNil(t, resp)
err := json.Unmarshal(resp.Body.Bytes(), &response)
require.NoError(t, err)
return &response, resp.Code
}
func readDecisionsStreamResp(t *testing.T, resp *httptest.ResponseRecorder) (map[string][]*models.Decision, int) {
response := make(map[string][]*models.Decision)
require.NotNil(t, resp)
err := json.Unmarshal(resp.Body.Bytes(), &response)
require.NoError(t, err)
return response, resp.Code
}
func CreateTestMachine(t *testing.T, router *gin.Engine) string {
b, err := json.Marshal(MachineTest)
require.NoError(t, err)
body := string(b)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodPost, "/v1/watchers", strings.NewReader(body))
req.Header.Set("User-Agent", UserAgent)
router.ServeHTTP(w, req)
return body
}
func CreateTestBouncer(t *testing.T, config *csconfig.DatabaseCfg) string {
dbClient, err := database.NewClient(config)
require.NoError(t, err)
apiKey, err := middlewares.GenerateAPIKey(keyLength)
require.NoError(t, err)
_, err = dbClient.CreateBouncer("test", "127.0.0.1", middlewares.HashSHA512(apiKey), types.ApiKeyAuthType)
require.NoError(t, err)
return apiKey
}
func TestWithWrongDBConfig(t *testing.T) {
config := LoadTestConfig(t)
config.API.Server.DbConfig.Type = "test"
apiServer, err := NewServer(config.API.Server)
cstest.RequireErrorContains(t, err, "unable to init database client: unknown database type 'test'")
assert.Nil(t, apiServer)
}
func TestWithWrongFlushConfig(t *testing.T) {
config := LoadTestConfig(t)
maxItems := -1
config.API.Server.DbConfig.Flush.MaxItems = &maxItems
apiServer, err := NewServer(config.API.Server)
cstest.RequireErrorContains(t, err, "max_items can't be zero or negative number")
assert.Nil(t, apiServer)
}
func TestUnknownPath(t *testing.T) {
router, _ := NewAPITest(t)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/test", nil)
req.Header.Set("User-Agent", UserAgent)
router.ServeHTTP(w, req)
assert.Equal(t, 404, w.Code)
}
/*
ListenURI string `yaml:"listen_uri,omitempty"` //127.0.0.1:8080
TLS *TLSCfg `yaml:"tls"`
DbConfig *DatabaseCfg `yaml:"-"`
LogDir string `yaml:"-"`
LogMedia string `yaml:"-"`
OnlineClient *OnlineApiClientCfg `yaml:"online_client"`
ProfilesPath string `yaml:"profiles_path,omitempty"`
Profiles []*ProfileCfg `yaml:"-"`
LogLevel *log.Level `yaml:"log_level"`
UseForwardedForHeaders bool `yaml:"use_forwarded_for_headers,omitempty"`
*/
func TestLoggingDebugToFileConfig(t *testing.T) {
/*declare settings*/
maxAge := "1h"
flushConfig := csconfig.FlushDBCfg{
MaxAge: &maxAge,
}
tempDir, _ := os.MkdirTemp("", "crowdsec_tests")
t.Cleanup(func() { os.RemoveAll(tempDir) })
dbconfig := csconfig.DatabaseCfg{
Type: "sqlite",
DbPath: filepath.Join(tempDir, "ent"),
Flush: &flushConfig,
}
cfg := csconfig.LocalApiServerCfg{
ListenURI: "127.0.0.1:8080",
LogMedia: "file",
LogDir: tempDir,
DbConfig: &dbconfig,
}
expectedFile := fmt.Sprintf("%s/crowdsec_api.log", tempDir)
expectedLines := []string{"/test42"}
cfg.LogLevel = ptr.Of(log.DebugLevel)
// Configure logging
err := types.SetDefaultLoggerConfig(cfg.LogMedia, cfg.LogDir, *cfg.LogLevel, cfg.LogMaxSize, cfg.LogMaxFiles, cfg.LogMaxAge, cfg.CompressLogs, false)
require.NoError(t, err)
api, err := NewServer(&cfg)
require.NoError(t, err)
require.NotNil(t, api)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/test42", nil)
req.Header.Set("User-Agent", UserAgent)
api.router.ServeHTTP(w, req)
assert.Equal(t, 404, w.Code)
//wait for the request to happen
time.Sleep(500 * time.Millisecond)
//check file content
data, err := os.ReadFile(expectedFile)
require.NoError(t, err)
for _, expectedStr := range expectedLines {
assert.Contains(t, string(data), expectedStr)
}
}
func TestLoggingErrorToFileConfig(t *testing.T) {
/*declare settings*/
maxAge := "1h"
flushConfig := csconfig.FlushDBCfg{
MaxAge: &maxAge,
}
tempDir, _ := os.MkdirTemp("", "crowdsec_tests")
t.Cleanup(func() { os.RemoveAll(tempDir) })
dbconfig := csconfig.DatabaseCfg{
Type: "sqlite",
DbPath: filepath.Join(tempDir, "ent"),
Flush: &flushConfig,
}
cfg := csconfig.LocalApiServerCfg{
ListenURI: "127.0.0.1:8080",
LogMedia: "file",
LogDir: tempDir,
DbConfig: &dbconfig,
}
expectedFile := fmt.Sprintf("%s/crowdsec_api.log", tempDir)
cfg.LogLevel = ptr.Of(log.ErrorLevel)
// Configure logging
err := types.SetDefaultLoggerConfig(cfg.LogMedia, cfg.LogDir, *cfg.LogLevel, cfg.LogMaxSize, cfg.LogMaxFiles, cfg.LogMaxAge, cfg.CompressLogs, false)
require.NoError(t, err)
api, err := NewServer(&cfg)
require.NoError(t, err)
require.NotNil(t, api)
w := httptest.NewRecorder()
req, _ := http.NewRequest(http.MethodGet, "/test42", nil)
req.Header.Set("User-Agent", UserAgent)
api.router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
//wait for the request to happen
time.Sleep(500 * time.Millisecond)
//check file content
x, err := os.ReadFile(expectedFile)
if err == nil {
require.Empty(t, x)
}
os.Remove("./crowdsec.log")
os.Remove(expectedFile)
}