chore: clean .gitignore cache
This commit is contained in:
@@ -1,727 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
|
||||
"github.com/Wikid82/charon/backend/internal/config"
|
||||
"github.com/Wikid82/charon/backend/internal/models"
|
||||
)
|
||||
|
||||
// Tests for GetWAFExclusions handler
|
||||
func TestSecurityHandler_GetWAFExclusions_Empty(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.GET("/security/waf/exclusions", handler.GetWAFExclusions)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
exclusions := resp["exclusions"].([]any)
|
||||
assert.Len(t, exclusions, 0)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_GetWAFExclusions_WithExclusions(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
// Create config with exclusions
|
||||
exclusionsJSON := `[{"rule_id":942100,"description":"SQL Injection rule"},{"rule_id":941100,"target":"ARGS:password"}]`
|
||||
cfg := models.SecurityConfig{Name: "default", WAFExclusions: exclusionsJSON}
|
||||
db.Create(&cfg)
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.GET("/security/waf/exclusions", handler.GetWAFExclusions)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
exclusions := resp["exclusions"].([]any)
|
||||
assert.Len(t, exclusions, 2)
|
||||
|
||||
// Verify first exclusion
|
||||
first := exclusions[0].(map[string]any)
|
||||
assert.Equal(t, float64(942100), first["rule_id"])
|
||||
assert.Equal(t, "SQL Injection rule", first["description"])
|
||||
}
|
||||
|
||||
func TestSecurityHandler_GetWAFExclusions_InvalidJSON(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
// Create config with invalid JSON
|
||||
cfg := models.SecurityConfig{Name: "default", WAFExclusions: "invalid json"}
|
||||
db.Create(&cfg)
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.GET("/security/waf/exclusions", handler.GetWAFExclusions)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
// Should return empty array on parse failure
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
exclusions := resp["exclusions"].([]any)
|
||||
assert.Len(t, exclusions, 0)
|
||||
}
|
||||
|
||||
// Tests for AddWAFExclusion handler
|
||||
func TestSecurityHandler_AddWAFExclusion_Success(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.SecurityAudit{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
|
||||
payload := map[string]any{
|
||||
"rule_id": 942100,
|
||||
"description": "SQL Injection false positive",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
exclusion := resp["exclusion"].(map[string]any)
|
||||
assert.Equal(t, float64(942100), exclusion["rule_id"])
|
||||
assert.Equal(t, "SQL Injection false positive", exclusion["description"])
|
||||
}
|
||||
|
||||
func TestSecurityHandler_AddWAFExclusion_WithTarget(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.SecurityAudit{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
|
||||
payload := map[string]any{
|
||||
"rule_id": 942100,
|
||||
"target": "ARGS:password",
|
||||
"description": "Skip password field for SQL injection",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp map[string]any
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
|
||||
exclusion := resp["exclusion"].(map[string]any)
|
||||
assert.Equal(t, "ARGS:password", exclusion["target"])
|
||||
}
|
||||
|
||||
func TestSecurityHandler_AddWAFExclusion_ToExistingConfig(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.SecurityAudit{}))
|
||||
|
||||
// Create config with existing exclusion
|
||||
existingExclusions := `[{"rule_id":941100,"description":"XSS rule"}]`
|
||||
cfg := models.SecurityConfig{Name: "default", WAFExclusions: existingExclusions}
|
||||
db.Create(&cfg)
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
router.GET("/security/waf/exclusions", handler.GetWAFExclusions)
|
||||
|
||||
// Add new exclusion
|
||||
payload := map[string]any{
|
||||
"rule_id": 942100,
|
||||
"description": "SQL Injection rule",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Verify both exclusions exist
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
exclusions := resp["exclusions"].([]any)
|
||||
assert.Len(t, exclusions, 2)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_AddWAFExclusion_Duplicate(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.SecurityAudit{}))
|
||||
|
||||
// Create config with existing exclusion
|
||||
existingExclusions := `[{"rule_id":942100,"description":"SQL Injection rule"}]`
|
||||
cfg := models.SecurityConfig{Name: "default", WAFExclusions: existingExclusions}
|
||||
db.Create(&cfg)
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
|
||||
// Try to add duplicate
|
||||
payload := map[string]any{
|
||||
"rule_id": 942100,
|
||||
"description": "Another description",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusConflict, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_AddWAFExclusion_DuplicateWithDifferentTarget(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.SecurityAudit{}))
|
||||
|
||||
// Create config with existing exclusion (no target)
|
||||
existingExclusions := `[{"rule_id":942100}]`
|
||||
cfg := models.SecurityConfig{Name: "default", WAFExclusions: existingExclusions}
|
||||
db.Create(&cfg)
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
|
||||
// Add same rule_id with different target - should succeed
|
||||
payload := map[string]any{
|
||||
"rule_id": 942100,
|
||||
"target": "ARGS:password",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_AddWAFExclusion_MissingRuleID(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
|
||||
payload := map[string]any{
|
||||
"description": "Missing rule_id",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_AddWAFExclusion_InvalidRuleID(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
|
||||
// Zero rule_id
|
||||
payload := map[string]any{
|
||||
"rule_id": 0,
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_AddWAFExclusion_NegativeRuleID(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
|
||||
payload := map[string]any{
|
||||
"rule_id": -1,
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_AddWAFExclusion_InvalidPayload(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/security/waf/exclusions", strings.NewReader("invalid json"))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
// Tests for DeleteWAFExclusion handler
|
||||
func TestSecurityHandler_DeleteWAFExclusion_Success(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.SecurityAudit{}))
|
||||
|
||||
// Create config with exclusions
|
||||
exclusionsJSON := `[{"rule_id":942100},{"rule_id":941100}]`
|
||||
cfg := models.SecurityConfig{Name: "default", WAFExclusions: exclusionsJSON}
|
||||
db.Create(&cfg)
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.DELETE("/security/waf/exclusions/:rule_id", handler.DeleteWAFExclusion)
|
||||
router.GET("/security/waf/exclusions", handler.GetWAFExclusions)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/security/waf/exclusions/942100", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
assert.True(t, resp["deleted"].(bool))
|
||||
|
||||
// Verify only one exclusion remains
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
exclusions := resp["exclusions"].([]any)
|
||||
assert.Len(t, exclusions, 1)
|
||||
first := exclusions[0].(map[string]any)
|
||||
assert.Equal(t, float64(941100), first["rule_id"])
|
||||
}
|
||||
|
||||
func TestSecurityHandler_DeleteWAFExclusion_WithTarget(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.SecurityAudit{}))
|
||||
|
||||
// Create config with targeted exclusion
|
||||
exclusionsJSON := `[{"rule_id":942100,"target":"ARGS:password"},{"rule_id":942100}]`
|
||||
cfg := models.SecurityConfig{Name: "default", WAFExclusions: exclusionsJSON}
|
||||
db.Create(&cfg)
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.DELETE("/security/waf/exclusions/:rule_id", handler.DeleteWAFExclusion)
|
||||
router.GET("/security/waf/exclusions", handler.GetWAFExclusions)
|
||||
|
||||
// Delete exclusion with target
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/security/waf/exclusions/942100?target=ARGS:password", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
|
||||
// Verify only the non-targeted exclusion remains
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
var resp map[string]any
|
||||
_ = json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
exclusions := resp["exclusions"].([]any)
|
||||
assert.Len(t, exclusions, 1)
|
||||
first := exclusions[0].(map[string]any)
|
||||
assert.Equal(t, float64(942100), first["rule_id"])
|
||||
assert.Empty(t, first["target"])
|
||||
}
|
||||
|
||||
func TestSecurityHandler_DeleteWAFExclusion_NotFound(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
// Create config with exclusions
|
||||
exclusionsJSON := `[{"rule_id":942100}]`
|
||||
cfg := models.SecurityConfig{Name: "default", WAFExclusions: exclusionsJSON}
|
||||
db.Create(&cfg)
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.DELETE("/security/waf/exclusions/:rule_id", handler.DeleteWAFExclusion)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/security/waf/exclusions/999999", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_DeleteWAFExclusion_NoConfig(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.DELETE("/security/waf/exclusions/:rule_id", handler.DeleteWAFExclusion)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/security/waf/exclusions/942100", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_DeleteWAFExclusion_InvalidRuleID(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.DELETE("/security/waf/exclusions/:rule_id", handler.DeleteWAFExclusion)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/security/waf/exclusions/invalid", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_DeleteWAFExclusion_ZeroRuleID(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.DELETE("/security/waf/exclusions/:rule_id", handler.DeleteWAFExclusion)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/security/waf/exclusions/0", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
func TestSecurityHandler_DeleteWAFExclusion_NegativeRuleID(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.DELETE("/security/waf/exclusions/:rule_id", handler.DeleteWAFExclusion)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("DELETE", "/security/waf/exclusions/-1", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||
}
|
||||
|
||||
// Integration test: Full WAF exclusion workflow
|
||||
func TestSecurityHandler_WAFExclusion_FullWorkflow(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
// Create a temporary file-based SQLite database for complete isolation
|
||||
// This avoids all the shared memory locking issues with in-memory databases
|
||||
tmpDir := t.TempDir()
|
||||
dbPath := filepath.Join(tmpDir, fmt.Sprintf("waf_test_%d.db", time.Now().UnixNano()))
|
||||
dsn := fmt.Sprintf("%s?_journal_mode=WAL&_busy_timeout=10000", dbPath)
|
||||
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
require.NoError(t, err, "failed to open test database")
|
||||
|
||||
// Ensure cleanup
|
||||
t.Cleanup(func() {
|
||||
sqlDB, _ := db.DB()
|
||||
if sqlDB != nil {
|
||||
sqlDB.Close()
|
||||
}
|
||||
os.Remove(dbPath)
|
||||
os.Remove(dbPath + "-wal")
|
||||
os.Remove(dbPath + "-shm")
|
||||
})
|
||||
|
||||
// Migrate the required models
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}, &models.SecurityAudit{}))
|
||||
|
||||
handler := NewSecurityHandler(config.SecurityConfig{}, db, nil)
|
||||
router := gin.New()
|
||||
router.GET("/security/waf/exclusions", handler.GetWAFExclusions)
|
||||
router.POST("/security/waf/exclusions", handler.AddWAFExclusion)
|
||||
router.DELETE("/security/waf/exclusions/:rule_id", handler.DeleteWAFExclusion)
|
||||
|
||||
// Step 1: Start with empty exclusions
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code, "Step 1: GET should return 200")
|
||||
var resp map[string]any
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp), "Step 1: response should be valid JSON")
|
||||
exclusions, ok := resp["exclusions"].([]any)
|
||||
require.True(t, ok, "Step 1: exclusions should be an array")
|
||||
require.Len(t, exclusions, 0, "Step 1: should start with 0 exclusions")
|
||||
|
||||
// Step 2: Add first exclusion (full rule removal)
|
||||
payload := map[string]any{
|
||||
"rule_id": 942100,
|
||||
"description": "SQL Injection false positive",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code, "Step 2: POST first exclusion should return 200, got: %s", w.Body.String())
|
||||
|
||||
// Step 3: Add second exclusion (targeted)
|
||||
payload = map[string]any{
|
||||
"rule_id": 941100,
|
||||
"target": "ARGS:content",
|
||||
"description": "XSS false positive in content field",
|
||||
}
|
||||
body, _ = json.Marshal(payload)
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("POST", "/security/waf/exclusions", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code, "Step 3: POST second exclusion should return 200, got: %s", w.Body.String())
|
||||
|
||||
// Step 4: Verify both exclusions exist
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code, "Step 4: GET should return 200")
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp), "Step 4: response should be valid JSON")
|
||||
exclusions, ok = resp["exclusions"].([]any)
|
||||
require.True(t, ok, "Step 4: exclusions should be an array")
|
||||
require.Len(t, exclusions, 2, "Step 4: should have 2 exclusions after adding 2")
|
||||
|
||||
// Step 5: Delete first exclusion
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("DELETE", "/security/waf/exclusions/942100", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code, "Step 5: DELETE should return 200, got: %s", w.Body.String())
|
||||
|
||||
// Step 6: Verify only second exclusion remains
|
||||
w = httptest.NewRecorder()
|
||||
req, _ = http.NewRequest("GET", "/security/waf/exclusions", http.NoBody)
|
||||
router.ServeHTTP(w, req)
|
||||
require.Equal(t, http.StatusOK, w.Code, "Step 6: GET should return 200")
|
||||
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp), "Step 6: response should be valid JSON")
|
||||
exclusions, ok = resp["exclusions"].([]any)
|
||||
require.True(t, ok, "Step 6: exclusions should be an array")
|
||||
require.Len(t, exclusions, 1, "Step 6: should have 1 exclusion after deleting 1")
|
||||
first := exclusions[0].(map[string]any)
|
||||
assert.Equal(t, float64(941100), first["rule_id"])
|
||||
assert.Equal(t, "ARGS:content", first["target"])
|
||||
}
|
||||
|
||||
// Test WAFDisabled field on ProxyHost
|
||||
func TestProxyHost_WAFDisabled_DefaultFalse(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}))
|
||||
|
||||
host := models.ProxyHost{
|
||||
UUID: "test-uuid",
|
||||
DomainNames: "example.com",
|
||||
ForwardHost: "backend",
|
||||
ForwardPort: 8080,
|
||||
Enabled: true,
|
||||
}
|
||||
db.Create(&host)
|
||||
|
||||
var retrieved models.ProxyHost
|
||||
db.First(&retrieved, host.ID)
|
||||
|
||||
assert.False(t, retrieved.WAFDisabled, "WAFDisabled should default to false")
|
||||
}
|
||||
|
||||
func TestProxyHost_WAFDisabled_SetTrue(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}))
|
||||
|
||||
host := models.ProxyHost{
|
||||
UUID: "test-uuid",
|
||||
DomainNames: "example.com",
|
||||
ForwardHost: "backend",
|
||||
ForwardPort: 8080,
|
||||
Enabled: true,
|
||||
WAFDisabled: true,
|
||||
}
|
||||
db.Create(&host)
|
||||
|
||||
var retrieved models.ProxyHost
|
||||
db.First(&retrieved, host.ID)
|
||||
|
||||
assert.True(t, retrieved.WAFDisabled, "WAFDisabled should be true when set")
|
||||
}
|
||||
|
||||
// Test WAFParanoiaLevel field on SecurityConfig
|
||||
func TestSecurityConfig_WAFParanoiaLevel_Default(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
cfg := models.SecurityConfig{
|
||||
Name: "default",
|
||||
WAFMode: "block",
|
||||
}
|
||||
db.Create(&cfg)
|
||||
|
||||
var retrieved models.SecurityConfig
|
||||
db.First(&retrieved, cfg.ID)
|
||||
|
||||
// GORM default is 1
|
||||
assert.Equal(t, 1, retrieved.WAFParanoiaLevel, "WAFParanoiaLevel should default to 1")
|
||||
}
|
||||
|
||||
func TestSecurityConfig_WAFParanoiaLevel_CustomValue(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
cfg := models.SecurityConfig{
|
||||
Name: "default",
|
||||
WAFMode: "block",
|
||||
WAFParanoiaLevel: 3,
|
||||
}
|
||||
db.Create(&cfg)
|
||||
|
||||
var retrieved models.SecurityConfig
|
||||
db.First(&retrieved, cfg.ID)
|
||||
|
||||
assert.Equal(t, 3, retrieved.WAFParanoiaLevel, "WAFParanoiaLevel should be 3")
|
||||
}
|
||||
|
||||
// Test WAFExclusions field on SecurityConfig
|
||||
func TestSecurityConfig_WAFExclusions_Empty(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
cfg := models.SecurityConfig{
|
||||
Name: "default",
|
||||
WAFMode: "block",
|
||||
}
|
||||
db.Create(&cfg)
|
||||
|
||||
var retrieved models.SecurityConfig
|
||||
db.First(&retrieved, cfg.ID)
|
||||
|
||||
assert.Empty(t, retrieved.WAFExclusions, "WAFExclusions should be empty by default")
|
||||
}
|
||||
|
||||
func TestSecurityConfig_WAFExclusions_JSONArray(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupTestDB(t)
|
||||
require.NoError(t, db.AutoMigrate(&models.SecurityConfig{}))
|
||||
|
||||
exclusions := `[{"rule_id":942100,"target":"ARGS:password","description":"Skip password field"}]`
|
||||
cfg := models.SecurityConfig{
|
||||
Name: "default",
|
||||
WAFMode: "block",
|
||||
WAFExclusions: exclusions,
|
||||
}
|
||||
db.Create(&cfg)
|
||||
|
||||
var retrieved models.SecurityConfig
|
||||
db.First(&retrieved, cfg.ID)
|
||||
|
||||
assert.Equal(t, exclusions, retrieved.WAFExclusions)
|
||||
|
||||
// Verify it can be parsed
|
||||
var parsed []map[string]any
|
||||
err := json.Unmarshal([]byte(retrieved.WAFExclusions), &parsed)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, parsed, 1)
|
||||
assert.Equal(t, float64(942100), parsed[0]["rule_id"])
|
||||
}
|
||||
Reference in New Issue
Block a user