- Implemented ImportSuccessModal to replace alert with a modal displaying import results and guidance. - Updated ImportCaddy to show the new modal with import summary and navigation options. - Created CertificateStatusCard to display certificate provisioning status on the dashboard. - Enhanced API types and hooks to support new features. - Added unit tests for ImportSuccessModal and CertificateStatusCard components. - Updated QA report to reflect the status of the new features and tests.
289 lines
9.5 KiB
Markdown
289 lines
9.5 KiB
Markdown
# Cerberus/Security Feature Issues - Diagnostic Plan
|
|
|
|
**Date:** December 12, 2025
|
|
**Status:** Analysis Complete
|
|
|
|
---
|
|
|
|
## Issue Summary
|
|
|
|
| # | Issue | Severity |
|
|
|---|-------|----------|
|
|
| 1 | Cerberus shows ON by default on first load (should be OFF) | High |
|
|
| 2 | Cerberus dashboard header shows "disabled" even when enabled | Medium |
|
|
| 3 | CrowdSec toggle auto-enables when Cerberus is enabled | Medium |
|
|
| 4 | CrowdSec toggle unresponsive + Config button grayed out | High |
|
|
|
|
---
|
|
|
|
## Root Cause Analysis
|
|
|
|
### Issue 1: Cerberus Shows ON by Default
|
|
|
|
**Root Cause:** The `feature_flags_handler.go` has a default value of `true` for all feature flags including `feature.cerberus.enabled`.
|
|
|
|
**File:** [backend/internal/api/handlers/feature_flags_handler.go#L39-L42](../../backend/internal/api/handlers/feature_flags_handler.go#L39-L42)
|
|
|
|
```go
|
|
// Line 39-42
|
|
for _, key := range defaultFlags {
|
|
defaultVal := true // <-- THIS IS THE BUG
|
|
if v, ok := defaultFlagValues[key]; ok {
|
|
defaultVal = v
|
|
}
|
|
```
|
|
|
|
**Problem:** The code sets `defaultVal := true` for all flags, then only overrides it if the key exists in `defaultFlagValues`. However, `feature.cerberus.enabled` is NOT in `defaultFlagValues`:
|
|
|
|
```go
|
|
// Line 29-31
|
|
var defaultFlagValues = map[string]bool{
|
|
"feature.crowdsec.console_enrollment": false,
|
|
}
|
|
```
|
|
|
|
**Result:** On first load with an empty database, `feature.cerberus.enabled` defaults to `true` instead of `false`.
|
|
|
|
**Additional Context:**
|
|
- The [backend/internal/config/config.go#L60](../../backend/internal/config/config.go#L60) correctly defaults `CerberusEnabled` to `false`:
|
|
```go
|
|
CerberusEnabled: getEnvAny("false", "CERBERUS_SECURITY_CERBERUS_ENABLED", ...) == "true"
|
|
```
|
|
- However, the feature flags handler ignores this config and uses its own default.
|
|
|
|
---
|
|
|
|
### Issue 2: Dashboard Header Shows "Disabled" Even When Enabled
|
|
|
|
**Root Cause:** The header banner logic in `Security.tsx` checks `status.cerberus?.enabled` which comes from the security status API, but there's a **data source mismatch**.
|
|
|
|
**Files:**
|
|
- [frontend/src/pages/Security.tsx#L141-L153](../../frontend/src/pages/Security.tsx#L141-L153) - Header banner logic
|
|
- [backend/internal/api/handlers/security_handler.go#L35-L49](../../backend/internal/api/handlers/security_handler.go#L35-L49) - Security status API
|
|
|
|
**Problem Flow:**
|
|
|
|
1. **Security.tsx** checks `status.cerberus?.enabled` from `/api/v1/security/status`
|
|
2. **security_handler.go** reads from config AND settings table:
|
|
```go
|
|
// Line 36-48
|
|
enabled := h.cfg.CerberusEnabled
|
|
var settingKey = "security.cerberus.enabled" // <-- WRONG KEY!
|
|
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; ...
|
|
```
|
|
3. **SystemSettings.tsx** toggles `feature.cerberus.enabled` (via feature flags API)
|
|
|
|
**The Mismatch:**
|
|
|
|
| Component | Key Used |
|
|
|-----------|----------|
|
|
| SystemSettings toggle | `feature.cerberus.enabled` |
|
|
| Security status API | `security.cerberus.enabled` |
|
|
|
|
The toggle writes to `feature.cerberus.enabled` but the security status reads from `security.cerberus.enabled` - **two different keys!**
|
|
|
|
---
|
|
|
|
### Issue 3: CrowdSec Auto-Enables When Cerberus is Enabled
|
|
|
|
**Root Cause:** The `docker-compose.override.yml` and `docker-compose.local.yml` both set `CHARON_SECURITY_CROWDSEC_MODE=local`:
|
|
|
|
**File:** [docker-compose.override.yml#L21](../../docker-compose.override.yml#L21)
|
|
```yaml
|
|
- CHARON_SECURITY_CROWDSEC_MODE=local
|
|
```
|
|
|
|
**Problem:** When the container starts:
|
|
1. Config loads with `CrowdSecMode: "local"` from env var
|
|
2. Security status API returns `crowdsec.enabled: true` because mode is "local"
|
|
3. Frontend shows CrowdSec as enabled
|
|
|
|
**File:** [backend/internal/api/handlers/security_handler.go#L59-L62](../../backend/internal/api/handlers/security_handler.go#L59-L62)
|
|
```go
|
|
// Allow runtime override for CrowdSec enabled flag via settings table
|
|
crowdsecEnabled := mode == "local" // <-- Auto-true if mode is "local"
|
|
```
|
|
|
|
---
|
|
|
|
### Issue 4: CrowdSec Toggle Unresponsive + Config Button Grayed Out
|
|
|
|
**Root Cause:** Multiple issues combine to break the toggle:
|
|
|
|
**A. Toggle Disabled Logic:**
|
|
|
|
**File:** [frontend/src/pages/Security.tsx#L127](../../frontend/src/pages/Security.tsx#L127)
|
|
```tsx
|
|
const crowdsecToggleDisabled = cerberusDisabled || crowdsecPowerMutation.isPending
|
|
```
|
|
|
|
**File:** [frontend/src/pages/Security.tsx#L126](../../frontend/src/pages/Security.tsx#L126)
|
|
```tsx
|
|
const cerberusDisabled = !status.cerberus?.enabled
|
|
```
|
|
|
|
Since `status.cerberus?.enabled` is `false` due to Issue 2 (wrong settings key), `cerberusDisabled` is `true`, making the toggle disabled.
|
|
|
|
**B. Config Button Disabled:**
|
|
|
|
**File:** [frontend/src/pages/Security.tsx#L128](../../frontend/src/pages/Security.tsx#L128)
|
|
```tsx
|
|
const crowdsecControlsDisabled = cerberusDisabled || crowdsecPowerMutation.isPending
|
|
```
|
|
|
|
Same logic - the controls are disabled because Cerberus appears disabled.
|
|
|
|
**C. Switch Component Event Handling:**
|
|
|
|
**File:** [frontend/src/components/ui/Switch.tsx#L17-L20](../../frontend/src/components/ui/Switch.tsx#L17-L20)
|
|
|
|
The Switch component passes `disabled` to the native checkbox input, which prevents click events. This is correct behavior - the issue is the `disabled` prop is incorrectly `true`.
|
|
|
|
---
|
|
|
|
## Recommended Fixes
|
|
|
|
### Fix 1: Update Feature Flag Defaults
|
|
|
|
**File:** `backend/internal/api/handlers/feature_flags_handler.go`
|
|
|
|
```go
|
|
// Change defaultFlagValues to include cerberus.enabled as false
|
|
var defaultFlagValues = map[string]bool{
|
|
"feature.cerberus.enabled": false, // ADD THIS
|
|
"feature.crowdsec.console_enrollment": false,
|
|
"feature.uptime.enabled": true, // Uptime can default ON
|
|
}
|
|
```
|
|
|
|
### Fix 2: Align Settings Keys
|
|
|
|
**Option A (Recommended):** Update security_handler.go to read from feature flags key
|
|
|
|
**File:** `backend/internal/api/handlers/security_handler.go`
|
|
|
|
```go
|
|
// Line 37: Change from
|
|
var settingKey = "security.cerberus.enabled"
|
|
// To
|
|
var settingKey = "feature.cerberus.enabled"
|
|
```
|
|
|
|
**Option B:** Create a sync mechanism between feature flags and security settings
|
|
|
|
### Fix 3: Remove CrowdSec Mode Override from Docker Compose
|
|
|
|
**Files:**
|
|
- `docker-compose.override.yml`
|
|
- `docker-compose.local.yml`
|
|
|
|
```yaml
|
|
# Remove or comment out:
|
|
# - CHARON_SECURITY_CROWDSEC_MODE=local
|
|
# Or change to:
|
|
- CHARON_SECURITY_CROWDSEC_MODE=disabled
|
|
```
|
|
|
|
### Fix 4: No Additional Fix Needed
|
|
|
|
Issue 4 is a symptom of Issues 1-2. Once those are fixed:
|
|
- `cerberusDisabled` will be `false` when Cerberus is enabled
|
|
- `crowdsecToggleDisabled` will be `false`
|
|
- `crowdsecControlsDisabled` will be `false`
|
|
- Toggle and Config button will be interactive
|
|
|
|
---
|
|
|
|
## Test Scenarios
|
|
|
|
### Test 1: Fresh Install Default State
|
|
```
|
|
Given: Clean database, no env vars set
|
|
When: User loads the Settings > System page
|
|
Then: Cerberus toggle should be OFF
|
|
And: /api/v1/feature-flags returns { "feature.cerberus.enabled": false }
|
|
```
|
|
|
|
### Test 2: Cerberus Toggle Sync
|
|
```
|
|
Given: User is on Settings > System page
|
|
When: User enables Cerberus toggle
|
|
Then: /api/v1/security/status returns { "cerberus": { "enabled": true } }
|
|
And: Security dashboard header banner is NOT displayed
|
|
```
|
|
|
|
### Test 3: CrowdSec Toggle Interaction
|
|
```
|
|
Given: Cerberus is enabled
|
|
And: User is on Security dashboard
|
|
When: User clicks CrowdSec toggle
|
|
Then: Toggle should respond to click
|
|
And: CrowdSec enabled state should change
|
|
And: Toast notification should appear
|
|
```
|
|
|
|
### Test 4: CrowdSec Config Button
|
|
```
|
|
Given: Cerberus is enabled
|
|
And: User is on Security dashboard
|
|
When: User clicks CrowdSec "Config" button
|
|
Then: User should navigate to /security/crowdsec
|
|
And: Button should NOT be grayed out
|
|
```
|
|
|
|
### Test 5: Environment Variable Override
|
|
```
|
|
Given: CERBERUS_SECURITY_CERBERUS_ENABLED=true set
|
|
When: User loads Settings > System (fresh DB)
|
|
Then: Cerberus toggle should be ON (env override)
|
|
```
|
|
|
|
---
|
|
|
|
## Implementation Priority
|
|
|
|
| Priority | Fix | Effort | Impact |
|
|
|----------|-----|--------|--------|
|
|
| P0 | Fix 2 (Key alignment) | Low | High - Fixes Issues 2, 4 |
|
|
| P1 | Fix 1 (Default values) | Low | High - Fixes Issue 1 |
|
|
| P2 | Fix 3 (Docker compose) | Low | Medium - Fixes Issue 3 |
|
|
|
|
---
|
|
|
|
## Files to Modify
|
|
|
|
1. **backend/internal/api/handlers/feature_flags_handler.go** - Add default value for cerberus
|
|
2. **backend/internal/api/handlers/security_handler.go** - Change settings key to `feature.cerberus.enabled`
|
|
3. **docker-compose.override.yml** - Remove or change CrowdSec mode
|
|
4. **docker-compose.local.yml** - Remove or change CrowdSec mode
|
|
|
|
---
|
|
|
|
## Additional Observations
|
|
|
|
1. **Dual Control Systems:** There are two overlapping control systems:
|
|
- Feature flags (`feature.cerberus.enabled`) - toggled in SystemSettings.tsx
|
|
- Security config (`SecurityConfig.Enabled` in DB) - used by Enable/Disable endpoints
|
|
|
|
Consider consolidating to one source of truth.
|
|
|
|
2. **Config vs Settings:** The `config.SecurityConfig` struct loaded from env vars is separate from DB-backed `SecurityConfig` model. This creates confusion about which takes precedence.
|
|
|
|
3. **No Migration:** When updating default values, existing users may need a migration or reset to see the new defaults.
|
|
|
|
---
|
|
|
|
## Code Reference Summary
|
|
|
|
| File | Line | Purpose |
|
|
|------|------|---------|
|
|
| `feature_flags_handler.go` | L29-31 | Missing cerberus default |
|
|
| `feature_flags_handler.go` | L39 | `defaultVal := true` bug |
|
|
| `security_handler.go` | L37 | Wrong settings key |
|
|
| `Security.tsx` | L126-128 | Disabled state logic |
|
|
| `SystemSettings.tsx` | L99-105 | Feature toggle UI |
|
|
| `docker-compose.override.yml` | L21 | CrowdSec mode env var |
|
|
| `config.go` | L60 | Correct cerberus default |
|