Files
Charon/backend/internal/cerberus/cerberus_middleware_test.go
GitHub Actions 25082778c9 feat(cerberus): integrate Cerberus security features (WAF, ACLs, rate limiting, CrowdSec)
- Implement GeoIPService for IP-to-country lookups with comprehensive error handling.
- Add tests for GeoIPService covering various scenarios including invalid IPs and database loading.
- Extend AccessListService to handle GeoIP service integration, including graceful degradation when GeoIP service is unavailable.
- Introduce new tests for AccessListService to validate geo ACL behavior and country code parsing.
- Update SecurityService to include new fields for WAF configuration and enhance decision logging functionality.
- Add extensive tests for SecurityService covering rule set management and decision logging.
- Create a detailed Security Coverage QA Plan to ensure 100% code coverage for security-related functionality.
2025-12-12 17:56:30 +00:00

183 lines
5.6 KiB
Go

package cerberus_test
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/Wikid82/charon/backend/internal/cerberus"
"github.com/Wikid82/charon/backend/internal/config"
"github.com/Wikid82/charon/backend/internal/models"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
func setupDB(t *testing.T) *gorm.DB {
dsn := fmt.Sprintf("file:cerberus_middleware_test_%d?mode=memory&cache=shared", time.Now().UnixNano())
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
require.NoError(t, err)
require.NoError(t, db.AutoMigrate(&models.Setting{}, &models.AccessList{}, &models.AccessListRule{}))
return db
}
// TestMiddleware_WAFEnabledTracksMetrics tests that the cerberus middleware tracks WAF metrics
// when WAF mode is enabled. Note: Actual WAF blocking is handled by Coraza at the Caddy layer,
// not by this middleware. The middleware only provides metrics tracking and ACL enforcement.
func TestMiddleware_WAFEnabledTracksMetrics(t *testing.T) {
db := setupDB(t)
cfg := config.SecurityConfig{WAFMode: "block"}
c := cerberus.New(cfg, db)
// Setup gin context
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
// Create a request - this middleware no longer blocks on payload content
// because Coraza handles WAF at the Caddy layer
req := httptest.NewRequest(http.MethodGet, "/?q=test", http.NoBody)
req.RequestURI = "/?q=test"
ctx.Request = req
// call middleware
mw := c.Middleware()
mw(ctx)
// Middleware should pass through - it only tracks metrics now
// WAF blocking happens at Caddy/Coraza layer
require.False(t, ctx.IsAborted(), "cerberus middleware should not block - WAF is handled by Coraza at Caddy layer")
}
func TestMiddleware_ACLBlocksClientIP(t *testing.T) {
db := setupDB(t)
cfg := config.SecurityConfig{ACLMode: "enabled"}
// Create an ACL that blocks 8.8.8.8
ruleJSON := `[ { "cidr": "8.8.8.8/32", "description": "block" } ]`
acl := &models.AccessList{Name: "Block8", Type: "blacklist", IPRules: ruleJSON, Enabled: true}
require.NoError(t, db.Create(acl).Error)
c := cerberus.New(cfg, db)
// Setup gin context with remote address 8.8.8.8
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
req.RemoteAddr = "8.8.8.8:1234"
ctx.Request = req
mw := c.Middleware()
mw(ctx)
require.Equal(t, http.StatusForbidden, w.Code)
}
func TestMiddleware_ACLAllowsClientIP(t *testing.T) {
db := setupDB(t)
cfg := config.SecurityConfig{ACLMode: "enabled"}
// Create a whitelist that allows 8.8.8.8
ruleJSON := `[ { "cidr": "8.8.8.8/32", "description": "allow" } ]`
acl := &models.AccessList{Name: "Allow8", Type: "whitelist", IPRules: ruleJSON, Enabled: true}
require.NoError(t, db.Create(acl).Error)
c := cerberus.New(cfg, db)
// Setup gin context with remote address 8.8.8.8 (allowed)
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
req.RemoteAddr = "8.8.8.8:1234"
ctx.Request = req
mw := c.Middleware()
mw(ctx)
// Should not block - middleware did not abort
require.False(t, ctx.IsAborted())
}
func TestMiddleware_NotEnabledSkips(t *testing.T) {
db := setupDB(t)
// All modes disabled by default
cfg := config.SecurityConfig{}
c := cerberus.New(cfg, db)
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
req.RemoteAddr = "1.2.3.4:1234"
ctx.Request = req
mw := c.Middleware()
mw(ctx)
require.False(t, ctx.IsAborted())
}
func TestMiddleware_WAFPassesWithNoPayload(t *testing.T) {
db := setupDB(t)
cfg := config.SecurityConfig{WAFMode: "block"}
c := cerberus.New(cfg, db)
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
req := httptest.NewRequest(http.MethodGet, "/?q=safe", http.NoBody)
req.RequestURI = "/?q=safe"
ctx.Request = req
mw := c.Middleware()
mw(ctx)
require.False(t, ctx.IsAborted())
}
func TestMiddleware_WAFMonitorLogsButDoesNotBlock(t *testing.T) {
db := setupDB(t)
cfg := config.SecurityConfig{WAFMode: "monitor"}
c := cerberus.New(cfg, db)
// Test 1: suspicious payload in monitor mode should NOT block
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
req := httptest.NewRequest(http.MethodGet, "/?q=<script>", http.NoBody)
req.RequestURI = "/?q=<script>"
ctx.Request = req
mw := c.Middleware()
mw(ctx)
require.False(t, ctx.IsAborted(), "monitor mode should not block suspicious payload")
// Test 2: safe query in monitor mode should also pass
w2 := httptest.NewRecorder()
ctx2, _ := gin.CreateTestContext(w2)
req2 := httptest.NewRequest(http.MethodGet, "/?q=safe", http.NoBody)
req2.RequestURI = "/?q=safe"
ctx2.Request = req2
mw2 := c.Middleware()
mw2(ctx2)
require.False(t, ctx2.IsAborted(), "monitor mode should not block safe payload")
}
func TestMiddleware_ACLDisabledDoesNotBlock(t *testing.T) {
db := setupDB(t)
cfg := config.SecurityConfig{ACLMode: "enabled"}
// Create a disabled ACL that would block 8.8.8.8 (but it's disabled)
ruleJSON := `[ { "cidr": "8.8.8.8/32", "description": "block" } ]`
acl := &models.AccessList{Name: "Block8_Disabled", Type: "blacklist", IPRules: ruleJSON, Enabled: false}
require.NoError(t, db.Create(acl).Error)
c := cerberus.New(cfg, db)
// Setup gin context with remote address 8.8.8.8
w := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(w)
req := httptest.NewRequest(http.MethodGet, "/", http.NoBody)
req.RemoteAddr = "8.8.8.8:1234"
ctx.Request = req
mw := c.Middleware()
mw(ctx)
// Disabled ACL should not block
require.False(t, ctx.IsAborted())
}