diff --git a/cloudflare/README.md b/cloudflare/README.md new file mode 100644 index 0000000..fc889b4 --- /dev/null +++ b/cloudflare/README.md @@ -0,0 +1,50 @@ +# Cloudflare Rules Export / Import + +`cloudflare_rules.py` exports and imports Cloudflare zone configuration as JSON files into the `cloudflare_rules/` directory. + +## Usage + +```bash +python cloudflare_rules.py export +python cloudflare_rules.py import +``` + +Alternatively, set environment variables and omit the arguments: + +```bash +export CLOUDFLARE_API_TOKEN=your_token +export CLOUDFLARE_ZONE_ID=your_zone_id + +python cloudflare_rules.py export +python cloudflare_rules.py import +``` + +## API Token Requirements + +Create a **zone-scoped** API token (not an account token) in the Cloudflare dashboard under **My Profile > API Tokens > Create Token**. + +Scope the token to the specific zone, with the following permissions: + +| Category | Permission | Required for | +|----------|------------|--------------| +| Zone / DNS | Read | DNS records export/import | +| Zone / Zone Settings | Read | Zone settings export | +| Zone / Zone WAF | Edit | WAF custom rules export/import | +| Zone / Cache Rules | Edit | Cache rules export/import | +| Zone / Page Rules | Edit | Page rules export/import | + +> **Note:** Import operations require Edit permissions; Read is sufficient for export-only use. + +## What is exported + +| File | Contents | +|------|----------| +| `dns_records.json` | All DNS records for the zone | +| `zone_settings.json` | Zone-level settings (SSL mode, security level, etc.) | +| `waf_rules.json` | WAF custom rules | +| `rate_limit_rules.json` | Rate limiting rules | +| `cache_rules.json` | Cache rules | +| `redirect_rules.json` | Dynamic redirect rules | +| `page_rules.json` | Page rules (legacy) | + +Phases with no rules configured are skipped without error. diff --git a/cloudflare/cloudflare_rules.py b/cloudflare/cloudflare_rules.py new file mode 100644 index 0000000..a6a538f --- /dev/null +++ b/cloudflare/cloudflare_rules.py @@ -0,0 +1,341 @@ +#!/usr/bin/env python3 + +""" +Cloudflare Rules Export / Import Script + +Usage: + Export rules: + python cloudflare_rules.py export + + Import rules: + python cloudflare_rules.py import + + Dry-run import (prints payloads without making any changes): + python cloudflare_rules.py import --dry-run + + Alternatively, set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID + environment variables and run: + python cloudflare_rules.py export +""" + +import json +import os +import sys + +import requests + +RULES_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cloudflare_rules") + + +def usage(): + print("Usage: python cloudflare_rules.py {export|import} [API_TOKEN] [ZONE_ID] [--dry-run]") + print() + print("Arguments:") + print(" export/import Operation to perform") + print(" API_TOKEN Cloudflare API Token (optional if CLOUDFLARE_API_TOKEN env var is set)") + print(" ZONE_ID Cloudflare Zone ID (optional if CLOUDFLARE_ZONE_ID env var is set)") + print(" --dry-run Print what would be sent without making any changes (import only)") + sys.exit(1) + + +def call_cloudflare(method, url, api_token, output_file=None, data_file=None, description="", dry_run=False): + print(f"{description}...") + + headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + } + + data = None + if data_file: + if not os.path.isfile(data_file): + print(f"Error: Data file {data_file} not found. Skipping {description}.") + return False + with open(data_file, "r") as f: + data = f.read() + + if dry_run: + print(f" [DRY RUN] {method} {url}") + if data: + parsed = json.loads(data) + print(f" Payload: {json.dumps(parsed, indent=2)}") + return True + + response = requests.request(method, url, headers=headers, data=data) + + if response.status_code == 404: + try: + errors = response.json().get("errors", []) + if any(e.get("code") == 10003 for e in errors): + print(f" No rules present, skipping.") + return True + except Exception: + pass + + if response.status_code < 200 or response.status_code > 299: + print(f"Error: {description} failed with HTTP status {response.status_code}") + if response.text: + print(f"Response: {response.text}") + return False + + if output_file: + with open(output_file, "w") as f: + f.write(response.text) + + return True + + +def export_dns_records(api_token, zone_id): + print("Exporting DNS Records...") + url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" + headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + } + + all_records = [] + page = 1 + while True: + response = requests.get(url, headers=headers, params={"page": page, "per_page": 100}) + if response.status_code < 200 or response.status_code > 299: + print(f"Error: Exporting DNS Records failed with HTTP status {response.status_code}") + if response.text: + print(f"Response: {response.text}") + return False + + data = response.json() + all_records.extend(data.get("result", [])) + + result_info = data.get("result_info", {}) + if page >= result_info.get("total_pages", 1): + break + page += 1 + + with open(os.path.join(RULES_DIR, "dns_records.json"), "w") as f: + json.dump({"result": all_records}, f, indent=2) + + print(f" Exported {len(all_records)} DNS records.") + return True + + +def import_dns_records(api_token, zone_id, dry_run=False): + print("Importing DNS Records...") + dns_file = os.path.join(RULES_DIR, "dns_records.json") + if not os.path.isfile(dns_file): + print(f"Error: Data file {dns_file} not found. Skipping DNS Records import.") + return False + + with open(dns_file, "r") as f: + data = json.load(f) + + records = data.get("result", []) + if not records: + print("No DNS records found to import.") + return True + + url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records" + headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + } + + exclude_fields = {"id", "zone_id", "zone_name", "created_on", "modified_on", "meta"} + + failed = False + for i, record in enumerate(records): + payload = {k: v for k, v in record.items() if k not in exclude_fields} + if dry_run: + print(f" [DRY RUN] POST {url}") + print(f" Payload: {json.dumps(payload, indent=2)}") + continue + response = requests.post(url, headers=headers, json=payload) + if response.status_code < 200 or response.status_code > 299: + print(f"Error: Importing DNS record {i} ({record.get('name', '')}) failed with HTTP status {response.status_code}") + if response.text: + print(f"Response: {response.text}") + failed = True + + if failed: + return False + + print("DNS Records imported successfully." if not dry_run else f" {len(records)} DNS records would be imported.") + return True + + +def import_zone_settings(api_token, zone_id, dry_run=False): + print("Importing Zone Settings...") + settings_file = os.path.join(RULES_DIR, "zone_settings.json") + if not os.path.isfile(settings_file): + print(f"Error: Data file {settings_file} not found. Skipping Zone Settings import.") + return False + + with open(settings_file, "r") as f: + data = json.load(f) + + items = [ + {"id": s["id"], "value": s["value"]} + for s in data.get("result", []) + if s.get("editable", False) + ] + + if not items: + print("No editable zone settings found to import.") + return True + + url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/settings" + headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + } + + if dry_run: + print(f" [DRY RUN] PATCH {url}") + print(f" Payload: {json.dumps({'items': items}, indent=2)}") + return True + + response = requests.patch(url, headers=headers, json={"items": items}) + if response.status_code < 200 or response.status_code > 299: + print(f"Error: Importing Zone Settings failed with HTTP status {response.status_code}") + if response.text: + print(f"Response: {response.text}") + return False + + print("Zone Settings imported successfully.") + return True + + +def export_rules(api_token, zone_id): + base_url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/rulesets" + pagerules_url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/pagerules" + + os.makedirs(RULES_DIR, exist_ok=True) + + failed = False + if not export_dns_records(api_token, zone_id): + failed = True + if not call_cloudflare("GET", f"https://api.cloudflare.com/client/v4/zones/{zone_id}/settings", api_token, output_file=os.path.join(RULES_DIR, "zone_settings.json"), description="Exporting Zone Settings"): + failed = True + if not call_cloudflare("GET", f"{base_url}/phases/http_request_firewall_custom/entrypoint", api_token, output_file=os.path.join(RULES_DIR, "waf_rules.json"), description="Exporting WAF Custom Rules"): + failed = True + if not call_cloudflare("GET", f"{base_url}/phases/http_ratelimit/entrypoint", api_token, output_file=os.path.join(RULES_DIR, "rate_limit_rules.json"), description="Exporting Rate Limiting Rules"): + failed = True + if not call_cloudflare("GET", f"{base_url}/phases/http_request_cache_settings/entrypoint", api_token, output_file=os.path.join(RULES_DIR, "cache_rules.json"), description="Exporting Cache Rules"): + failed = True + if not call_cloudflare("GET", f"{base_url}/phases/http_request_dynamic_redirect/entrypoint", api_token, output_file=os.path.join(RULES_DIR, "redirect_rules.json"), description="Exporting Redirect Rules"): + failed = True + if not call_cloudflare("GET", pagerules_url, api_token, output_file=os.path.join(RULES_DIR, "page_rules.json"), description="Exporting Page Rules"): + failed = True + + if not failed: + print(f"Export completed successfully. JSON files saved in {RULES_DIR}.") + else: + print("Export completed with errors.") + sys.exit(1) + + +def import_page_rules(api_token, zone_id, dry_run=False): + pagerules_url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/pagerules" + + print("Importing Page Rules...") + page_rules_file = os.path.join(RULES_DIR, "page_rules.json") + if not os.path.isfile(page_rules_file): + print(f"Error: Data file {page_rules_file} not found. Skipping Page Rules import.") + return False + + with open(page_rules_file, "r") as f: + data = json.load(f) + + rules = data.get("result", []) + if not rules: + print("No page rules found to import.") + return True + + headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + } + + page_failed = False + for i, rule in enumerate(rules): + payload = { + "targets": rule["targets"], + "actions": rule["actions"], + "priority": rule.get("priority", i + 1), + "status": rule.get("status", "active"), + } + + if dry_run: + print(f" [DRY RUN] POST {pagerules_url}") + print(f" Payload: {json.dumps(payload, indent=2)}") + continue + + response = requests.post(pagerules_url, headers=headers, json=payload) + + if response.status_code < 200 or response.status_code > 299: + print(f"Error: Importing Page Rule {i} failed with HTTP status {response.status_code}") + if response.text: + print(f"Response: {response.text}") + page_failed = True + + if page_failed: + return False + + print("Page Rules imported successfully." if not dry_run else f" {len(rules)} page rules would be imported.") + return True + + +def import_rules(api_token, zone_id, dry_run=False): + base_url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/rulesets" + + if dry_run: + print("--- DRY RUN: no changes will be made ---") + + failed = False + if not import_dns_records(api_token, zone_id, dry_run=dry_run): + failed = True + if not import_zone_settings(api_token, zone_id, dry_run=dry_run): + failed = True + if not call_cloudflare("PUT", f"{base_url}/phases/http_request_firewall_custom/entrypoint", api_token, data_file=os.path.join(RULES_DIR, "waf_rules.json"), description="Importing WAF Custom Rules", dry_run=dry_run): + failed = True + if not call_cloudflare("PUT", f"{base_url}/phases/http_ratelimit/entrypoint", api_token, data_file=os.path.join(RULES_DIR, "rate_limit_rules.json"), description="Importing Rate Limiting Rules", dry_run=dry_run): + failed = True + if not call_cloudflare("PUT", f"{base_url}/phases/http_request_cache_settings/entrypoint", api_token, data_file=os.path.join(RULES_DIR, "cache_rules.json"), description="Importing Cache Rules", dry_run=dry_run): + failed = True + if not call_cloudflare("PUT", f"{base_url}/phases/http_request_dynamic_redirect/entrypoint", api_token, data_file=os.path.join(RULES_DIR, "redirect_rules.json"), description="Importing Redirect Rules", dry_run=dry_run): + failed = True + if not import_page_rules(api_token, zone_id, dry_run=dry_run): + failed = True + + if not failed: + print("Dry run completed successfully." if dry_run else "Import completed successfully.") + else: + print("Dry run completed with errors." if dry_run else "Import completed with errors.") + sys.exit(1) + + +def main(): + if len(sys.argv) < 2: + usage() + + args = sys.argv[1:] + dry_run = "--dry-run" in args + args = [a for a in args if a != "--dry-run"] + + operation = args[0] if args else None + api_token = args[1] if len(args) > 1 else os.environ.get("CLOUDFLARE_API_TOKEN", "") + zone_id = args[2] if len(args) > 2 else os.environ.get("CLOUDFLARE_ZONE_ID", "") + + if not api_token or not zone_id: + print("Error: API_TOKEN and ZONE_ID must be provided either as arguments or environment variables.") + usage() + + if operation == "export": + export_rules(api_token, zone_id) + elif operation == "import": + import_rules(api_token, zone_id, dry_run=dry_run) + else: + usage() + + +if __name__ == "__main__": + main() diff --git a/cloudflare/cloudflare_rules/cache_rules.json b/cloudflare/cloudflare_rules/cache_rules.json new file mode 100644 index 0000000..bcb1286 --- /dev/null +++ b/cloudflare/cloudflare_rules/cache_rules.json @@ -0,0 +1,10 @@ +{ + "result": null, + "success": false, + "errors": [ + { + "message": "request is not authorized" + } + ], + "messages": [] +} diff --git a/cloudflare/cloudflare_rules/dns_records.json b/cloudflare/cloudflare_rules/dns_records.json new file mode 100644 index 0000000..fe97801 --- /dev/null +++ b/cloudflare/cloudflare_rules/dns_records.json @@ -0,0 +1,272 @@ +{ + "result": [ + { + "id": "010ff13a3227891727529ac477d9ff71", + "name": "blog.doaj.org", + "type": "A", + "content": "18.171.142.0", + "proxiable": true, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": "Switchover to new blog 2024-03-27 doajPM issue 3846, proxied 2025-04-29", + "tags": [], + "created_on": "2024-03-27T15:36:53.800738Z", + "modified_on": "2025-07-09T12:39:38.660026Z", + "comment_modified_on": "2025-04-29T10:20:21.335214Z" + }, + { + "id": "fb21fa6cb2e87eeb25a7579796f17655", + "name": "doaj.org", + "type": "A", + "content": "46.101.64.14", + "proxiable": true, + "proxied": true, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-27T22:26:14.802855Z", + "modified_on": "2019-02-27T22:26:14.802855Z" + }, + { + "id": "4fc4850bf40ccd8039d32d34632704c8", + "name": "www.doaj.org", + "type": "A", + "content": "46.101.64.14", + "proxiable": true, + "proxied": true, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": "Used to do cloudflare www redirect via bulk redirect list", + "tags": [], + "created_on": "2019-02-27T22:26:18.0461Z", + "modified_on": "2025-01-13T21:02:09.249335Z", + "comment_modified_on": "2025-01-13T11:09:09.64401Z" + }, + { + "id": "8f98d18d384f9a4729b2b8f0df353bbb", + "name": "email.doaj.org", + "type": "CNAME", + "content": "mailgun.org", + "proxiable": true, + "proxied": false, + "ttl": 1, + "settings": { + "flatten_cname": false + }, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.248173Z", + "modified_on": "2019-02-26T15:02:43.248173Z" + }, + { + "id": "fd2dafef4fe11b8d78159dff95376fdf", + "name": "doaj.org", + "type": "MX", + "content": "alt2.aspmx.l.google.com", + "priority": 5, + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.373301Z", + "modified_on": "2019-02-26T15:02:43.373301Z" + }, + { + "id": "1c0dd5cff95b2dc44953f9bba40d8dfd", + "name": "doaj.org", + "type": "MX", + "content": "smtp1.speednames.com", + "priority": 10, + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.361906Z", + "modified_on": "2019-02-26T15:02:43.361906Z" + }, + { + "id": "24b7d3f6451151ab963679a6fa8aec19", + "name": "doaj.org", + "type": "MX", + "content": "aspmx2.googlemail.com", + "priority": 10, + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.342434Z", + "modified_on": "2019-02-26T15:02:43.342434Z" + }, + { + "id": "47952c2e566b0b2a3b7dacc719bfa15a", + "name": "doaj.org", + "type": "MX", + "content": "alt1.aspmx.l.google.com", + "priority": 5, + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.33171Z", + "modified_on": "2019-02-26T15:02:43.33171Z" + }, + { + "id": "d8a8b6884e97e1171cad843538fbece8", + "name": "doaj.org", + "type": "MX", + "content": "smtp2.speednames.com", + "priority": 20, + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.319384Z", + "modified_on": "2019-02-26T15:02:43.319384Z" + }, + { + "id": "2af6bb3bf11ef9ef0eeb7945851ada48", + "name": "doaj.org", + "type": "MX", + "content": "aspmx.l.google.com", + "priority": 1, + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.308062Z", + "modified_on": "2019-02-26T15:02:43.308062Z" + }, + { + "id": "4523184de998c52bc87c1987f18e1543", + "name": "doaj.org", + "type": "MX", + "content": "aspmx3.googlemail.com", + "priority": 10, + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.296877Z", + "modified_on": "2019-02-26T15:02:43.296877Z" + }, + { + "id": "71180160f87c9d642dc302c6f57a2d01", + "name": "_acme-challenge.blog.doaj.org", + "type": "TXT", + "content": "pcZDkDRi5AtIJtbAHSmpTKP9fTwc6EmAQaY4eKZeCy4", + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": "doajPM issue 3486", + "tags": [], + "created_on": "2024-03-27T15:16:36.475316Z", + "modified_on": "2024-03-27T15:16:36.475316Z", + "comment_modified_on": "2024-03-27T15:16:36.475316Z" + }, + { + "id": "480212615d3b0a2985eaa78e965a0e2d", + "name": "_acme-challenge.blog.doaj.org", + "type": "TXT", + "content": "bf1DyW8R3lyBaOr5VVezL7qZdJqiLe8GMfOsg3E7fM4", + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": "doajPM issue 3486", + "tags": [], + "created_on": "2024-03-21T10:30:25.829805Z", + "modified_on": "2024-03-27T15:11:56.224944Z", + "comment_modified_on": "2024-03-21T10:30:25.829805Z" + }, + { + "id": "b142b1fd166956cfa6021156eabd5806", + "name": "doaj.org", + "type": "TXT", + "content": "google-site-verification=hvwBB9ZfLa1qG4dzyFBmhdJnyjoJbe9urPyo5ZQgxGY", + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-04-03T06:08:47.124548Z", + "modified_on": "2019-04-03T06:08:47.124548Z" + }, + { + "id": "5cc6d84b226699c0dcc36153bd3d7252", + "name": "doaj.org", + "type": "TXT", + "content": "v=spf1 include:_spf.google.com include:mailgun.org ~all", + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.266635Z", + "modified_on": "2019-02-26T15:02:43.266635Z" + }, + { + "id": "ec1e560ad017cbb26da6b5731ff2bdff", + "name": "google._domainkey.doaj.org", + "type": "TXT", + "content": "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFP2HR8yqu8RdKyJ/JOglCsFzTYtlwZX3vpEnuJkJPEtNoS7ambJuj2HsHF542suy+6UROUklDeIQJQhZqeigHDC8yFRWZXv9oqVmysHud/I8ZL+6BiifqjSm90GxJRqSkqVVsmtj1Tll18Id6kSfTI5Cx95Mx06AiGZcovKf5fQIDAQAB", + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.386761Z", + "modified_on": "2019-02-26T15:02:43.386761Z" + }, + { + "id": "cc74bdea3fcbcdadd11a0006cd889495", + "name": "krs._domainkey.doaj.org", + "type": "TXT", + "content": "k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3mdBqN0OAyWHLzimgkV3b06/iRJa1EgsQ8tUsO/+jTuftaYfUBpQCM/0aUcPR+4ctKU4RRlI6dVCyV6j4XbSrAxxbitTKsxi8Nuo2pZkHJAfZGlBJlpiZ1+i9XD4tEJK4htnOma0Tinl6+S/LjK9hC+VGDm8IcJtlMTNOPzbP7QIDAQAB", + "proxiable": false, + "proxied": false, + "ttl": 1, + "settings": {}, + "meta": {}, + "comment": null, + "tags": [], + "created_on": "2019-02-26T15:02:43.399323Z", + "modified_on": "2019-02-26T15:02:43.399323Z" + } + ] +} \ No newline at end of file diff --git a/cloudflare/cloudflare_rules/page_rules.json b/cloudflare/cloudflare_rules/page_rules.json new file mode 100644 index 0000000..ac5e026 --- /dev/null +++ b/cloudflare/cloudflare_rules/page_rules.json @@ -0,0 +1 @@ +{"result":[{"id":"380581670412fcfb02a75e0f049f84fe","targets":[{"target":"url","constraint":{"operator":"matches","value":"*doaj.org\/oai*"}}],"actions":[{"id":"browser_check","value":"off"},{"id":"security_level","value":"essentially_off"},{"id":"cache_level","value":"bypass"}],"priority":3,"status":"active","created_on":"2019-04-03T06:14:26.000000Z","modified_on":"2022-07-14T12:07:36.000000Z"},{"id":"56386c243bba2ac9c67cb21173da335e","targets":[{"target":"url","constraint":{"operator":"matches","value":"*doaj.org\/api*"}}],"actions":[{"id":"browser_check","value":"off"},{"id":"security_level","value":"essentially_off"},{"id":"cache_level","value":"bypass"}],"priority":2,"status":"active","created_on":"2019-02-26T15:20:15.000000Z","modified_on":"2022-07-15T09:47:38.000000Z"},{"id":"8ed1bac74f92fef5bada7e4091897453","targets":[{"target":"url","constraint":{"operator":"matches","value":"*doaj.org\/query*"}}],"actions":[{"id":"browser_cache_ttl","value":1800},{"id":"cache_level","value":"cache_everything"}],"priority":1,"status":"active","created_on":"2019-02-26T15:21:39.000000Z","modified_on":"2019-02-27T22:27:21.000000Z"}],"success":true,"errors":[],"messages":[]} \ No newline at end of file diff --git a/cloudflare/cloudflare_rules/rate_limit_rules.json b/cloudflare/cloudflare_rules/rate_limit_rules.json new file mode 100644 index 0000000..30fbbfa --- /dev/null +++ b/cloudflare/cloudflare_rules/rate_limit_rules.json @@ -0,0 +1,36 @@ +{ + "result": { + "description": "", + "id": "ffbc4d7fcdae4cd3a36aaaa9ac921483", + "kind": "zone", + "last_updated": "2025-07-09T09:18:41.981517Z", + "name": "default", + "phase": "http_ratelimit", + "rules": [ + { + "action": "block", + "description": "Leaked credential check", + "enabled": true, + "expression": "(cf.waf.credential_check.password_leaked)", + "id": "e254b99c19e746ce9b0d9e33eaff8992", + "last_updated": "2025-07-09T09:18:41.981517Z", + "ratelimit": { + "characteristics": [ + "ip.src", + "cf.colo.id" + ], + "mitigation_timeout": 3600, + "period": 10, + "requests_per_period": 5 + }, + "ref": "e254b99c19e746ce9b0d9e33eaff8992", + "version": "1" + } + ], + "source": "rate_limit", + "version": "1" + }, + "success": true, + "errors": [], + "messages": [] +} diff --git a/cloudflare/cloudflare_rules/redirect_rules.json b/cloudflare/cloudflare_rules/redirect_rules.json new file mode 100644 index 0000000..fc06c29 --- /dev/null +++ b/cloudflare/cloudflare_rules/redirect_rules.json @@ -0,0 +1,10 @@ +{ + "result": null, + "success": false, + "errors": [ + { + "message": "phase \"http_request_redirect\" not allowed at zone level" + } + ], + "messages": [] +} diff --git a/cloudflare/cloudflare_rules/waf_rules.json b/cloudflare/cloudflare_rules/waf_rules.json new file mode 100644 index 0000000..c066812 --- /dev/null +++ b/cloudflare/cloudflare_rules/waf_rules.json @@ -0,0 +1,35 @@ +{ + "result": { + "description": "", + "id": "3d79da9812e44c449dde54903717be8a", + "kind": "zone", + "last_updated": "2025-05-20T10:09:27.585167Z", + "name": "default", + "phase": "http_request_firewall_custom", + "rules": [ + { + "action": "skip", + "action_parameters": { + "phases": [ + "http_request_firewall_managed" + ] + }, + "description": "Allow Blog uploads", + "enabled": true, + "expression": "(http.request.full_uri contains \"blog.doaj.org/wp-admin/\")", + "id": "b47819cd21af43f09d0d9b972755b749", + "last_updated": "2025-05-20T10:09:27.585167Z", + "logging": { + "enabled": true + }, + "ref": "b47819cd21af43f09d0d9b972755b749", + "version": "1" + } + ], + "source": "firewall_custom", + "version": "1" + }, + "success": true, + "errors": [], + "messages": [] +} diff --git a/cloudflare/cloudflare_rules/zone_settings.json b/cloudflare/cloudflare_rules/zone_settings.json new file mode 100644 index 0000000..7b5d3e0 --- /dev/null +++ b/cloudflare/cloudflare_rules/zone_settings.json @@ -0,0 +1 @@ +{"result":[{"id":"0rtt","value":"off","modified_on":null,"editable":true},{"id":"advanced_ddos","value":"on","modified_on":null,"editable":false},{"id":"always_online","value":"off","modified_on":null,"editable":true},{"id":"always_use_https","value":"off","modified_on":null,"editable":true},{"id":"automatic_https_rewrites","value":"off","modified_on":null,"editable":true},{"id":"brotli","value":"on","modified_on":null,"editable":true},{"id":"browser_cache_ttl","value":14400,"modified_on":null,"editable":true},{"id":"browser_check","value":"on","modified_on":null,"editable":true},{"id":"cache_level","value":"aggressive","modified_on":null,"editable":true},{"id":"challenge_ttl","value":1800,"modified_on":null,"editable":true},{"id":"ciphers","value":[],"modified_on":null,"editable":true},{"id":"cname_flattening","value":"flatten_at_root","modified_on":null,"editable":true},{"id":"development_mode","value":"off","modified_on":"2024-06-18T12:54:30.102680Z","time_remaining":0,"editable":true},{"id":"early_hints","value":"off","modified_on":null,"editable":true},{"id":"ech","value":"off","modified_on":null,"editable":true},{"id":"edge_cache_ttl","value":7200,"modified_on":null,"editable":true},{"id":"email_obfuscation","value":"off","modified_on":"2025-11-20T13:12:40.394201Z","editable":true},{"id":"filter_logs_to_cloudflare","value":"off","modified_on":null,"editable":true},{"id":"hotlink_protection","modified_on":null,"value":"off","editable":true},{"id":"http2","value":"on","modified_on":null,"editable":true},{"id":"http3","value":"off","modified_on":null,"editable":true},{"id":"ip_geolocation","value":"on","modified_on":null,"editable":true},{"id":"ipv6","value":"on","modified_on":"2019-02-26T15:02:28.015186Z","editable":true},{"id":"log_to_cloudflare","value":"on","modified_on":null,"editable":true},{"id":"long_lived_grpc","value":"off","modified_on":null,"editable":false},{"id":"max_upload","value":100,"modified_on":null,"editable":true},{"id":"min_tls_version","value":"1.0","modified_on":null,"editable":true},{"id":"minify","value":{"css":"off","html":"off","js":"off"},"modified_on":null,"editable":true},{"id":"mirage","value":"off","modified_on":null,"editable":true},{"id":"mobile_redirect","value":{"status":"off","mobile_subdomain":null,"strip_uri":false},"modified_on":null,"editable":true},{"id":"opportunistic_encryption","value":"on","modified_on":null,"editable":true},{"id":"opportunistic_onion","value":"on","modified_on":null,"editable":true},{"id":"orange_to_orange","value":"off","modified_on":null,"editable":true},{"id":"origin_error_page_pass_thru","value":"off","modified_on":null,"editable":false},{"id":"polish","value":"off","modified_on":null,"editable":true},{"id":"pq_keyex","value":"on","modified_on":null,"editable":true},{"id":"prefetch_preload","value":"off","modified_on":null,"editable":false},{"id":"privacy_pass","value":"on","modified_on":null,"editable":true},{"id":"proxy_read_timeout","value":"100","modified_on":null,"editable":false},{"id":"pseudo_ipv4","value":"off","modified_on":null,"editable":true},{"id":"replace_insecure_js","value":"off","modified_on":null,"editable":true},{"id":"response_buffering","value":"off","modified_on":null,"editable":false},{"id":"rocket_loader","value":"off","modified_on":null,"editable":true},{"id":"security_header","modified_on":null,"value":{"strict_transport_security":{"enabled":false,"max_age":0,"include_subdomains":false,"preload":false,"nosniff":false}},"editable":true},{"id":"security_level","value":"medium","modified_on":"2021-11-03T13:34:20.508035Z","editable":true},{"id":"server_side_exclude","value":"on","modified_on":null,"editable":true},{"id":"sort_query_string_for_cache","value":"off","modified_on":null,"editable":false},{"id":"ssl","value":"full","modified_on":"2019-02-26T15:16:46.197069Z","certificate_status":"active","validation_errors":[],"editable":true},{"id":"tls_1_2_only","value":"off","modified_on":null,"editable":true},{"id":"tls_1_3","value":"on","modified_on":null,"editable":true},{"id":"tls_client_auth","value":"off","modified_on":null,"editable":true},{"id":"true_client_ip_header","value":"off","modified_on":null,"editable":false},{"id":"visitor_ip","value":"on","modified_on":null,"editable":true},{"id":"waf","value":"off","modified_on":"2024-11-13T10:51:29.586282Z","editable":true},{"id":"webp","value":"off","modified_on":null,"editable":true},{"id":"websockets","value":"on","modified_on":null,"editable":true}],"success":true,"errors":[],"messages":[]} \ No newline at end of file