Skip to content

Instantly share code, notes, and snippets.

@mosajjal
Created August 22, 2021 11:04
Show Gist options
  • Save mosajjal/56a552f2d0a7a55f6106f91ba7e0ebe6 to your computer and use it in GitHub Desktop.
Save mosajjal/56a552f2d0a7a55f6106f91ba7e0ebe6 to your computer and use it in GitHub Desktop.
Automated Script to Use Cloudflare as a DNS server for Tailscale. Global alternative to Magic DNS
"""
This script trys to add and delete records from cloudflare based on the current status of the account in tailscale, and eliminate the need for TailScale's magic DNS.
Each domain will be created under a subdomain "SUBDOMAIN", that way it'll be easier to find and detele records.
IMPORTANT: this script is not very well tested and it tries to DELETE some DNS records. Use it at your own risk, especially if your SUBDOMAIN value is generic and it's also used elsewhere in your domain.
"""
import os
import requests
TS_USERNAME = os.environ.get("TS_USERNAME") or "" # set env variable of TS_USERNAME for your Tailscale Username
TS_API_KEY = os.environ.get("TS_API_KEY") or "" # set env variable of TS_USERNAME for your Tailscale API Key
TS_API_URL = "https://api.tailscale.com/api/v2/"
CF_API_KEY = os.environ.get("CF_API_KEY") or "" # set env variable of CF_API_KEY for your Cloudflare API Key
CF_DOMAIN = os.environ.get("CF_DOMAIN") or "" # set env variable of CF_DOMAIN for the main domain in your Cloudflare zone. eg .example.com
CF_ZONE_ID = os.environ.get("CF_ZONE_ID") or "" # set env variable of CF_ZONE_ID for your Cloudflare domain Zone ID
CF_ACC_ID = os.environ.get("CF_ACC_ID") or "" # set env variable of CF_ACC_ID for your Cloudflare username, usually it'sa in email format
CF_API_URL = 'https://api.cloudflare.com/client/v4/'
DELIM = "-|_|-" # unique delimeter for the tailscale and cloudflare domains.
SUBDOMAIN = "tailscale" # creates the domains under tailscale "subdomain". eg host1.tailscale.example.com
# basic sanity check on the CF_DOMAIN
CF_DOMAIN = "." + CF_DOMAIN if CF_DOMAIN[0] != "." else CF_DOMAIN
def cf_grab_all_records_matching(zone_id, pattern):
out = dict()
headers = {
"X-Auth-Email": CF_ACC_ID,
"X-Auth-Key": CF_API_KEY,
"Content-Type": "application/json"
}
res = requests.get(f"{CF_API_URL}zones/{zone_id}/dns_records?per_page=100", headers=headers)
for item in res.json().get("result"):
if pattern in item.get("name"):
out[f"{item.get('name')}{DELIM}{item.get('content')}"] = {"id": item.get("id"), "type": item.get("type")}
return out
def cf_del_record(record_id):
headers = {
"X-Auth-Email": CF_ACC_ID,
"X-Auth-Key": CF_API_KEY,
"Content-Type": "application/json"
}
res = requests.delete(f"{CF_API_URL}zones/{CF_ZONE_ID}/dns_records/{record_id}", headers=headers)
return res.status_code == 200
def cf_add_record(domain, value):
headers = {
"X-Auth-Email": CF_ACC_ID,
"X-Auth-Key": CF_API_KEY,
"Content-Type": "application/json"
}
data = {
"type": "AAAA" if ":" in value else "A",
"name": domain,
"content": value,
"ttl": 1,
"proxied": False
}
res = requests.post(f"{CF_API_URL}zones/{CF_ZONE_ID}/dns_records", headers=headers, json=data)
return res.status_code == 200
def tailscale_current_status(suffix):
out = dict()
res = requests.get(f"{TS_API_URL}tailnet/{TS_USERNAME}/devices", auth=(TS_API_KEY,""))
if res.status_code != 200:
return {"sucess":False, "message":f"Error: {res.json()}"}
else:
for item in res.json().get("devices"):
for address in item["addresses"]:
new_fqdn = item['name'].split(".")[0] + "." + suffix + CF_DOMAIN
type = "AAAA" if ":" in address else "A"
out[f"{new_fqdn}{DELIM}{address}"] = {"id": 0, "type": type}
return out
def main():
ts_cf_zones = cf_grab_all_records_matching(CF_ZONE_ID, SUBDOMAIN + CF_DOMAIN)
ts_current = tailscale_current_status(SUBDOMAIN)
cf_to_del = []
cf_to_add = []
# items that are in ts_current and not in ts_cf_zones -> add
for item in list(set(ts_current.keys()) - set(ts_cf_zones.keys())):
cf_to_add.append((item.split(DELIM)))
# items that are in ts_cf_zones and not in ts_current -> delete
for item in list(set(ts_cf_zones.keys()) - set(ts_current.keys())):
cf_to_del.append(ts_cf_zones[item]['id'])
for item in cf_to_add:
res = cf_add_record(item[0], item[1])
print(f"added {item[0]} with suceess: {res}")
for item in cf_to_del:
res = cf_del_record(item)
print(f"deleted {item} with suceess: {res}")
if len(cf_to_add) == 0 and len(cf_to_del) == 0:
print("Nothing to do")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment