fix: change Caddy config reload from async to sync for deterministic applied state
This commit is contained in:
@@ -177,18 +177,18 @@ func (h *SettingsHandler) UpdateSetting(c *gin.Context) {
|
||||
h.Cerberus.InvalidateCache()
|
||||
}
|
||||
|
||||
// Trigger async Caddy config reload (doesn't block HTTP response)
|
||||
// Trigger sync Caddy config reload so callers can rely on deterministic applied state
|
||||
if h.CaddyManager != nil {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := h.CaddyManager.ApplyConfig(ctx); err != nil {
|
||||
logger.Log().WithError(err).Warn("Failed to reload Caddy config after security setting change")
|
||||
} else {
|
||||
logger.Log().WithField("setting_key", req.Key).Info("Caddy config reloaded after security setting change")
|
||||
}
|
||||
}()
|
||||
if err := h.CaddyManager.ApplyConfig(ctx); err != nil {
|
||||
logger.Log().WithError(err).Warn("Failed to reload Caddy config after security setting change")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reload configuration"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log().WithField("setting_key", req.Key).Info("Caddy config reloaded after security setting change")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,18 +283,18 @@ func (h *SettingsHandler) PatchConfig(c *gin.Context) {
|
||||
h.Cerberus.InvalidateCache()
|
||||
}
|
||||
|
||||
// Trigger async Caddy config reload
|
||||
// Trigger sync Caddy config reload so callers can rely on deterministic applied state
|
||||
if h.CaddyManager != nil {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := h.CaddyManager.ApplyConfig(ctx); err != nil {
|
||||
logger.Log().WithError(err).Warn("Failed to reload Caddy config after security settings change")
|
||||
} else {
|
||||
logger.Log().Info("Caddy config reloaded after security settings change")
|
||||
}
|
||||
}()
|
||||
if err := h.CaddyManager.ApplyConfig(ctx); err != nil {
|
||||
logger.Log().WithError(err).Warn("Failed to reload Caddy config after security settings change")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reload configuration"})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log().Info("Caddy config reloaded after security settings change")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package handlers_test
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -22,6 +23,19 @@ import (
|
||||
"github.com/Wikid82/charon/backend/internal/models"
|
||||
)
|
||||
|
||||
type mockCaddyConfigManager struct {
|
||||
applyFunc func(context.Context) error
|
||||
calls int
|
||||
}
|
||||
|
||||
func (m *mockCaddyConfigManager) ApplyConfig(ctx context.Context) error {
|
||||
m.calls++
|
||||
if m.applyFunc != nil {
|
||||
return m.applyFunc(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startTestSMTPServer(t *testing.T) (host string, port int) {
|
||||
t.Helper()
|
||||
|
||||
@@ -295,6 +309,56 @@ func TestSettingsHandler_UpdateSetting_EnablesCerberusWhenACLEnabled(t *testing.
|
||||
assert.True(t, cfg.Enabled)
|
||||
}
|
||||
|
||||
func TestSettingsHandler_UpdateSetting_SecurityKeyAppliesConfigSynchronously(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupSettingsTestDB(t)
|
||||
|
||||
mgr := &mockCaddyConfigManager{}
|
||||
handler := handlers.NewSettingsHandlerWithDeps(db, mgr, nil, nil, "")
|
||||
router := newAdminRouter()
|
||||
router.POST("/settings", handler.UpdateSetting)
|
||||
|
||||
payload := map[string]string{
|
||||
"key": "security.waf.enabled",
|
||||
"value": "true",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/settings", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, 1, mgr.calls)
|
||||
}
|
||||
|
||||
func TestSettingsHandler_UpdateSetting_SecurityKeyApplyFailureReturnsError(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupSettingsTestDB(t)
|
||||
|
||||
mgr := &mockCaddyConfigManager{applyFunc: func(context.Context) error {
|
||||
return fmt.Errorf("apply failed")
|
||||
}}
|
||||
handler := handlers.NewSettingsHandlerWithDeps(db, mgr, nil, nil, "")
|
||||
router := newAdminRouter()
|
||||
router.POST("/settings", handler.UpdateSetting)
|
||||
|
||||
payload := map[string]string{
|
||||
"key": "security.waf.enabled",
|
||||
"value": "true",
|
||||
}
|
||||
body, _ := json.Marshal(payload)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("POST", "/settings", bytes.NewBuffer(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||
assert.Equal(t, 1, mgr.calls)
|
||||
}
|
||||
|
||||
func TestSettingsHandler_PatchConfig_SyncsAdminWhitelist(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := setupSettingsTestDB(t)
|
||||
|
||||
Reference in New Issue
Block a user