261 lines
9.0 KiB
Python
261 lines
9.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Cloudflare Infrastructure Monitoring Dashboard
|
|
Provides real-time monitoring of Cloudflare resources and services
|
|
"""
|
|
|
|
import os
|
|
import json
|
|
import time
|
|
import requests
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Any
|
|
|
|
|
|
class CloudflareMonitor:
|
|
def __init__(self):
|
|
self.base_url = "https://api.cloudflare.com/client/v4"
|
|
self.headers = {
|
|
"Authorization": f"Bearer {os.getenv('CLOUDFLARE_API_TOKEN')}",
|
|
"Content-Type": "application/json",
|
|
}
|
|
self.account_id = os.getenv("CLOUDFLARE_ACCOUNT_ID")
|
|
|
|
if not self.account_id or not os.getenv("CLOUDFLARE_API_TOKEN"):
|
|
raise ValueError("Missing Cloudflare credentials in environment")
|
|
|
|
def make_request(self, endpoint: str) -> Dict[str, Any]:
|
|
"""Make API request with error handling"""
|
|
url = f"{self.base_url}{endpoint}"
|
|
try:
|
|
response = requests.get(url, headers=self.headers, timeout=10)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
except requests.RequestException as e:
|
|
return {"success": False, "errors": [str(e)]}
|
|
|
|
def get_account_info(self) -> Dict[str, Any]:
|
|
"""Get account information"""
|
|
return self.make_request(f"/accounts/{self.account_id}")
|
|
|
|
def get_zones(self) -> List[Dict[str, Any]]:
|
|
"""Get all zones"""
|
|
result = self.make_request(f"/zones?account.id={self.account_id}&per_page=50")
|
|
return result.get("result", []) if result.get("success") else []
|
|
|
|
def get_zone_analytics(self, zone_id: str) -> Dict[str, Any]:
|
|
"""Get zone analytics for the last hour"""
|
|
since = (datetime.now() - timedelta(hours=1)).isoformat()
|
|
return self.make_request(f"/zones/{zone_id}/analytics/dashboard?since={since}")
|
|
|
|
def get_waf_rules(self, zone_id: str) -> List[Dict[str, Any]]:
|
|
"""Get WAF rules for a zone"""
|
|
result = self.make_request(f"/zones/{zone_id}/firewall/waf/packages")
|
|
if result.get("success"):
|
|
packages = result.get("result", [])
|
|
rules = []
|
|
for package in packages:
|
|
rules_result = self.make_request(
|
|
f"/zones/{zone_id}/firewall/waf/packages/{package['id']}/rules"
|
|
)
|
|
if rules_result.get("success"):
|
|
rules.extend(rules_result.get("result", []))
|
|
return rules
|
|
return []
|
|
|
|
def get_tunnels(self) -> List[Dict[str, Any]]:
|
|
"""Get Cloudflare Tunnels"""
|
|
result = self.make_request(f"/accounts/{self.account_id}/cfd_tunnel")
|
|
return result.get("result", []) if result.get("success") else []
|
|
|
|
def get_dns_records(self, zone_id: str) -> List[Dict[str, Any]]:
|
|
"""Get DNS records for a zone"""
|
|
result = self.make_request(f"/zones/{zone_id}/dns_records?per_page=100")
|
|
return result.get("result", []) if result.get("success") else []
|
|
|
|
def get_health_status(self) -> Dict[str, Any]:
|
|
"""Get overall health status"""
|
|
status = "healthy"
|
|
issues = []
|
|
|
|
# Check zones
|
|
zones = self.get_zones()
|
|
if not zones:
|
|
issues.append("No zones found")
|
|
status = "warning"
|
|
|
|
# Check account access
|
|
account_info = self.get_account_info()
|
|
if not account_info.get("success"):
|
|
issues.append("Account access failed")
|
|
status = "critical"
|
|
|
|
return {"status": status, "issues": issues}
|
|
|
|
|
|
def format_table(data: List[Dict[str, Any]], headers: List[str]) -> str:
|
|
"""Format data as a table"""
|
|
if not data:
|
|
return "No data available"
|
|
|
|
# Calculate column widths
|
|
col_widths = [len(header) for header in headers]
|
|
for row in data:
|
|
for i, header in enumerate(headers):
|
|
value = str(row.get(header, ""))
|
|
col_widths[i] = max(col_widths[i], len(value))
|
|
|
|
# Create header row
|
|
header_row = " | ".join(
|
|
header.ljust(col_widths[i]) for i, header in enumerate(headers)
|
|
)
|
|
separator = "-" * len(header_row)
|
|
|
|
# Create data rows
|
|
rows = [header_row, separator]
|
|
for row in data:
|
|
row_data = [
|
|
str(row.get(header, "")).ljust(col_widths[i])
|
|
for i, header in enumerate(headers)
|
|
]
|
|
rows.append(" | ".join(row_data))
|
|
|
|
return "\n".join(rows)
|
|
|
|
|
|
def main():
|
|
print("🌐 Cloudflare Infrastructure Monitoring Dashboard")
|
|
print("=" * 60)
|
|
print(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
print()
|
|
|
|
try:
|
|
monitor = CloudflareMonitor()
|
|
|
|
# Health check
|
|
print("🔍 Health Status")
|
|
print("-" * 30)
|
|
health = monitor.get_health_status()
|
|
status_emoji = {"healthy": "✅", "warning": "⚠️", "critical": "❌"}
|
|
print(
|
|
f"Status: {status_emoji.get(health['status'], '❓')} {health['status'].upper()}"
|
|
)
|
|
if health["issues"]:
|
|
for issue in health["issues"]:
|
|
print(f" - {issue}")
|
|
print()
|
|
|
|
# Account information
|
|
print("🏢 Account Information")
|
|
print("-" * 30)
|
|
account_info = monitor.get_account_info()
|
|
if account_info.get("success"):
|
|
account = account_info["result"]
|
|
print(f"Name: {account.get('name', 'N/A')}")
|
|
print(f"Type: {account.get('type', 'N/A')}")
|
|
print(f"Created: {account.get('created_on', 'N/A')}")
|
|
else:
|
|
print("Failed to retrieve account information")
|
|
print()
|
|
|
|
# Zones overview
|
|
print("🌐 Zones Overview")
|
|
print("-" * 30)
|
|
zones = monitor.get_zones()
|
|
zone_data = []
|
|
for zone in zones[:10]: # Limit to first 10 zones
|
|
zone_data.append(
|
|
{
|
|
"Name": zone.get("name", "N/A"),
|
|
"Status": zone.get("status", "N/A"),
|
|
"Plan": zone.get("plan", {}).get("name", "N/A"),
|
|
"Development": zone.get("development_mode", "N/A"),
|
|
}
|
|
)
|
|
|
|
print(format_table(zone_data, ["Name", "Status", "Plan", "Development"]))
|
|
print(f"Total zones: {len(zones)}")
|
|
print()
|
|
|
|
# DNS Records (for first zone)
|
|
dns_records = []
|
|
waf_rules = []
|
|
|
|
if zones:
|
|
first_zone = zones[0]
|
|
print("📋 DNS Records (First Zone)")
|
|
print("-" * 30)
|
|
dns_records = monitor.get_dns_records(first_zone["id"])
|
|
dns_data = []
|
|
for record in dns_records[:15]: # Limit to first 15 records
|
|
dns_data.append(
|
|
{
|
|
"Type": record.get("type", "N/A"),
|
|
"Name": record.get("name", "N/A"),
|
|
"Content": record.get("content", "N/A")[:40] + "..."
|
|
if len(record.get("content", "")) > 40
|
|
else record.get("content", "N/A"),
|
|
}
|
|
)
|
|
|
|
print(format_table(dns_data, ["Type", "Name", "Content"]))
|
|
print(f"Total DNS records: {len(dns_records)}")
|
|
print()
|
|
|
|
# Tunnels
|
|
print("🔗 Cloudflare Tunnels")
|
|
print("-" * 30)
|
|
tunnels = monitor.get_tunnels()
|
|
tunnel_data = []
|
|
for tunnel in tunnels:
|
|
tunnel_data.append(
|
|
{
|
|
"Name": tunnel.get("name", "N/A"),
|
|
"Status": tunnel.get("status", "N/A"),
|
|
"Connections": len(tunnel.get("connections", [])),
|
|
}
|
|
)
|
|
|
|
print(format_table(tunnel_data, ["Name", "Status", "Connections"]))
|
|
print(f"Total tunnels: {len(tunnels)}")
|
|
print()
|
|
|
|
# WAF Rules (for first zone)
|
|
if zones:
|
|
first_zone = zones[0]
|
|
print("🛡️ WAF Rules (First Zone)")
|
|
print("-" * 30)
|
|
waf_rules = monitor.get_waf_rules(first_zone["id"])
|
|
waf_data = []
|
|
for rule in waf_rules[:10]: # Limit to first 10 rules
|
|
waf_data.append(
|
|
{
|
|
"ID": rule.get("id", "N/A"),
|
|
"Description": rule.get("description", "N/A")[:50] + "..."
|
|
if len(rule.get("description", "")) > 50
|
|
else rule.get("description", "N/A"),
|
|
"Mode": rule.get("mode", "N/A"),
|
|
}
|
|
)
|
|
|
|
print(format_table(waf_data, ["ID", "Description", "Mode"]))
|
|
print(f"Total WAF rules: {len(waf_rules)}")
|
|
print()
|
|
|
|
# Summary
|
|
print("📊 Summary")
|
|
print("-" * 30)
|
|
print(f"Zones: {len(zones)}")
|
|
print(f"Tunnels: {len(tunnels)}")
|
|
if zones:
|
|
print(f"DNS Records (first zone): {len(dns_records)}")
|
|
print(f"WAF Rules (first zone): {len(waf_rules)}")
|
|
|
|
except Exception as e:
|
|
print(f"❌ Error: {e}")
|
|
print("Please ensure your Cloudflare credentials are properly configured.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|