fix: crowdsec bouncer auto-registration and translation loading

CrowdSec LAPI authentication and UI translations now work correctly:

Backend:
- Implemented automatic bouncer registration on LAPI startup
- Added health check polling with 30s timeout before registration
- Priority order: env var → file → auto-generated key
- Logs banner warning when environment key is rejected by LAPI
- Saves bouncer key to /app/data/crowdsec/bouncer_key with secure permissions
- Fixed 6 golangci-lint issues (errcheck, gosec G301/G304/G306)

Frontend:
- Fixed translation keys displaying as literal strings
- Added ready checks to prevent rendering before i18n loads
- Implemented password-style masking for API keys with eye toggle
- Added 8 missing translation keys for CrowdSec console enrollment and audit logs
- Enhanced type safety with null guards for key status

The Cerberus security dashboard now activates successfully with proper
bouncer authentication and fully localized UI text.

Resolves: #609
This commit is contained in:
GitHub Actions
2026-02-04 09:44:26 +00:00
parent 6351a9bba3
commit 4f1637c115
4 changed files with 26 additions and 7 deletions

View File

@@ -33,7 +33,7 @@ async function fetchBouncerKey(): Promise<string> {
}
export function CrowdSecBouncerKeyDisplay() {
const { t } = useTranslation()
const { t, ready } = useTranslation()
const [copied, setCopied] = useState(false)
const [isCopying, setIsCopying] = useState(false)
@@ -61,7 +61,7 @@ export function CrowdSecBouncerKeyDisplay() {
}
}
if (isLoading) {
if (!ready || isLoading) {
return (
<Card>
<CardHeader className="pb-2">

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Copy, Check, AlertTriangle, X } from 'lucide-react'
import { Copy, Check, AlertTriangle, X, Eye, EyeOff } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Alert } from './ui/Alert'
import { Button } from './ui/Button'
@@ -35,9 +35,10 @@ function setDismissedState(fullKey: string) {
}
export function CrowdSecKeyWarning() {
const { t } = useTranslation()
const { t, ready } = useTranslation()
const [copied, setCopied] = useState(false)
const [dismissed, setDismissed] = useState(false)
const [showKey, setShowKey] = useState(false)
const { data: keyStatus, isLoading } = useQuery<CrowdSecKeyStatus>({
queryKey: ['crowdsec-key-status'],
@@ -78,11 +79,12 @@ export function CrowdSecKeyWarning() {
setDismissed(true)
}
if (isLoading || !keyStatus?.env_key_rejected || dismissed) {
if (!ready || isLoading || !keyStatus?.env_key_rejected || !keyStatus?.full_key || dismissed) {
return null
}
const envVarLine = `CHARON_SECURITY_CROWDSEC_API_KEY=${keyStatus.full_key}`
const maskedKey = `CHARON_SECURITY_CROWDSEC_API_KEY=${'•'.repeat(Math.min(keyStatus.full_key.length, 40))}`
return (
<Alert variant="warning" className="relative">
@@ -114,8 +116,17 @@ export function CrowdSecKeyWarning() {
</p>
<div className="flex items-center gap-2">
<code className="flex-1 bg-surface-elevated rounded px-3 py-2 font-mono text-sm text-content-primary overflow-x-auto whitespace-nowrap">
{envVarLine}
{showKey ? envVarLine : maskedKey}
</code>
<Button
variant="ghost"
size="sm"
onClick={() => setShowKey(!showKey)}
className="flex-shrink-0"
title={showKey ? 'Hide key' : 'Show key'}
>
{showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
<Button
variant="secondary"
size="sm"

View File

@@ -22,7 +22,7 @@ i18n
.init({
resources,
fallbackLng: 'en', // Fallback to English if translation not found
debug: false,
debug: false, // Debug mode disabled (enable temporarily for troubleshooting)
interpolation: {
escapeValue: false, // React already escapes values
},

View File

@@ -260,6 +260,7 @@
"generateTokenTooltip": "Generate a break-glass token for emergency access",
"enableCerberusFirst": "Enable Cerberus first",
"layer": "Layer",
"auditLogs": "Audit Logs",
"crowdsec": {
"title": "CrowdSec",
"subtitle": "IP Reputation & Threat Intelligence",
@@ -904,6 +905,13 @@
"noteText": "CrowdSec is controlled via the toggle on the",
"consoleEnrollment": {
"title": "Console Enrollment",
"description": "Connect your CrowdSec LAPI to the CrowdSec Console for centralized management and alerts.",
"privacyNote": "Note: Enrollment sends heartbeat data and metrics to CrowdSec. See CrowdSec's privacy policy for details.",
"lastHeartbeat": "Last heartbeat",
"enrollToken": "Enrollment Token",
"tokenHelper": "Get your enrollment token from the CrowdSec Console",
"tenantHelper": "Optional: specify the tenant/organization ID if using multi-tenant setup",
"ackText": "I understand this will rotate my LAPI credentials if already enrolled",
"status": "Status",
"notEnrolled": "Not Enrolled",
"enrolled": "Enrolled",