feat: implement modular security services with CrowdSec and WAF integration
This commit is contained in:
@@ -20,6 +20,7 @@ const Backups = lazy(() => import('./pages/Backups'))
|
||||
const Tasks = lazy(() => import('./pages/Tasks'))
|
||||
const Logs = lazy(() => import('./pages/Logs'))
|
||||
const Domains = lazy(() => import('./pages/Domains'))
|
||||
const Security = lazy(() => import('./pages/Security'))
|
||||
const Uptime = lazy(() => import('./pages/Uptime'))
|
||||
const Notifications = lazy(() => import('./pages/Notifications'))
|
||||
const Login = lazy(() => import('./pages/Login'))
|
||||
@@ -47,6 +48,7 @@ export default function App() {
|
||||
<Route path="remote-servers" element={<RemoteServers />} />
|
||||
<Route path="domains" element={<Domains />} />
|
||||
<Route path="certificates" element={<Certificates />} />
|
||||
<Route path="security" element={<Security />} />
|
||||
<Route path="uptime" element={<Uptime />} />
|
||||
<Route path="notifications" element={<Notifications />} />
|
||||
<Route path="import" element={<ImportCaddy />} />
|
||||
|
||||
24
frontend/src/api/security.ts
Normal file
24
frontend/src/api/security.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import client from './client'
|
||||
|
||||
export interface SecurityStatus {
|
||||
crowdsec: {
|
||||
mode: 'disabled' | 'local' | 'external'
|
||||
api_url: string
|
||||
enabled: boolean
|
||||
}
|
||||
waf: {
|
||||
mode: 'disabled' | 'enabled'
|
||||
enabled: boolean
|
||||
}
|
||||
rate_limit: {
|
||||
enabled: boolean
|
||||
}
|
||||
acl: {
|
||||
enabled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export const getSecurityStatus = async (): Promise<SecurityStatus> => {
|
||||
const response = await client.get<SecurityStatus>('/security/status')
|
||||
return response.data
|
||||
}
|
||||
@@ -47,6 +47,7 @@ export default function Layout({ children }: LayoutProps) {
|
||||
{ name: 'Remote Servers', path: '/remote-servers', icon: '🖥️' },
|
||||
{ name: 'Domains', path: '/domains', icon: '🌍' },
|
||||
{ name: 'Certificates', path: '/certificates', icon: '🔒' },
|
||||
{ name: 'Security', path: '/security', icon: '🛡️' },
|
||||
{ name: 'Uptime', path: '/uptime', icon: '📈' },
|
||||
{ name: 'Notifications', path: '/notifications', icon: '🔔' },
|
||||
{ name: 'Import Caddyfile', path: '/import', icon: '📥' },
|
||||
@@ -91,15 +92,15 @@ export default function Layout({ children }: LayoutProps) {
|
||||
${mobileSidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'}
|
||||
${isCollapsed ? 'w-20' : 'w-64'}
|
||||
`}>
|
||||
<div className={`h-16 flex items-center justify-center border-b border-gray-200 dark:border-gray-800`}>
|
||||
<div className={`h-20 flex items-center justify-center border-b border-gray-200 dark:border-gray-800`}>
|
||||
{isCollapsed ? (
|
||||
<img src="/logo.png" alt="CPM+" className="h-10 w-10" />
|
||||
<img src="/logo.png" alt="CPM+" className="h-12 w-10" />
|
||||
) : (
|
||||
<img src="/banner.png" alt="CPM+" className="h-12 w-auto" />
|
||||
<img src="/banner.png" alt="CPM+" className="h-16 w-auto" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col flex-1 px-4 mt-16 lg:mt-0">
|
||||
<div className="flex flex-col flex-1 px-4 mt-16 lg:mt-6">
|
||||
<nav className="flex-1 space-y-1">
|
||||
{navigation.map((item) => {
|
||||
if (item.children) {
|
||||
@@ -246,7 +247,7 @@ export default function Layout({ children }: LayoutProps) {
|
||||
{/* Main Content */}
|
||||
<main className={`flex-1 min-w-0 overflow-auto pt-16 lg:pt-0 flex flex-col transition-all duration-200 ${isCollapsed ? 'lg:ml-20' : 'lg:ml-64'}`}>
|
||||
{/* Desktop Header */}
|
||||
<header className="hidden lg:flex items-center justify-between px-8 py-4 bg-white dark:bg-dark-sidebar border-b border-gray-200 dark:border-gray-800 relative">
|
||||
<header className="hidden lg:flex items-center justify-between px-8 h-20 bg-white dark:bg-dark-sidebar border-b border-gray-200 dark:border-gray-800 relative">
|
||||
<div className="w-1/3 flex items-center gap-4">
|
||||
<button
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
|
||||
160
frontend/src/pages/Security.tsx
Normal file
160
frontend/src/pages/Security.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Shield, ShieldAlert, ShieldCheck, Lock, Activity, ExternalLink } from 'lucide-react'
|
||||
import { getSecurityStatus } from '../api/security'
|
||||
import { Card } from '../components/ui/Card'
|
||||
import { Button } from '../components/ui/Button'
|
||||
|
||||
export default function Security() {
|
||||
const { data: status, isLoading } = useQuery({
|
||||
queryKey: ['security-status'],
|
||||
queryFn: getSecurityStatus,
|
||||
})
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="p-8 text-center">Loading security status...</div>
|
||||
}
|
||||
|
||||
if (!status) {
|
||||
return <div className="p-8 text-center text-red-500">Failed to load security status</div>
|
||||
}
|
||||
|
||||
const allDisabled = !status.crowdsec.enabled && !status.waf.enabled && !status.rate_limit.enabled && !status.acl.enabled
|
||||
|
||||
if (allDisabled) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center space-y-6">
|
||||
<div className="bg-gray-100 dark:bg-gray-800 p-6 rounded-full">
|
||||
<Shield className="w-16 h-16 text-gray-400" />
|
||||
</div>
|
||||
<div className="max-w-md space-y-2">
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white">Security Services Not Enabled</h2>
|
||||
<p className="text-gray-500 dark:text-gray-400">
|
||||
CaddyProxyManager+ supports advanced security features like CrowdSec, WAF, ACLs, and Rate Limiting.
|
||||
These are optional and can be enabled via environment variables.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => window.open('https://wikid82.github.io/CaddyProxyManagerPlus/security', '_blank')}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
View Implementation Guide
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
|
||||
<ShieldCheck className="w-8 h-8 text-green-500" />
|
||||
Security Dashboard
|
||||
</h1>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => window.open('https://wikid82.github.io/CaddyProxyManagerPlus/security', '_blank')}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
Documentation
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{/* CrowdSec */}
|
||||
<Card className={status.crowdsec.enabled ? 'border-green-200 dark:border-green-900' : ''}>
|
||||
<div className="flex flex-row items-center justify-between pb-2">
|
||||
<h3 className="text-sm font-medium text-white">CrowdSec</h3>
|
||||
<ShieldAlert className={`w-4 h-4 ${status.crowdsec.enabled ? 'text-green-500' : 'text-gray-400'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold mb-1 text-white">
|
||||
{status.crowdsec.enabled ? 'Active' : 'Disabled'}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{status.crowdsec.enabled
|
||||
? `Mode: ${status.crowdsec.mode}`
|
||||
: 'Intrusion Prevention System'}
|
||||
</p>
|
||||
{status.crowdsec.enabled && (
|
||||
<div className="mt-4">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => window.open(status.crowdsec.mode === 'external' ? status.crowdsec.api_url : 'http://localhost:8080', '_blank')}
|
||||
>
|
||||
Open Console
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* WAF */}
|
||||
<Card className={status.waf.enabled ? 'border-green-200 dark:border-green-900' : ''}>
|
||||
<div className="flex flex-row items-center justify-between pb-2">
|
||||
<h3 className="text-sm font-medium text-white">WAF (Coraza)</h3>
|
||||
<Shield className={`w-4 h-4 ${status.waf.enabled ? 'text-green-500' : 'text-gray-400'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold mb-1 text-white">
|
||||
{status.waf.enabled ? 'Active' : 'Disabled'}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
OWASP Core Rule Set
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* ACL */}
|
||||
<Card className={status.acl.enabled ? 'border-green-200 dark:border-green-900' : ''}>
|
||||
<div className="flex flex-row items-center justify-between pb-2">
|
||||
<h3 className="text-sm font-medium text-white">Access Control</h3>
|
||||
<Lock className={`w-4 h-4 ${status.acl.enabled ? 'text-green-500' : 'text-gray-400'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold mb-1 text-white">
|
||||
{status.acl.enabled ? 'Active' : 'Disabled'}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
IP-based Allow/Deny Lists
|
||||
</p>
|
||||
{status.acl.enabled && (
|
||||
<div className="mt-4">
|
||||
<Button variant="secondary" size="sm" className="w-full">
|
||||
Manage Lists
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Rate Limiting */}
|
||||
<Card className={status.rate_limit.enabled ? 'border-green-200 dark:border-green-900' : ''}>
|
||||
<div className="flex flex-row items-center justify-between pb-2">
|
||||
<h3 className="text-sm font-medium text-white">Rate Limiting</h3>
|
||||
<Activity className={`w-4 h-4 ${status.rate_limit.enabled ? 'text-green-500' : 'text-gray-400'}`} />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-2xl font-bold mb-1 text-white">
|
||||
{status.rate_limit.enabled ? 'Active' : 'Disabled'}
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
DDoS Protection
|
||||
</p>
|
||||
{status.rate_limit.enabled && (
|
||||
<div className="mt-4">
|
||||
<Button variant="secondary" size="sm" className="w-full">
|
||||
Configure Limits
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user