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:
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user