Diagnosed azcomputerguru.com 521 errors: Cox's BGP route to specific Cloudflare origin-pull prefixes (162.158.0.0/16, 172.64.0.0/13, 173.245.48.0/20, 141.101.64.0/18) is broken from 72.194.62.0/29. Confirmed by TCP probe matrix from pfSense WAN, traceroute latency comparison, and state-table showing 0 inbound CF connections while direct-internet traffic still reached origin. Deployed Cloudflare Tunnel 'acg-origin' on Jupiter Unraid as a Docker container. Routes 4 proxied hostnames (azcomputerguru.com, analytics., community., radio.) through the tunnel with HTTPS backend to IX 172.16.3.10:443 with per-ingress SNI matching. All 4 hostnames return 200 OK through CF edge after the cutover. Repo hygiene: - Merged clients/ix-server/ into clients/internal-infrastructure/ (IX is internal infra, not a paying-client account). Git detected the session-log files as renames so history is preserved. Updated 4 stale path references in 2 files. - Moved cox-bgp ticket draft out of projects/dataforth-dos/ (wrong project) to clients/internal-infrastructure/vendor-tickets/. - Relocated tunnel-setup helper scripts from projects/dataforth-dos/datasheet-pipeline/implementation/ to clients/internal-infrastructure/scripts/cloudflared-tunnel-setup/. Deleted superseded/abandoned login attempts. Sanitized hardcoded Jupiter/pfSense SSH passwords to pull from SOPS vault at runtime; Cloudflare token reads from env var (tokens still in 1Password, vault entry is metadata-only). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
59 lines
2.2 KiB
Python
59 lines
2.2 KiB
Python
"""Pull CF Analytics via GraphQL to see origin-status per CF PoP."""
|
|
import json, os, sys, urllib.request
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
ZONE = '1beb9917c22b54be32e5215df2c227ce'
|
|
# CF API tokens live in 1Password (vault entry services/cloudflare.sops.yaml
|
|
# currently holds metadata only). Provide via env vars before running.
|
|
TOKENS = {
|
|
'full-dns': os.environ.get('CF_API_TOKEN_FULL_DNS', ''),
|
|
'legacy': os.environ.get('CF_API_TOKEN_LEGACY', ''),
|
|
}
|
|
|
|
since_30 = (datetime.now(timezone.utc) - timedelta(minutes=30)).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
|
|
QUERY = '''
|
|
query($zone:String!, $since:Time!){
|
|
viewer {
|
|
zones(filter:{zoneTag:$zone}){
|
|
httpRequestsAdaptiveGroups(limit:50, filter:{datetime_geq:$since}, orderBy:[count_DESC]){
|
|
count
|
|
dimensions { coloCode edgeResponseStatus originResponseStatus clientRequestHTTPHost }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
'''
|
|
|
|
def gql(token, query, vars):
|
|
req = urllib.request.Request(
|
|
'https://api.cloudflare.com/client/v4/graphql',
|
|
data=json.dumps({'query': query, 'variables': vars}).encode(),
|
|
headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'},
|
|
)
|
|
with urllib.request.urlopen(req, timeout=30) as r:
|
|
return json.loads(r.read())
|
|
|
|
for name, tok in TOKENS.items():
|
|
print(f'\n===== Trying {name} token =====')
|
|
try:
|
|
r = gql(tok, QUERY, {'zone': ZONE, 'since': since_30})
|
|
if r.get('errors'):
|
|
print('errors:', json.dumps(r['errors'], indent=2)[:600])
|
|
else:
|
|
zones = r.get('data', {}).get('viewer', {}).get('zones', [])
|
|
if not zones:
|
|
print('no zones returned')
|
|
continue
|
|
groups = zones[0].get('httpRequestsAdaptiveGroups', [])
|
|
print(f'{len(groups)} groups returned')
|
|
print(f'{"count":>6} {"colo":5} {"edge":5} {"origin":6} host')
|
|
for g in groups:
|
|
d = g['dimensions']
|
|
print(f"{g['count']:>6} {d.get('coloCode','-'):5} "
|
|
f"{str(d.get('edgeResponseStatus','-')):5} "
|
|
f"{str(d.get('originResponseStatus','-')):6} "
|
|
f"{d.get('clientRequestHTTPHost','-')}")
|
|
except Exception as e:
|
|
print(f'FAIL: {e}')
|