diff --git a/backend/internal/api/handlers/security_handler.go b/backend/internal/api/handlers/security_handler.go index f1b345a8..a778e987 100644 --- a/backend/internal/api/handlers/security_handler.go +++ b/backend/internal/api/handlers/security_handler.go @@ -30,10 +30,8 @@ func (h *SecurityHandler) GetStatus(c *gin.Context) { // Check runtime setting override var settingKey = "security.cerberus.enabled" if h.db != nil { - var setting struct { - Value string - } - if err := h.db.Raw("SELECT value FROM settings WHERE key = ? LIMIT 1", settingKey).Scan(&setting).Error; err == nil { + var setting struct{ Value string } + if err := h.db.Raw("SELECT value FROM settings WHERE key = ? LIMIT 1", settingKey).Scan(&setting).Error; err == nil && setting.Value != "" { if strings.EqualFold(setting.Value, "true") { enabled = true } else { @@ -65,14 +63,20 @@ func (h *SecurityHandler) GetStatus(c *gin.Context) { // Allow runtime override for ACL enabled flag via settings table aclEnabled := h.cfg.ACLMode == "enabled" + aclEffective := aclEnabled && enabled if h.db != nil { var a struct{ Value string } - if err := h.db.Raw("SELECT value FROM settings WHERE key = ? LIMIT 1", "security.acl.enabled").Scan(&a).Error; err == nil { + if err := h.db.Raw("SELECT value FROM settings WHERE key = ? LIMIT 1", "security.acl.enabled").Scan(&a).Error; err == nil && a.Value != "" { if strings.EqualFold(a.Value, "true") { aclEnabled = true } else if strings.EqualFold(a.Value, "false") { aclEnabled = false } + + // If Cerberus is disabled, ACL should not be considered enabled even + // if the ACL setting is true. This keeps ACL tied to the Cerberus + // suite state in the UI and APIs. + aclEffective = aclEnabled && enabled } } @@ -93,7 +97,7 @@ func (h *SecurityHandler) GetStatus(c *gin.Context) { }, "acl": gin.H{ "mode": h.cfg.ACLMode, - "enabled": aclEnabled, + "enabled": aclEffective, }, }) } diff --git a/backend/internal/api/handlers/security_handler_clean_test.go b/backend/internal/api/handlers/security_handler_clean_test.go index 01a80944..9bb1f83b 100644 --- a/backend/internal/api/handlers/security_handler_clean_test.go +++ b/backend/internal/api/handlers/security_handler_clean_test.go @@ -52,6 +52,7 @@ func TestSecurityHandler_GetStatus_Clean(t *testing.T) { var response map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &response) assert.NoError(t, err) + // response body intentionally not printed in clean test assert.NotNil(t, response["cerberus"]) } @@ -89,8 +90,18 @@ func TestSecurityHandler_ACL_DBOverride(t *testing.T) { if err := db.Create(&models.Setting{Key: "security.acl.enabled", Value: "true"}).Error; err != nil { t.Fatalf("failed to insert setting: %v", err) } + // Confirm the DB write succeeded + var s models.Setting + if err := db.Where("key = ?", "security.acl.enabled").First(&s).Error; err != nil { + t.Fatalf("setting not found in DB: %v", err) + } + if s.Value != "true" { + t.Fatalf("unexpected value in DB for security.acl.enabled: %s", s.Value) + } + // DB write succeeded; no additional dump needed - cfg := config.SecurityConfig{ACLMode: "disabled"} + // Ensure Cerberus is enabled so ACL can be active + cfg := config.SecurityConfig{ACLMode: "disabled", CerberusEnabled: true} handler := NewSecurityHandler(cfg, db) router := gin.New() router.GET("/security/status", handler.GetStatus) @@ -107,6 +118,38 @@ func TestSecurityHandler_ACL_DBOverride(t *testing.T) { assert.Equal(t, true, acl["enabled"].(bool)) } +func TestSecurityHandler_ACL_DisabledWhenCerberusOff(t *testing.T) { + gin.SetMode(gin.TestMode) + + db := setupTestDB(t) + // set DB to enable ACL but disable Cerberus + if err := db.Create(&models.Setting{Key: "security.acl.enabled", Value: "true"}).Error; err != nil { + t.Fatalf("failed to insert setting: %v", err) + } + if err := db.Create(&models.Setting{Key: "security.cerberus.enabled", Value: "false"}).Error; err != nil { + t.Fatalf("failed to insert setting: %v", err) + } + + cfg := config.SecurityConfig{ACLMode: "enabled", CerberusEnabled: true} + handler := NewSecurityHandler(cfg, db) + router := gin.New() + router.GET("/security/status", handler.GetStatus) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/security/status", nil) + router.ServeHTTP(w, req) + assert.Equal(t, http.StatusOK, w.Code) + + var response map[string]interface{} + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + cerb := response["cerberus"].(map[string]interface{}) + assert.Equal(t, false, cerb["enabled"].(bool)) + acl := response["acl"].(map[string]interface{}) + // ACL must be false because Cerberus is disabled + assert.Equal(t, false, acl["enabled"].(bool)) +} + func TestSecurityHandler_CrowdSec_Mode_DBOverride(t *testing.T) { gin.SetMode(gin.TestMode)