diff --git a/docs/features.md b/docs/features.md index 349bbcc4..e1cb0936 100644 --- a/docs/features.md +++ b/docs/features.md @@ -545,6 +545,39 @@ Uses WebSocket technology to stream logs with zero delay. - `?source=waf` — Only WAF-related events - `?source=cerberus` — All Cerberus security events +### WebSocket Connection Monitoring + +**What it does:** Tracks and displays all active WebSocket connections in real-time, helping you troubleshoot connection issues and monitor system health. + +**What you see:** + +- Total active WebSocket connections +- Breakdown by connection type (General Logs vs Security Logs) +- Oldest connection age +- Detailed connection information: + - Connection ID and type + - Remote address (client IP) + - Active filters being used + - Connection duration + +**Where to find it:** System Settings → WebSocket Connections card + +**API Endpoints:** Programmatically access WebSocket statistics: + +- `GET /api/v1/websocket/stats` — Aggregate connection statistics +- `GET /api/v1/websocket/connections` — Detailed list of all active connections + +**Use cases:** + +- **Troubleshooting:** Verify WebSocket connections are working when live logs aren't updating +- **Monitoring:** Track how many users are viewing live logs in real-time +- **Debugging:** Identify connection issues with proxy/load balancer configurations +- **Capacity Planning:** Understand WebSocket connection patterns and usage + +**Auto-refresh:** The status card automatically updates every 5 seconds to show current connection state. + +**See also:** [WebSocket Troubleshooting Guide](troubleshooting/websocket.md) for help resolving connection issues. + ### Notification System **What it does:** Sends alerts when security events match your configured criteria. diff --git a/docs/live-logs-guide.md b/docs/live-logs-guide.md index 69ad8d0d..18e43349 100644 --- a/docs/live-logs-guide.md +++ b/docs/live-logs-guide.md @@ -595,6 +595,7 @@ ws.onmessage = (event) => { - **[Security Guide](https://wikid82.github.io/charon/security)** \u2014 Learn about Cerberus features - **[API Documentation](https://wikid82.github.io/charon/api)** \u2014 Full API reference - **[Features Overview](https://wikid82.github.io/charon/features)** \u2014 See all Charon capabilities +- **[WebSocket Troubleshooting](troubleshooting/websocket.md)** — Fix WebSocket connection issues - **[Troubleshooting](https://wikid82.github.io/charon/troubleshooting)** \u2014 Common issues and solutions --- diff --git a/docs/troubleshooting/websocket.md b/docs/troubleshooting/websocket.md new file mode 100644 index 00000000..37a0170b --- /dev/null +++ b/docs/troubleshooting/websocket.md @@ -0,0 +1,364 @@ +# Troubleshooting WebSocket Issues + +WebSocket connections are used in Charon for real-time features like live log streaming. If you're experiencing issues with WebSocket connections (e.g., logs not updating in real-time), this guide will help you diagnose and resolve the problem. + +## Quick Diagnostics + +### Check WebSocket Connection Status + +1. Go to **System Settings** in the Charon UI +2. Scroll to the **WebSocket Connections** card +3. Check if there are active connections displayed + +The WebSocket status card shows: +- Total number of active WebSocket connections +- Breakdown by type (General Logs vs Security Logs) +- Oldest connection age +- Detailed connection info (when expanded) + +### Browser Console Check + +Open your browser's Developer Tools (F12) and check the Console tab for: +- WebSocket connection errors +- Connection refused messages +- Authentication failures +- CORS errors + +## Common Issues and Solutions + +### 1. Proxy/Load Balancer Configuration + +**Symptom:** WebSocket connections fail to establish or disconnect immediately. + +**Cause:** If running Charon behind a reverse proxy (Nginx, Apache, HAProxy, or load balancer), the proxy might be terminating WebSocket connections or not forwarding the upgrade request properly. + +**Solution:** + +#### Nginx Configuration + +```nginx +location /api/v1/logs/live { + proxy_pass http://charon:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Increase timeouts for long-lived connections + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; +} + +location /api/v1/cerberus/logs/ws { + proxy_pass http://charon:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Increase timeouts for long-lived connections + proxy_read_timeout 3600s; + proxy_send_timeout 3600s; +} +``` + +Key requirements: +- `proxy_http_version 1.1` — Required for WebSocket support +- `Upgrade` and `Connection` headers — Required for WebSocket upgrade +- Long `proxy_read_timeout` — Prevents connection from timing out + +#### Apache Configuration + +```apache + + ServerName charon.example.com + + # Enable WebSocket proxy + ProxyRequests Off + ProxyPreserveHost On + + # WebSocket endpoints + ProxyPass /api/v1/logs/live ws://localhost:8080/api/v1/logs/live retry=0 timeout=3600 + ProxyPassReverse /api/v1/logs/live ws://localhost:8080/api/v1/logs/live + + ProxyPass /api/v1/cerberus/logs/ws ws://localhost:8080/api/v1/cerberus/logs/ws retry=0 timeout=3600 + ProxyPassReverse /api/v1/cerberus/logs/ws ws://localhost:8080/api/v1/cerberus/logs/ws + + # Regular HTTP endpoints + ProxyPass / http://localhost:8080/ + ProxyPassReverse / http://localhost:8080/ + +``` + +Required modules: +```bash +a2enmod proxy proxy_http proxy_wstunnel +``` + +### 2. Network Timeouts + +**Symptom:** WebSocket connections work initially but disconnect after some idle time. + +**Cause:** Intermediate network infrastructure (firewalls, load balancers, NAT devices) may have idle timeout settings shorter than the WebSocket keepalive interval. + +**Solution:** + +Charon sends WebSocket ping frames every 30 seconds to keep connections alive. If you're still experiencing timeouts: + +1. **Check proxy timeout settings** (see above) +2. **Check firewall idle timeout:** + ```bash + # Linux iptables + iptables -L -v -n | grep ESTABLISHED + + # If timeout is too short, increase it: + iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + echo 3600 > /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established + ``` + +3. **Check load balancer settings:** + - AWS ALB/ELB: Set idle timeout to 3600 seconds + - GCP Load Balancer: Set timeout to 1 hour + - Azure Load Balancer: Set idle timeout to maximum + +### 3. HTTPS Certificate Errors (Docker) + +**Symptom:** WebSocket connections fail with TLS/certificate errors, especially in Docker environments. + +**Cause:** Missing CA certificates in the Docker container, or self-signed certificates not trusted by the browser. + +**Solution:** + +#### Install CA Certificates (Docker) + +Add to your Dockerfile: +```dockerfile +RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates +``` + +Or for existing containers: +```bash +docker exec -it charon apt-get update && apt-get install -y ca-certificates +``` + +#### For Self-Signed Certificates (Development Only) + +**Warning:** This compromises security. Only use in development environments. + +Set environment variable: +```bash +docker run -e FF_IGNORE_CERT_ERRORS=1 charon:latest +``` + +Or in docker-compose.yml: +```yaml +services: + charon: + environment: + - FF_IGNORE_CERT_ERRORS=1 +``` + +#### Better Solution: Use Valid Certificates + +1. Use Let's Encrypt (free, automated) +2. Use a trusted CA certificate +3. Import your self-signed cert into the browser's trust store + +### 4. Firewall Settings + +**Symptom:** WebSocket connections fail or time out. + +**Cause:** Firewall blocking WebSocket traffic on ports 80/443. + +**Solution:** + +#### Linux (iptables) + +Allow WebSocket traffic: +```bash +# Allow HTTP/HTTPS +iptables -A INPUT -p tcp --dport 80 -j ACCEPT +iptables -A INPUT -p tcp --dport 443 -j ACCEPT + +# Allow established connections (for WebSocket) +iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT + +# Save rules +iptables-save > /etc/iptables/rules.v4 +``` + +#### Docker + +Ensure ports are exposed: +```yaml +services: + charon: + ports: + - "8080:8080" + - "443:443" +``` + +#### Cloud Providers + +- **AWS:** Add inbound rules to Security Group for ports 80/443 +- **GCP:** Add firewall rules for ports 80/443 +- **Azure:** Add Network Security Group rules for ports 80/443 + +### 5. Connection Stability / Packet Loss + +**Symptom:** Frequent WebSocket disconnections and reconnections. + +**Cause:** Unstable network with packet loss prevents WebSocket connections from staying open. + +**Solution:** + +#### Check Network Stability + +```bash +# Ping test +ping -c 100 charon.example.com + +# Check packet loss (should be < 1%) +mtr charon.example.com +``` + +#### Enable Connection Retry (Client-Side) + +The Charon frontend automatically handles reconnection for security logs but not general logs. If you need more robust reconnection: + +1. Monitor the WebSocket status in System Settings +2. Refresh the page if connections are frequently dropping +3. Consider using a more stable network connection +4. Check if VPN or proxy is causing issues + +### 6. Browser Compatibility + +**Symptom:** WebSocket connections don't work in certain browsers. + +**Cause:** Very old browsers don't support WebSocket protocol. + +**Supported Browsers:** +- Chrome 16+ ✅ +- Firefox 11+ ✅ +- Safari 7+ ✅ +- Edge (all versions) ✅ +- IE 10+ ⚠️ (deprecated, use Edge) + +**Solution:** Update to a modern browser. + +### 7. CORS Issues + +**Symptom:** Browser console shows CORS errors with WebSocket connections. + +**Cause:** Cross-origin WebSocket connection blocked by browser security policy. + +**Solution:** + +WebSocket connections should be same-origin (from the same domain as the Charon UI). If you're accessing Charon from a different domain: + +1. **Preferred:** Access Charon UI from the same domain +2. **Alternative:** Configure CORS in Charon (if supported) +3. **Development Only:** Use browser extension to disable CORS (NOT for production) + +### 8. Authentication Issues + +**Symptom:** WebSocket connection fails with 401 Unauthorized. + +**Cause:** Authentication token not being sent with WebSocket connection. + +**Solution:** + +Charon WebSocket endpoints support three authentication methods: + +1. **HttpOnly Cookie** (automatic) — Used by default when accessing UI from browser +2. **Query Parameter** — `?token=` +3. **Authorization Header** — Not supported for browser WebSocket connections + +If you're accessing WebSocket from a script or tool: +```javascript +const ws = new WebSocket('wss://charon.example.com/api/v1/logs/live?token=YOUR_TOKEN'); +``` + +## Monitoring WebSocket Connections + +### Using the System Settings UI + +1. Navigate to **System Settings** in Charon +2. View the **WebSocket Connections** card +3. Expand details to see: + - Connection ID + - Connection type (General/Security) + - Remote address + - Active filters + - Connection duration + +### Using the API + +Check WebSocket statistics programmatically: + +```bash +# Get connection statistics +curl -H "Authorization: Bearer YOUR_TOKEN" \ + https://charon.example.com/api/v1/websocket/stats + +# Get detailed connection list +curl -H "Authorization: Bearer YOUR_TOKEN" \ + https://charon.example.com/api/v1/websocket/connections +``` + +Response example: +```json +{ + "total_active": 2, + "logs_connections": 1, + "cerberus_connections": 1, + "oldest_connection": "2024-01-15T10:30:00Z", + "last_updated": "2024-01-15T11:00:00Z" +} +``` + +### Using Browser DevTools + +1. Open DevTools (F12) +2. Go to **Network** tab +3. Filter by **WS** (WebSocket) +4. Look for connections to: + - `/api/v1/logs/live` + - `/api/v1/cerberus/logs/ws` + +Check: +- Status should be `101 Switching Protocols` +- Messages tab shows incoming log entries +- No errors in Frames tab + +## Still Having Issues? + +If none of the above solutions work: + +1. **Check Charon logs:** + ```bash + docker logs charon | grep -i websocket + ``` + +2. **Enable debug logging** (if available) + +3. **Report an issue on GitHub:** + - [Charon Issues](https://github.com/Wikid82/charon/issues) + - Include: + - Charon version + - Browser and version + - Proxy/load balancer configuration + - Error messages from browser console + - Charon server logs + +## See Also + +- [Live Logs Guide](../live-logs-guide.md) +- [Security Documentation](../security.md) +- [API Documentation](../api.md) diff --git a/frontend/src/api/websocket.ts b/frontend/src/api/websocket.ts new file mode 100644 index 00000000..11fadedb --- /dev/null +++ b/frontend/src/api/websocket.ts @@ -0,0 +1,40 @@ +import client from './client'; + +export interface ConnectionInfo { + id: string; + type: 'logs' | 'cerberus'; + connected_at: string; + last_activity_at: string; + remote_addr?: string; + user_agent?: string; + filters?: string; +} + +export interface ConnectionStats { + total_active: number; + logs_connections: number; + cerberus_connections: number; + oldest_connection?: string; + last_updated: string; +} + +export interface ConnectionsResponse { + connections: ConnectionInfo[]; + count: number; +} + +/** + * Get all active WebSocket connections + */ +export const getWebSocketConnections = async (): Promise => { + const response = await client.get('/websocket/connections'); + return response.data; +}; + +/** + * Get aggregate WebSocket connection statistics + */ +export const getWebSocketStats = async (): Promise => { + const response = await client.get('/websocket/stats'); + return response.data; +}; diff --git a/frontend/src/components/WebSocketStatusCard.tsx b/frontend/src/components/WebSocketStatusCard.tsx new file mode 100644 index 00000000..a26f2128 --- /dev/null +++ b/frontend/src/components/WebSocketStatusCard.tsx @@ -0,0 +1,175 @@ +import { useState } from 'react'; +import { Wifi, WifiOff, Activity, Clock, Filter, Globe } from 'lucide-react'; +import { useWebSocketConnections, useWebSocketStats } from '../hooks/useWebSocketStatus'; +import { + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + Badge, + Skeleton, + Alert, +} from './ui'; +import { formatDistanceToNow } from 'date-fns'; + +interface WebSocketStatusCardProps { + className?: string; + showDetails?: boolean; +} + +/** + * Component to display WebSocket connection status and statistics + */ +export function WebSocketStatusCard({ className = '', showDetails = false }: WebSocketStatusCardProps) { + const [expanded, setExpanded] = useState(showDetails); + const { data: connections, isLoading: connectionsLoading } = useWebSocketConnections(); + const { data: stats, isLoading: statsLoading } = useWebSocketStats(); + + const isLoading = connectionsLoading || statsLoading; + + if (isLoading) { + return ( + + + + + + +
+ + +
+
+
+ ); + } + + if (!stats) { + return ( + + Unable to load WebSocket status + + ); + } + + const hasActiveConnections = stats.total_active > 0; + + return ( + + +
+
+
+ {hasActiveConnections ? ( + + ) : ( + + )} +
+
+ WebSocket Connections + + Real-time connection monitoring + +
+
+ + {stats.total_active} Active + +
+
+ + {/* Statistics Grid */} +
+
+
+ + General Logs +
+

{stats.logs_connections}

+
+
+
+ + Security Logs +
+

{stats.cerberus_connections}

+
+
+ + {/* Oldest Connection */} + {stats.oldest_connection && ( +
+
+ + Oldest Connection +
+

+ {formatDistanceToNow(new Date(stats.oldest_connection), { addSuffix: true })} +

+
+ )} + + {/* Connection Details */} + {expanded && connections && connections.connections.length > 0 && ( +
+

Active Connections

+
+ {connections.connections.map((conn) => ( +
+
+ + {conn.type === 'logs' ? 'General' : 'Security'} + + + {conn.id.substring(0, 8)}... + +
+ {conn.remote_addr && ( +
+ + {conn.remote_addr} +
+ )} + {conn.filters && ( +
+ + {conn.filters} +
+ )} +
+ + + Connected {formatDistanceToNow(new Date(conn.connected_at), { addSuffix: true })} + +
+
+ ))} +
+
+ )} + + {/* Toggle Details Button */} + {connections && connections.connections.length > 0 && ( + + )} + + {/* No Connections Message */} + {!hasActiveConnections && ( +
+ No active WebSocket connections +
+ )} +
+
+ ); +} diff --git a/frontend/src/hooks/useWebSocketStatus.ts b/frontend/src/hooks/useWebSocketStatus.ts new file mode 100644 index 00000000..574ef74f --- /dev/null +++ b/frontend/src/hooks/useWebSocketStatus.ts @@ -0,0 +1,24 @@ +import { useQuery } from '@tanstack/react-query'; +import { getWebSocketConnections, getWebSocketStats } from '../api/websocket'; + +/** + * Hook to fetch and manage WebSocket connection data + */ +export const useWebSocketConnections = () => { + return useQuery({ + queryKey: ['websocket', 'connections'], + queryFn: getWebSocketConnections, + refetchInterval: 5000, // Refresh every 5 seconds + }); +}; + +/** + * Hook to fetch and manage WebSocket statistics + */ +export const useWebSocketStats = () => { + return useQuery({ + queryKey: ['websocket', 'stats'], + queryFn: getWebSocketStats, + refetchInterval: 5000, // Refresh every 5 seconds + }); +}; diff --git a/frontend/src/pages/SystemSettings.tsx b/frontend/src/pages/SystemSettings.tsx index 1aea57e6..3c53a9ca 100644 --- a/frontend/src/pages/SystemSettings.tsx +++ b/frontend/src/pages/SystemSettings.tsx @@ -16,6 +16,7 @@ import { getFeatureFlags, updateFeatureFlags } from '../api/featureFlags' import client from '../api/client' import { Server, RefreshCw, Save, Activity, Info, ExternalLink } from 'lucide-react' import { ConfigReloadOverlay } from '../components/LoadingStates' +import { WebSocketStatusCard } from '../components/WebSocketStatusCard' interface HealthResponse { status: string @@ -410,6 +411,9 @@ export default function SystemSettings() { + + {/* WebSocket Connection Status */} + )