- Implemented `useManualChallenge`, `useChallengePoll`, and `useManualChallengeMutations` hooks for managing manual DNS challenges. - Created tests for the `useManualChallenge` hooks to ensure correct fetching and mutation behavior. - Added `ManualDNSChallenge` component for displaying challenge details and actions. - Developed end-to-end tests for the Manual DNS Provider feature, covering provider selection, challenge UI, and accessibility compliance. - Included error handling tests for verification failures and network errors.
536 lines
18 KiB
Go
536 lines
18 KiB
Go
package caddy
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Wikid82/charon/backend/internal/config"
|
|
"github.com/Wikid82/charon/backend/internal/models"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestManager_ApplyConfig(t *testing.T) {
|
|
// Mock Caddy Admin API
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == "POST" {
|
|
// Verify payload
|
|
var config Config
|
|
err := json.NewDecoder(r.Body).Decode(&config)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Setup Manager
|
|
tmpDir := t.TempDir()
|
|
client := newTestClient(t, caddyServer.URL)
|
|
manager := NewManager(client, db, tmpDir, "", false, config.SecurityConfig{})
|
|
|
|
// Create a host
|
|
host := models.ProxyHost{
|
|
DomainNames: "example.com",
|
|
ForwardHost: "127.0.0.1",
|
|
ForwardPort: 8080,
|
|
}
|
|
db.Create(&host)
|
|
|
|
// Apply Config
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.NoError(t, err)
|
|
|
|
// Verify config was saved to DB
|
|
var caddyConfig models.CaddyConfig
|
|
err = db.First(&caddyConfig).Error
|
|
assert.NoError(t, err)
|
|
assert.True(t, caddyConfig.Success)
|
|
}
|
|
|
|
func TestManager_ApplyConfig_Failure(t *testing.T) {
|
|
// Mock Caddy Admin API to fail
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Setup Manager
|
|
tmpDir := t.TempDir()
|
|
client := newTestClient(t, caddyServer.URL)
|
|
manager := NewManager(client, db, tmpDir, "", false, config.SecurityConfig{})
|
|
|
|
// Create a host
|
|
host := models.ProxyHost{
|
|
DomainNames: "example.com",
|
|
ForwardHost: "127.0.0.1",
|
|
ForwardPort: 8080,
|
|
}
|
|
require.NoError(t, db.Create(&host).Error)
|
|
|
|
// Apply Config - should fail
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "apply failed")
|
|
|
|
// Verify failure was recorded
|
|
var caddyConfig models.CaddyConfig
|
|
err = db.First(&caddyConfig).Error
|
|
assert.NoError(t, err)
|
|
assert.False(t, caddyConfig.Success)
|
|
assert.NotEmpty(t, caddyConfig.ErrorMsg)
|
|
}
|
|
|
|
func TestManager_Ping(t *testing.T) {
|
|
// Mock Caddy Admin API
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/config/" && r.Method == "GET" {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
client := newTestClient(t, caddyServer.URL)
|
|
manager := NewManager(client, nil, "", "", false, config.SecurityConfig{})
|
|
|
|
err := manager.Ping(context.Background())
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestManager_GetCurrentConfig(t *testing.T) {
|
|
// Mock Caddy Admin API
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/config/" && r.Method == "GET" {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"apps": {"http": {}}}`))
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
client := newTestClient(t, caddyServer.URL)
|
|
manager := NewManager(client, nil, "", "", false, config.SecurityConfig{})
|
|
|
|
cfg, err := manager.GetCurrentConfig(context.Background())
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, cfg)
|
|
assert.NotNil(t, cfg.Apps)
|
|
assert.NotNil(t, cfg.Apps.HTTP)
|
|
}
|
|
|
|
func TestManager_RotateSnapshots(t *testing.T) {
|
|
// Setup Manager
|
|
tmpDir := t.TempDir()
|
|
|
|
// Mock Caddy Admin API (Success)
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
client := newTestClient(t, caddyServer.URL)
|
|
manager := NewManager(client, db, tmpDir, "", false, config.SecurityConfig{})
|
|
|
|
// Create 15 dummy config files
|
|
for i := 0; i < 15; i++ {
|
|
// Use past timestamps
|
|
ts := time.Now().Add(-time.Duration(i+1) * time.Minute).Unix()
|
|
fname := fmt.Sprintf("config-%d.json", ts)
|
|
f, _ := os.Create(filepath.Join(tmpDir, fname))
|
|
_ = f.Close()
|
|
}
|
|
|
|
// Call ApplyConfig once
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.NoError(t, err)
|
|
|
|
// Check number of files
|
|
files, _ := os.ReadDir(tmpDir)
|
|
|
|
// Count files matching config-*.json
|
|
count := 0
|
|
for _, f := range files {
|
|
if filepath.Ext(f.Name()) == ".json" {
|
|
count++
|
|
}
|
|
}
|
|
// Should be 10 (kept)
|
|
assert.Equal(t, 10, count)
|
|
}
|
|
|
|
func TestManager_Rollback_Success(t *testing.T) {
|
|
// Mock Caddy Admin API
|
|
// First call succeeds (initial setup), second call fails (bad config), third call succeeds (rollback)
|
|
callCount := 0
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
callCount++
|
|
if r.URL.Path == "/load" && r.Method == "POST" {
|
|
if callCount == 2 {
|
|
w.WriteHeader(http.StatusInternalServerError) // Fail the second apply
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Setup Manager
|
|
tmpDir := t.TempDir()
|
|
client := newTestClient(t, caddyServer.URL)
|
|
manager := NewManager(client, db, tmpDir, "", false, config.SecurityConfig{})
|
|
|
|
// 1. Apply valid config (creates snapshot)
|
|
host1 := models.ProxyHost{
|
|
UUID: "uuid-1",
|
|
DomainNames: "example.com",
|
|
ForwardHost: "127.0.0.1",
|
|
ForwardPort: 8080,
|
|
}
|
|
db.Create(&host1)
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.NoError(t, err)
|
|
|
|
// Verify snapshot exists
|
|
snapshots, _ := manager.listSnapshots()
|
|
assert.Len(t, snapshots, 1)
|
|
|
|
// Sleep to ensure different timestamp for next snapshot
|
|
time.Sleep(1100 * time.Millisecond)
|
|
|
|
// 2. Apply another config (will fail at Caddy level)
|
|
host2 := models.ProxyHost{
|
|
UUID: "uuid-2",
|
|
DomainNames: "fail.com",
|
|
ForwardHost: "127.0.0.1",
|
|
ForwardPort: 8081,
|
|
}
|
|
db.Create(&host2)
|
|
|
|
// This should fail, trigger rollback, and succeed in rolling back
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "apply failed (rolled back)")
|
|
|
|
// Verify we still have 1 snapshot (the failed one was removed)
|
|
snapshots, _ = manager.listSnapshots()
|
|
assert.Len(t, snapshots, 1)
|
|
}
|
|
|
|
func TestManager_ApplyConfig_DBError(t *testing.T) {
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Setup Manager
|
|
tmpDir := t.TempDir()
|
|
client := NewClient("http://localhost")
|
|
manager := NewManager(client, db, tmpDir, "", false, config.SecurityConfig{})
|
|
|
|
// Close DB to force error
|
|
sqlDB, _ := db.DB()
|
|
_ = sqlDB.Close()
|
|
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "fetch proxy hosts")
|
|
}
|
|
|
|
func TestManager_ApplyConfig_ValidationError(t *testing.T) {
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Setup Manager with a file as configDir to force saveSnapshot error
|
|
tmpDir := t.TempDir()
|
|
configDir := filepath.Join(tmpDir, "config-file")
|
|
_ = os.WriteFile(configDir, []byte("not a dir"), 0o644)
|
|
|
|
client := NewClient("http://localhost")
|
|
manager := NewManager(client, db, configDir, "", false, config.SecurityConfig{})
|
|
|
|
host := models.ProxyHost{
|
|
DomainNames: "example.com",
|
|
ForwardHost: "127.0.0.1",
|
|
ForwardPort: 8080,
|
|
}
|
|
db.Create(&host)
|
|
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "save snapshot")
|
|
}
|
|
|
|
func TestManager_Rollback_Failure(t *testing.T) {
|
|
// Mock Caddy Admin API - Always Fail
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}))
|
|
|
|
// Setup Manager
|
|
tmpDir := t.TempDir()
|
|
client := newTestClient(t, caddyServer.URL)
|
|
manager := NewManager(client, db, tmpDir, "", false, config.SecurityConfig{})
|
|
|
|
// Create a dummy snapshot manually so rollback has something to try
|
|
_ = os.WriteFile(filepath.Join(tmpDir, "config-123.json"), []byte("{}"), 0o644)
|
|
|
|
// Apply Config - will fail, try rollback, rollback will fail
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.Error(t, err)
|
|
assert.Contains(t, err.Error(), "rollback also failed")
|
|
}
|
|
|
|
func TestComputeEffectiveFlags_DefaultsNoDB(t *testing.T) {
|
|
// No DB - rely on SecurityConfig defaults only
|
|
secCfg := config.SecurityConfig{CerberusEnabled: true, ACLMode: "enabled", WAFMode: "enabled", RateLimitMode: "enabled", CrowdSecMode: "local"}
|
|
manager := NewManager(nil, nil, "", "", false, secCfg)
|
|
|
|
cerb, acl, waf, rl, cs := manager.computeEffectiveFlags(context.Background())
|
|
require.True(t, cerb)
|
|
require.True(t, acl)
|
|
require.True(t, waf)
|
|
require.True(t, rl)
|
|
require.True(t, cs)
|
|
|
|
// If Cerberus disabled, all subcomponents must be disabled
|
|
secCfg.CerberusEnabled = false
|
|
manager = NewManager(nil, nil, "", "", false, secCfg)
|
|
cerb, acl, waf, rl, cs = manager.computeEffectiveFlags(context.Background())
|
|
require.False(t, cerb)
|
|
require.False(t, acl)
|
|
require.False(t, waf)
|
|
require.False(t, rl)
|
|
require.False(t, cs)
|
|
|
|
// Unknown/unrecognized CrowdSec mode should disable CrowdSec in computed flags
|
|
secCfg = config.SecurityConfig{CerberusEnabled: true, ACLMode: "enabled", WAFMode: "enabled", RateLimitMode: "enabled", CrowdSecMode: "unknown"}
|
|
manager = NewManager(nil, nil, "", "", false, secCfg)
|
|
cerb, acl, waf, rl, cs = manager.computeEffectiveFlags(context.Background())
|
|
require.True(t, cerb)
|
|
require.True(t, acl)
|
|
require.True(t, waf)
|
|
require.True(t, rl)
|
|
require.False(t, cs)
|
|
}
|
|
|
|
// Removed combined DB overrides test - replaced by smaller, focused DB tests
|
|
|
|
func TestComputeEffectiveFlags_DB_CerberusDisabled(t *testing.T) {
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.Setting{}))
|
|
|
|
secCfg := config.SecurityConfig{CerberusEnabled: true, ACLMode: "enabled", WAFMode: "enabled", RateLimitMode: "enabled", CrowdSecMode: "local"}
|
|
manager := NewManager(nil, db, "", "", false, secCfg)
|
|
|
|
// Set runtime override to disable cerberus
|
|
res := db.Create(&models.Setting{Key: "feature.cerberus.enabled", Value: "false"})
|
|
require.NoError(t, res.Error)
|
|
|
|
cerb, acl, waf, rl, cs := manager.computeEffectiveFlags(context.Background())
|
|
require.False(t, cerb)
|
|
require.False(t, acl)
|
|
require.False(t, waf)
|
|
require.False(t, rl)
|
|
require.False(t, cs)
|
|
}
|
|
|
|
// TestComputeEffectiveFlags_DB_ACLDisables: replaced by TestComputeEffectiveFlags_DB_ACLTrueAndFalse
|
|
// TestComputeEffectiveFlags_DB_ACLDisables: Replaced by focused tests TestComputeEffectiveFlags_DB_ACLTrueAndFalse
|
|
|
|
func TestComputeEffectiveFlags_DB_CrowdSecExternal(t *testing.T) {
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.Setting{}))
|
|
|
|
secCfg := config.SecurityConfig{CerberusEnabled: true, ACLMode: "enabled", WAFMode: "enabled", RateLimitMode: "enabled", CrowdSecMode: "local"}
|
|
manager := NewManager(nil, db, "", "", false, secCfg)
|
|
|
|
res := db.Create(&models.Setting{Key: "security.crowdsec.mode", Value: "unknown"})
|
|
require.NoError(t, res.Error)
|
|
|
|
_, _, _, _, cs := manager.computeEffectiveFlags(context.Background())
|
|
require.False(t, cs)
|
|
}
|
|
|
|
func TestComputeEffectiveFlags_DB_CrowdSecUnknown(t *testing.T) {
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.Setting{}))
|
|
|
|
secCfg := config.SecurityConfig{CerberusEnabled: true, ACLMode: "enabled", WAFMode: "enabled", RateLimitMode: "enabled", CrowdSecMode: "local"}
|
|
manager := NewManager(nil, db, "", "", false, secCfg)
|
|
|
|
res := db.Create(&models.Setting{Key: "security.crowdsec.mode", Value: "unknown"})
|
|
require.NoError(t, res.Error)
|
|
_, _, _, _, cs := manager.computeEffectiveFlags(context.Background())
|
|
require.False(t, cs)
|
|
}
|
|
|
|
func TestComputeEffectiveFlags_DB_CrowdSecLocal(t *testing.T) {
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.Setting{}))
|
|
|
|
secCfg := config.SecurityConfig{CerberusEnabled: true, ACLMode: "enabled", WAFMode: "enabled", RateLimitMode: "enabled", CrowdSecMode: "local"}
|
|
manager := NewManager(nil, db, "", "", false, secCfg)
|
|
|
|
res := db.Create(&models.Setting{Key: "security.crowdsec.mode", Value: "local"})
|
|
require.NoError(t, res.Error)
|
|
_, _, _, _, cs := manager.computeEffectiveFlags(context.Background())
|
|
require.True(t, cs)
|
|
}
|
|
|
|
func TestComputeEffectiveFlags_DB_ACLTrueAndFalse(t *testing.T) {
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.Setting{}))
|
|
|
|
secCfg := config.SecurityConfig{CerberusEnabled: true, ACLMode: "enabled"}
|
|
manager := NewManager(nil, db, "", "", false, secCfg)
|
|
|
|
// Set acl true
|
|
res := db.Create(&models.Setting{Key: "security.acl.enabled", Value: "true"})
|
|
require.NoError(t, res.Error)
|
|
_, acl, _, _, _ := manager.computeEffectiveFlags(context.Background())
|
|
require.True(t, acl)
|
|
|
|
// Set acl false
|
|
db.Where("key = ?", "security.acl.enabled").Delete(&models.Setting{})
|
|
res = db.Create(&models.Setting{Key: "security.acl.enabled", Value: "false"})
|
|
require.NoError(t, res.Error)
|
|
_, acl, _, _, _ = manager.computeEffectiveFlags(context.Background())
|
|
require.False(t, acl)
|
|
}
|
|
|
|
func TestComputeEffectiveFlags_DB_WAFMonitor(t *testing.T) {
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.Setting{}, &models.SecurityConfig{}))
|
|
|
|
secCfg := config.SecurityConfig{CerberusEnabled: true, WAFMode: "enabled"}
|
|
manager := NewManager(nil, db, "", "", false, secCfg)
|
|
|
|
// Set WAF mode to monitor
|
|
res := db.Create(&models.SecurityConfig{Name: "default", Enabled: true, WAFMode: "monitor"})
|
|
require.NoError(t, res.Error)
|
|
|
|
_, _, waf, _, _ := manager.computeEffectiveFlags(context.Background())
|
|
require.True(t, waf) // Should still be true (enabled)
|
|
}
|
|
|
|
func TestManager_ApplyConfig_WAFMonitor(t *testing.T) {
|
|
// Mock Caddy Admin API
|
|
caddyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/load" && r.Method == "POST" {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer caddyServer.Close()
|
|
|
|
// Setup DB
|
|
dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", t.Name())
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.AutoMigrate(&models.ProxyHost{}, &models.Location{}, &models.Setting{}, &models.CaddyConfig{}, &models.SSLCertificate{}, &models.SecurityConfig{}, &models.SecurityRuleSet{}, &models.SecurityDecision{}))
|
|
|
|
// Set WAF mode to monitor
|
|
db.Create(&models.SecurityConfig{Name: "default", Enabled: true, WAFMode: "monitor", AdminWhitelist: "127.0.0.1"})
|
|
|
|
// Create a ruleset
|
|
db.Create(&models.SecurityRuleSet{Name: "owasp-crs", Content: "SecRule REQUEST_URI \"@rx ^/admin\" \"id:101,phase:1,deny,status:403\""})
|
|
|
|
// Setup Manager
|
|
tmpDir := t.TempDir()
|
|
client := newTestClient(t, caddyServer.URL)
|
|
manager := NewManager(client, db, tmpDir, "", false, config.SecurityConfig{CerberusEnabled: true, WAFMode: "enabled"})
|
|
|
|
// Capture file writes to verify WAF mode injection
|
|
var writtenContent string
|
|
originalWriteFile := writeFileFunc
|
|
defer func() { writeFileFunc = originalWriteFile }()
|
|
writeFileFunc = func(filename string, data []byte, perm os.FileMode) error {
|
|
if strings.Contains(filename, "owasp-crs") && strings.HasSuffix(filename, ".conf") {
|
|
writtenContent = string(data)
|
|
}
|
|
return originalWriteFile(filename, data, perm)
|
|
}
|
|
|
|
// Create a host
|
|
host := models.ProxyHost{
|
|
DomainNames: "example.com",
|
|
ForwardHost: "127.0.0.1",
|
|
ForwardPort: 8080,
|
|
}
|
|
db.Create(&host)
|
|
|
|
// Apply Config
|
|
err = manager.ApplyConfig(context.Background())
|
|
assert.NoError(t, err)
|
|
|
|
// Verify that DetectionOnly was injected into the ruleset file
|
|
assert.Contains(t, writtenContent, "SecRuleEngine DetectionOnly")
|
|
assert.Contains(t, writtenContent, "SecRequestBodyAccess On")
|
|
}
|