Initial commit
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Comprehensive Cloudflare configuration checker.
|
||||
|
||||
This script diagnoses common Cloudflare issues including:
|
||||
- SSL/TLS mode mismatches
|
||||
- DNS configuration problems
|
||||
- Cache settings
|
||||
- Page rules and redirect loops
|
||||
|
||||
Requires:
|
||||
- requests library
|
||||
- Cloudflare API credentials (email + Global API Key OR API Token)
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
|
||||
def check_ssl_configuration(
|
||||
zone_id: str, email: str, api_key: str
|
||||
) -> Tuple[bool, List[str]]:
|
||||
"""
|
||||
Check SSL/TLS configuration for common issues.
|
||||
|
||||
Returns: (has_issues, issues_list)
|
||||
"""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
return True, ["Error: 'requests' library not installed. Install with: pip install requests"]
|
||||
|
||||
issues = []
|
||||
|
||||
# Get SSL mode
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.cloudflare.com/client/v4/zones/{zone_id}/settings/ssl",
|
||||
headers={"X-Auth-Email": email, "X-Auth-Key": api_key},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
return True, [f"API Error: {response.status_code} - {response.text}"]
|
||||
|
||||
data = response.json()
|
||||
if not data.get("success"):
|
||||
return True, [f"API Error: {data.get('errors', 'Unknown error')}"]
|
||||
|
||||
ssl_mode = data["result"]["value"]
|
||||
|
||||
# Check Always Use HTTPS setting
|
||||
https_response = requests.get(
|
||||
f"https://api.cloudflare.com/client/v4/zones/{zone_id}/settings/always_use_https",
|
||||
headers={"X-Auth-Email": email, "X-Auth-Key": api_key},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
always_https = "off"
|
||||
if https_response.ok:
|
||||
https_data = https_response.json()
|
||||
if https_data.get("success"):
|
||||
always_https = https_data["result"]["value"]
|
||||
|
||||
# Analyze configuration
|
||||
if ssl_mode == "flexible":
|
||||
issues.append(
|
||||
f"⚠️ SSL Mode is 'flexible' - this can cause redirect loops if your origin "
|
||||
f"server enforces HTTPS (common with GitHub Pages, Netlify, Vercel, etc.)"
|
||||
)
|
||||
issues.append(
|
||||
" Recommendation: Change SSL mode to 'full' or 'full (strict)' if your "
|
||||
"origin supports HTTPS"
|
||||
)
|
||||
|
||||
if ssl_mode == "off":
|
||||
issues.append("⚠️ SSL is disabled - visitors will see 'Not Secure' warnings")
|
||||
|
||||
# Report current configuration
|
||||
if not issues:
|
||||
issues.append(f"✅ SSL Mode: {ssl_mode} - Configuration looks good")
|
||||
issues.append(f" Always Use HTTPS: {always_https}")
|
||||
|
||||
return len([i for i in issues if i.startswith("⚠️")]) > 0, issues
|
||||
|
||||
except requests.RequestException as e:
|
||||
return True, [f"Network Error: {str(e)}"]
|
||||
except Exception as e:
|
||||
return True, [f"Unexpected Error: {str(e)}"]
|
||||
|
||||
|
||||
def check_dns_records(
|
||||
zone_id: str, domain: str, email: str, api_key: str
|
||||
) -> Tuple[bool, List[str]]:
|
||||
"""
|
||||
Check DNS configuration for common issues.
|
||||
|
||||
Returns: (has_issues, issues_list)
|
||||
"""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
return True, ["Error: 'requests' library not installed"]
|
||||
|
||||
issues = []
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records",
|
||||
headers={"X-Auth-Email": email, "X-Auth-Key": api_key},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
return True, [f"API Error: {response.status_code}"]
|
||||
|
||||
data = response.json()
|
||||
if not data.get("success"):
|
||||
return True, [f"API Error: {data.get('errors')}"]
|
||||
|
||||
records = data["result"]
|
||||
|
||||
# Check for root domain records
|
||||
root_records = [r for r in records if r["name"] == domain]
|
||||
if not root_records:
|
||||
issues.append(f"⚠️ No DNS records found for root domain '{domain}'")
|
||||
|
||||
# Check proxy status
|
||||
proxied_records = [r for r in records if r.get("proxied")]
|
||||
if proxied_records:
|
||||
issues.append(f"✅ Found {len(proxied_records)} proxied record(s) (Cloudflare CDN enabled)")
|
||||
|
||||
unproxied_important = [
|
||||
r for r in records
|
||||
if not r.get("proxied") and r["type"] in ["A", "AAAA", "CNAME"]
|
||||
]
|
||||
if unproxied_important:
|
||||
issues.append(
|
||||
f"ℹ️ Found {len(unproxied_important)} non-proxied record(s) - "
|
||||
"these bypass Cloudflare's CDN and security features"
|
||||
)
|
||||
|
||||
return len([i for i in issues if i.startswith("⚠️")]) > 0, issues
|
||||
|
||||
except Exception as e:
|
||||
return True, [f"Error checking DNS: {str(e)}"]
|
||||
|
||||
|
||||
def check_page_rules(
|
||||
zone_id: str, email: str, api_key: str
|
||||
) -> Tuple[bool, List[str]]:
|
||||
"""
|
||||
Check Page Rules for potential redirect loops.
|
||||
|
||||
Returns: (has_issues, issues_list)
|
||||
"""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
return True, ["Error: 'requests' library not installed"]
|
||||
|
||||
issues = []
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.cloudflare.com/client/v4/zones/{zone_id}/pagerules",
|
||||
headers={"X-Auth-Email": email, "X-Auth-Key": api_key},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
return True, [f"API Error: {response.status_code}"]
|
||||
|
||||
data = response.json()
|
||||
if not data.get("success"):
|
||||
return True, [f"API Error: {data.get('errors')}"]
|
||||
|
||||
rules = data["result"]
|
||||
|
||||
if not rules:
|
||||
issues.append("✅ No Page Rules configured")
|
||||
return False, issues
|
||||
|
||||
# Check for redirect rules
|
||||
redirect_rules = []
|
||||
for rule in rules:
|
||||
actions = rule.get("actions", [])
|
||||
for action in actions:
|
||||
if action.get("id") in ["forwarding_url", "always_use_https"]:
|
||||
redirect_rules.append({
|
||||
"url": rule.get("targets", [{}])[0].get("constraint", {}).get("value"),
|
||||
"action": action.get("id"),
|
||||
"status": rule.get("status")
|
||||
})
|
||||
|
||||
if redirect_rules:
|
||||
issues.append(f"ℹ️ Found {len(redirect_rules)} redirect Page Rule(s):")
|
||||
for r in redirect_rules:
|
||||
issues.append(f" - {r['url']}: {r['action']} (status: {r['status']})")
|
||||
issues.append(
|
||||
" Note: Conflicting redirect rules can cause redirect loops"
|
||||
)
|
||||
else:
|
||||
issues.append("✅ No redirect Page Rules found")
|
||||
|
||||
return False, issues
|
||||
|
||||
except Exception as e:
|
||||
return True, [f"Error checking Page Rules: {str(e)}"]
|
||||
|
||||
|
||||
def main():
|
||||
"""Main diagnostic function."""
|
||||
if len(sys.argv) < 4:
|
||||
print("Usage: python check_cloudflare_config.py <domain> <email> <api_key>")
|
||||
print("\nExample:")
|
||||
print(" python check_cloudflare_config.py typeof.tech user@example.com abc123...")
|
||||
print("\nGet your Global API Key from:")
|
||||
print(" https://dash.cloudflare.com/profile/api-tokens")
|
||||
sys.exit(1)
|
||||
|
||||
domain = sys.argv[1]
|
||||
email = sys.argv[2]
|
||||
api_key = sys.argv[3]
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("Error: 'requests' library not found")
|
||||
print("Install with: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\n🔍 Checking Cloudflare configuration for: {domain}\n")
|
||||
print("=" * 60)
|
||||
|
||||
# Get zone ID
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.cloudflare.com/client/v4/zones?name={domain}",
|
||||
headers={"X-Auth-Email": email, "X-Auth-Key": api_key},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
print(f"❌ API Error: {response.status_code}")
|
||||
sys.exit(1)
|
||||
|
||||
data = response.json()
|
||||
if not data.get("success") or not data.get("result"):
|
||||
print(f"❌ Domain '{domain}' not found in your Cloudflare account")
|
||||
sys.exit(1)
|
||||
|
||||
zone_id = data["result"][0]["id"]
|
||||
print(f"✅ Found zone: {domain} (ID: {zone_id})\n")
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ Network error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Run all checks
|
||||
all_issues = []
|
||||
|
||||
print("\n📋 SSL/TLS Configuration:")
|
||||
print("-" * 60)
|
||||
has_ssl_issues, ssl_issues = check_ssl_configuration(zone_id, email, api_key)
|
||||
for issue in ssl_issues:
|
||||
print(issue)
|
||||
|
||||
print("\n📋 DNS Configuration:")
|
||||
print("-" * 60)
|
||||
has_dns_issues, dns_issues = check_dns_records(zone_id, domain, email, api_key)
|
||||
for issue in dns_issues:
|
||||
print(issue)
|
||||
|
||||
print("\n📋 Page Rules:")
|
||||
print("-" * 60)
|
||||
has_rules_issues, rules_issues = check_page_rules(zone_id, email, api_key)
|
||||
for issue in rules_issues:
|
||||
print(issue)
|
||||
|
||||
# Summary
|
||||
print("\n" + "=" * 60)
|
||||
if has_ssl_issues or has_dns_issues or has_rules_issues:
|
||||
print("⚠️ Issues found - review the warnings above")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print("✅ No critical issues detected!")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
186
skills/cloudflare-troubleshooting/scripts/fix_ssl_mode.py
Normal file
186
skills/cloudflare-troubleshooting/scripts/fix_ssl_mode.py
Normal file
@@ -0,0 +1,186 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fix Cloudflare SSL/TLS mode to resolve redirect loops.
|
||||
|
||||
This script changes the SSL mode to resolve common redirect loop issues
|
||||
caused by SSL mode mismatches between Cloudflare and origin servers.
|
||||
|
||||
Common scenarios:
|
||||
- GitHub Pages + Flexible mode → Change to Full
|
||||
- Netlify/Vercel + Flexible mode → Change to Full
|
||||
- Any HTTPS-enforcing origin + Flexible mode → Change to Full
|
||||
|
||||
Requires:
|
||||
- requests library
|
||||
- Cloudflare API credentials
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def fix_ssl_mode(zone_id: str, target_mode: str, email: str, api_key: str) -> bool:
|
||||
"""
|
||||
Change SSL mode for a zone.
|
||||
|
||||
Args:
|
||||
zone_id: Cloudflare zone ID
|
||||
target_mode: Target SSL mode ('flexible', 'full', 'full_strict', 'off')
|
||||
email: Cloudflare account email
|
||||
api_key: Cloudflare Global API Key
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("Error: 'requests' library not installed")
|
||||
print("Install with: pip install requests")
|
||||
return False
|
||||
|
||||
# Validate mode
|
||||
valid_modes = ["flexible", "full", "strict", "off"]
|
||||
if target_mode not in valid_modes:
|
||||
print(f"Error: Invalid SSL mode '{target_mode}'")
|
||||
print(f"Valid modes: {', '.join(valid_modes)}")
|
||||
return False
|
||||
|
||||
# Note: API uses 'strict' but documentation calls it 'full (strict)'
|
||||
api_mode = target_mode
|
||||
|
||||
try:
|
||||
response = requests.patch(
|
||||
f"https://api.cloudflare.com/client/v4/zones/{zone_id}/settings/ssl",
|
||||
headers={
|
||||
"X-Auth-Email": email,
|
||||
"X-Auth-Key": api_key,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
json={"value": api_mode},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
print(f"❌ API Error: {response.status_code}")
|
||||
print(f"Response: {response.text}")
|
||||
return False
|
||||
|
||||
data = response.json()
|
||||
|
||||
if not data.get("success"):
|
||||
print(f"❌ Failed to update SSL mode")
|
||||
print(f"Errors: {data.get('errors', 'Unknown error')}")
|
||||
return False
|
||||
|
||||
new_mode = data["result"]["value"]
|
||||
print(f"✅ SSL mode successfully changed to: {new_mode}")
|
||||
print(f"\n⏳ Cloudflare is updating edge servers (typically takes 10-30 seconds)")
|
||||
print(f"💡 Recommendation: Clear your browser cache or use incognito mode to test")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def purge_cache(zone_id: str, email: str, api_key: str) -> bool:
|
||||
"""Purge all Cloudflare cache for the zone."""
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache",
|
||||
headers={
|
||||
"X-Auth-Email": email,
|
||||
"X-Auth-Key": api_key,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
json={"purge_everything": True},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.ok and response.json().get("success"):
|
||||
print("✅ Cache purged successfully")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function."""
|
||||
if len(sys.argv) < 5:
|
||||
print("Usage: python fix_ssl_mode.py <domain> <email> <api_key> <mode> [--purge-cache]")
|
||||
print("\nSSL Modes:")
|
||||
print(" flexible - Cloudflare → Origin uses HTTP (can cause loops with HTTPS origins)")
|
||||
print(" full - Cloudflare → Origin uses HTTPS (recommended for most origins)")
|
||||
print(" strict - Full + validates origin certificate (most secure)")
|
||||
print(" off - No encryption (not recommended)")
|
||||
print("\nExamples:")
|
||||
print(" # Fix redirect loop for GitHub Pages")
|
||||
print(" python fix_ssl_mode.py typeof.tech user@example.com abc123... full --purge-cache")
|
||||
print("\n # Switch to strict mode")
|
||||
print(" python fix_ssl_mode.py example.com user@example.com abc123... strict")
|
||||
sys.exit(1)
|
||||
|
||||
domain = sys.argv[1]
|
||||
email = sys.argv[2]
|
||||
api_key = sys.argv[3]
|
||||
target_mode = sys.argv[4]
|
||||
should_purge = "--purge-cache" in sys.argv
|
||||
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
print("Error: 'requests' library not found")
|
||||
print("Install with: pip install requests")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"\n🔧 Fixing SSL configuration for: {domain}")
|
||||
print("=" * 60)
|
||||
|
||||
# Get zone ID
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.cloudflare.com/client/v4/zones?name={domain}",
|
||||
headers={"X-Auth-Email": email, "X-Auth-Key": api_key},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if not response.ok:
|
||||
print(f"❌ API Error: {response.status_code}")
|
||||
sys.exit(1)
|
||||
|
||||
data = response.json()
|
||||
if not data.get("success") or not data.get("result"):
|
||||
print(f"❌ Domain '{domain}' not found in your Cloudflare account")
|
||||
sys.exit(1)
|
||||
|
||||
zone_id = data["result"][0]["id"]
|
||||
print(f"✅ Found zone: {domain}\n")
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ Network error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Fix SSL mode
|
||||
if not fix_ssl_mode(zone_id, target_mode, email, api_key):
|
||||
sys.exit(1)
|
||||
|
||||
# Optionally purge cache
|
||||
if should_purge:
|
||||
print(f"\n🗑️ Purging cache...")
|
||||
purge_cache(zone_id, email, api_key)
|
||||
|
||||
print("\n✅ Done! Test your site after 30 seconds.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user