服务器托管商通知服务器地址要变更,一批域名的DNS解析都托管在Cloudflare,要手工一个一个修改域名DNS的IP费时又费力。
Cloudflare提供了API接口,可以批量修改DNS的IP地址。
大致思路:
1、调用 List Zones 获取账户下所有的zones
2、循环获取某个zone的zone_id后,调用 List DNS Records 获取所有content为旧IP的A记录
3、调用 Patch DNS Record 将对应A记录的IP修改为新IP
脚本gist:cloudflare-DNS-bulk-update.sh
#!/bin/bash old_ip="111.111.111.111" new_ip="222.222.222.222" email="[email protected]" api_token="0123456789abcdefghijklmnopqrstuvwxyz1234" zone_id_list=( $(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/?per_page=500" -H "X-Auth-Email: $email " -H "Authorization:Bearer $api_token " -H "Content-Type: application/json"| jq -r '.result[].id') ) for zone_id in "${zone_id_list[@]}" do echo "zone_id is "$zone_id record_a_list=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records?per_page=500&type=A&content=$old_ip " -H "X-Auth-Email: $email " -H "Authorization:Bearer $api_token " -H "Content-Type: application/json") record_a_ip=$(echo $record_a_list | jq -r '{"result"}[] | .[0] | .content') echo "cloudflare dns ip is:" $record_a_ip if [ -n $record_a_ip -a $record_a_ip == $old_ip ] then echo "change the A record" record_list=$(echo $record_a_list | jq -r '.result[].id') for id in ${record_list} do echo "dns_records id is " $id curl -s -X PATCH "https://api.cloudflare.com/client/v4/zones/${zone_id}/dns_records/${id}" -H "X-Auth-Email: $email " -H "Authorization:Bearer $api_token " -H "Content-Type: application/json" --data "{\"content\":\"$new_ip\"}" done fi done
备注:
1、由于只修改IP地址,不需要更改其他信息,因此使用 Patch DNS Record ,而非 Update DNS Record 。Patch命令只需要传入IP信息,不需要获取并传其他A记录的信息
2、由于Cloudflare API返回结果协议格式为json,shell脚本使用了 jq
使用yum安装jq,需要先安装epel-release
yum -y install epel-release
yum -y install jq
又或者可以试试PHP:
<?php /** * Title: 批量修改CloudFlare上的所有域名的DNS * Author: Rudon <[email protected]> * Date: 2019-03-08 * * https://dash.cloudflare.com/ * https://api.cloudflare.com/ * https://segmentfault.com/q/1010000009595203/a-1020000009598171 * * * === 第一步 === * 获取你的所有域名的zone_identifier, 保存为./result_zone_id_and_domain.json * 可以参考: * 获取CloudFlare上的所有域名的ID (zone_identifier) - by PHP * https://blog.csdn.net/qq285744011/article/details/88345077 * * === 第二步 === * 遍历json文件里的域名, * 根据官方手册,先查询这个zone(和域名一对一)的现存DNS纪录 * 再根据自己的需要,增删改查。 * 增 https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record * 删 https://api.cloudflare.com/#dns-records-for-a-zone-delete-dns-record * 改 https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record * 查 https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records * * <本文的目标> * 每个域名,只允许存在一条A纪录(where type = 'A' and name = 'yourdomain.com' and content = '6.6.6.6') * 多了就删除, * 不存在就创建 * content不是6.6.6.6的就更新为6.6.6.6 (或者先全部删除,再新建) * */ ini_set('max_execution_time', 1800); /* https://dash.cloudflare.com/ Email address associated with your account */ $config_IP_wanted = '6.6.6.6'; $x_email = '[email protected]'; $x_auth_key = '165419c1be6c1be6605dd605dd16540'; /* 登陆后,右上角头像,MyProfile,API Tokens,Global API Key */ $json_file_with_domains_and_zone_id = __DIR__.'/result_zone_id_and_domain.json'; $domains_ignored = array( 'aaa.com', 'ccc.com', 'ddd.cn' ); /** * https://blog.csdn.net/qq285744011/article/details/87859137 * * @param type $url * @param type $my_head_array | array() | array('key1:value1', 'key2:value2') * @return string */ function geturl($url, $my_head_array = array()){ $headerArray =array("Content-Type: application/json;","Accept: application/json"); if(is_array($my_head_array) && count($my_head_array)){ $headerArray = $my_head_array; } $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArray); $output = curl_exec($ch); curl_close($ch); return $output; } function posturl($url,$data = array(),$my_head_array = array()){ $data = (is_array($data)) ? json_encode($data,JSON_UNESCAPED_UNICODE) : $data; $headerArray =array("Content-type:application/json;charset='utf-8'","Accept:application/json"); if(is_array($my_head_array) && count($my_head_array)){ $headerArray = $my_head_array; } $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); //curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE); //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST,FALSE); curl_setopt($curl, CURLOPT_POST, 1); curl_setopt($curl, CURLOPT_POSTFIELDS, $data); curl_setopt($curl, CURLOPT_HTTPHEADER, $headerArray); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($curl); curl_close($curl); return $output; } function puturl($url,$data = array(),$my_head_array = array()){ $data = (is_array($data)) ? json_encode($data,JSON_UNESCAPED_UNICODE) : $data; $headerArray =array("Content-type:application/json;charset='utf-8'","Accept:application/json"); if(is_array($my_head_array) && count($my_head_array)){ $headerArray = $my_head_array; } $ch = curl_init(); //初始化CURL句柄 curl_setopt($ch, CURLOPT_URL, $url); //设置请求的URL curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type:application/json')); curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); //设为TRUE把curl_exec()结果转化为字串,而不是直接输出 curl_setopt($ch, CURLOPT_CUSTOMREQUEST,"PUT"); //设置请求方式 curl_setopt($ch, CURLOPT_POSTFIELDS, $data);//设置提交的字符串 $output = curl_exec($ch); curl_close($ch); return $output; } function delurl($url, $my_head_array = array()){ $headerArray =array('Content-type:application/json'); if(is_array($my_head_array) && count($my_head_array)){ $headerArray = $my_head_array; } $ch = curl_init(); curl_setopt ($ch, CURLOPT_URL, $url); curl_setopt ($ch, CURLOPT_HTTPHEADER, $headerArray); curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt ($ch, CURLOPT_CUSTOMREQUEST, "DELETE"); $output = curl_exec($ch); curl_close($ch); return $output; } function p ($v = '-Your-Variable-Here-') { header('Content-Type: text/css; charset=utf-8'); print_r($v); die; } /* Check */ if(!is_file($json_file_with_domains_and_zone_id)){ die('Missing json file for zones'); } $arr_domain_and_id = json_decode(file_get_contents($json_file_with_domains_and_zone_id), true); if(!is_array($arr_domain_and_id) || !count($arr_domain_and_id)){ die('Invalid array for domains and zone ID'); } /* Loop and check */ $conf_wanted = array( 'type' => 'A', 'name' => '', // Your domain name 'content' => $config_IP_wanted, // 'ttl' => 1, // 'proxied' => false ); foreach ($arr_domain_and_id as $cur_domain => $cur_zone_id) { print('<hr />'); error_log('Dealing with '.$cur_domain); print("<div>Checking {$cur_domain}</div>"); $cur_domain = strtolower($cur_domain); /* Ignore */ if(in_array($cur_domain, $domains_ignored)){ continue; } $existing_dns_records = array(); $identifier_of_entries_to_delete = array(); $identifier_of_entries_to_update = array(); $curl_head = array( "X-Auth-Email: {$x_email}", "X-Auth-Key: {$x_auth_key}", "Content-Type: application/json" ); /* Get the list of DNS record at first */ $curl_url_base = "https://api.cloudflare.com/client/v4/zones/{$cur_zone_id}/dns_records"; // 注意!结尾没有斜杠! $q_str = '?type='.$conf_wanted['type'].'&name='.$cur_domain.'&per_page=100'; /** * curl -X GET "https://api.cloudflare.com/client/v4/zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records?type=A&name=example.com&content=127.0.0.1&page=1&per_page=20&order=type&direction=desc&match=all" \ -H "X-Auth-Email: [email protected]" \ -H "X-Auth-Key: c2547eb70b638f5e225cf483cc5cfdda41" \ -H "Content-Type: application/json" */ $curl_url_list = $curl_url_base.$q_str; /** * curl -X POST "https://api.cloudflare.com/client/v4/zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records" \ -H "X-Auth-Email: [email protected]" \ -H "X-Auth-Key: c2547eb70b638f5e225cf483cc5cfdda41" \ -H "Content-Type: application/json" \ --data '{"type":"A","name":"example.com","content":"127.0.0.1","ttl":120,"priority":10,"proxied":false}' */ /** * curl -X DELETE "https://api.cloudflare.com/client/v4/zones/023e105f4ecef8ad9ca31a8372d0c353/dns_records/372e67954025e0ba6aaa6d586b9e0b59" \ -H "X-Auth-Email: [email protected]" \ -H "X-Auth-Key: c2547eb70b638f5e225cf483cc5cfdda41" \ -H "Content-Type: application/json" */ $res_str = geturl($curl_url_list, $curl_head); $res_arr = json_decode($res_str, true); if(!is_array($res_arr) || !count($res_arr) || !key_exists('success', $res_arr) || !$res_arr['success']){ error_log('ERROR for '.$cur_domain.': '.$res_str); print("<div>Error {$cur_domain}</div><div>{$res_str}</div><hr />"); continue; } $list_of_record = $res_arr['result']; $count_entries = count($list_of_record); $perfect_entry = array(); // id,type,name,content,proxiable,proxied,ttl,locked,zone_id,zone_name,etc foreach ($list_of_record as $k=>$v) { if($v['content'] == $conf_wanted['content']){ $perfect_entry = $v; } } /* Compare */ if($count_entries == 1 && count($perfect_entry)){ $message = 'Everything is fine for '.$cur_domain; error_log($message); print('<div>'.$message.'</div>'); continue; } else { $need_to_create = false; if ($count_entries == 0) { /* Create at last */ $need_to_create = true; } elseif (!count($perfect_entry)) { /* There's no correct entries, delete them and rebuild */ foreach ($list_of_record as $one) { $identifier_of_entries_to_delete[] = $one['id']; } /* Create at last */ $need_to_create = true; } else { /* There's one or more correct entries, just keep them */ foreach ($list_of_record as $one) { if($one['content'] != $conf_wanted['content']){ $identifier_of_entries_to_delete[] = $one['id']; } } $need_to_create = false; } /* Delete */ if(count($identifier_of_entries_to_delete)){ foreach ($identifier_of_entries_to_delete as $oneIDdel) { $url = $curl_url_base . '/' . $oneIDdel; $res_curl = delurl($url, $curl_head); $res_curl_arr = json_decode($res_curl, true); if(!is_array($res_curl_arr) || !count($res_curl_arr) || !key_exists('success', $res_curl_arr) || !$res_curl_arr['success']){ $message = 'Failed to delete A record #'.$oneIDdel.' for '.$cur_domain; error_log($message); print('<div>'.$message.'</div>'); print('<div>'.$url.'</div>'); print("<div>{$res_curl}</div>"); } else { $message = 'Succeeded to delete A record #'.$oneIDdel.' for '.$cur_domain; error_log($message); print('<div>'.$message.'</div>'); } } } /* Build */ if($need_to_create){ $good_data = $conf_wanted; $good_data['name'] = $cur_domain; $url = $curl_url_base; $res_curl = posturl($url, $good_data, $curl_head); $res_curl_arr = json_decode($res_curl, true); if(!is_array($res_curl_arr) || !count($res_curl_arr) || !key_exists('success', $res_curl_arr) || !$res_curl_arr['success']){ $message = 'Failed to create A record for '.$cur_domain; error_log($message); print('<div>'.$message.'</div>'); print('<div>'.$message.'</div>'); print('<div>'.$url.'</div>'); print("<div>{$res_curl}</div>"); } else { $message = 'Succeeded to create A record for '.$cur_domain; error_log($message); print('<div>'.$message.'</div>'); } } } } /** * End */
方法一
使用API,找到global api key,對應domain的zone ID和Account ID
1.1 https://api.cloudflare.com/#dns-records-for-a-zone-delete-dns-record
1.2 https://www.tech-otaku.com/web-development/using-cloudflare-api-manage-dns-records/#436
方法二
通過匯出DNS記錄成txt文檔,然後手工修改再上傳覆蓋。
方法三
使用powershell 脚本,https://community.cloudflare.com/t/bulk-delete-dns-records/421686
本地計算機需要在設置中查找開發者選項,並允許開發者選項中本地運行powershell脚本
注意:要編輯DNS的API token,不是API Key。
还有一种方法:
本文件一共有5个文件配合使用:
domians_list.txt :用来存放需要添加的域名
cloudflare.env :存放各种变量
cloudflare.sh :存放linux命令,执行时也是执行这一个脚本即可
filter_zone_id.py :用于将CloudFlare返回的数据过滤(过滤出域名及其区域ID)
filter_dns_id.py :用于将CloudFlare返回的数据过滤(过滤出解析记录及其记录ID)
domians_list.txt
就是把顶级域名写到里面,一行一个,如:
a.com b.com c.com
cloudflare.env
#!/bin/bash #通用变量 #当前目录 PWD=$(pwd) PYTHON=$(which python) #这里DNS指的是解析记录 #CloudFlare的登录账号 CF_API_EMAIL=CloudFlare的登录账号 #在用户信息里面查看“Global KEY” CF_API_KEY=在用户信息里面查看“Global KEY” #组织名 ORGANIZATION_NAME="组织名" #组织ID ORGANIZATION_ID="组织ID" #解析参数 # DNS_TYPE=A/CNAME/AAAA DNS_TYPE=解析类型 #DNS_SUBDOMAIN=子解析记录 DNS_SUBDOMAIN= CONTENT_A=域名需要解析的IP CONTENT_CNAME=域名需要解析的CNAME
cloudflare.sh
#!/bin/bash source ./cloudflare.env #新增域名 for DOMAIN in $(cat $PWD/domain_list.txt) do curl -X POST -H "X-Auth-Key: ${CF_API_KEY}" \ -H "X-Auth-Email: ${CF_API_EMAIL}" \ -H "Content-Type: application/json" "https://api.cloudflare.com/client/v4/zones" \ --data '{"name":"'"${DOMAIN}"'","jump_start":true,"organization":{"name":"'"${ORGANIZATION_NAME}"'","id":"'"${ORGANIZATION_ID}"'"}}' >> $PWD/get_zone_id.cf ECHO -e "\n" >> $PWD/get_zone_id.cf done #get_zone_id.cf -- 存放新增域名时Cloudflare返回的JSON 数据 #filter_zone_id.py -- 用于从 "get_zone_id.cf" 中过滤出域名及其相对应的区域ID并写入文件 "zone_id.cf" #执行python脚本,过滤出域名的区域ID $PYTHON $PWD/filter_zone_id.py #获取DNS ID列表 while read line do ZONE_NAME=$(ECHO "$line" | awk '{print $1}') ZONE_ID=$(ECHO "$line" | awk '{print $2}') curl -X GET "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \ -H "X-Auth-Email: ${CF_API_EMAIL}" -H "X-Auth-Key: ${CF_API_KEY}" \ -H "Content-Type: application/json" >> $PWD/get_dns_id.cf ECHO -e "\n" >> $PWD/get_dns_id.cf done < $PWD/zone_id.cf #get_dns_id.cf -- 存放获取DNS_ID时Cloudflare返回的JSON 数据 #filter_dns_id.py -- 用于从 "get_dns_id.cf" 中过滤出DNS_ID及其DNS_NAME并写入文件 "dns_id.cf" #执行python脚本,过滤出DNS_ID $PYTHON $PWD/filter_dns_id.py #删除DNS记录 #删除DNS记录还需要指定区域ID,每个域名的区域DI不同,所以进行一个判断,判断DNS_NAME是否模糊匹配之前取到的ZONE_NAME #如果匹配则使用这个ZONE_ID while read zone do ZONE_NAME=$(ECHO "$zone" | awk '{print $1}') ZONE_ID=$(ECHO "$zone" | awk '{print $2}') while read dns do DNS_NAME=$(ECHO "$dns" | awk '{ print $1 }') DNS_ID=$(ECHO "$dns" | awk '{ print $2 }') if [[ "*$DNS_NAME" =~ "$ZONE_NAME" ]] then curl -X DELETE "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records/${DNS_ID}" \ -H "X-Auth-Email:${CF_API_EMAIL}" \ -H "X-Auth-Key:${CF_API_KEY}" \ -H "Content-Type:application/json" fi done < $PWD/dns_id.cf done < $PWD/zone_id.cf #增加DNS记录 #TTL=1 为自动 #proxied=true 使用CF的CDN,等于false是不使用 #data传入变量格式: "'"$EVN"'" #设置变量 while read line do ZONE_NAME=$(ECHO "$line" | awk '{print $1}') ZONE_ID=$(ECHO "$line" | awk '{print $2}') curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \ -H "X-Auth-Email:${CF_API_EMAIL}" \ -H "X-Auth-Key:${CF_API_KEY}" \ -H "Content-Type:application/json" \ --data '{"type":"A","name":"@","content":"'"${CONTENT_A}"'","ttl":1,"priority":10,"proxied":true}' curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \ -H "X-Auth-Email:${CF_API_EMAIL}" \ -H "X-Auth-Key:${CF_API_KEY}" \ -H "Content-Type:application/json" \ --data '{"type":"CNAME","name":"www","content":"'"${CONTENT_CNAME}"'","ttl":1,"priority":10,"proxied":false}' curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/dns_records" \ -H "X-Auth-Email:${CF_API_EMAIL}" \ -H "X-Auth-Key:${CF_API_KEY}" \ -H "Content-Type:application/json" \ --data '{"type":"CNAME","name":"m","content":"'"${CONTENT_CNAME}"'","ttl":1,"priority":10,"proxied":false}' done < $PWD/zone_id.cf
filter_zone_id.py
#!/usr/bin/env python #*- coding:utf-8 -* #打开文件获取添加域名成功后返回的数据,然后获取到域名的区域ID #通过区域ID获取DNS列表 import json import os import traceback #traceback 完整输出报错信息 #str(e) 只给出异常信息,不包括异常信息的类型,如1/0的异常信息 #repr(e) 给出较全的异常信息,包括异常信息的类型,如1/0的异常信息 PWD = os.getcwd() PWD_GET = PWD+'/get_zone_id.cf' PWD_ZONE = PWD+'/zone_id.cf' ERROR_FILE = PWD+'/zone_error.log' #读取添加域名时返回的字符串,并且循环写入字典,以便于过滤出域名的区域ID with open (PWD_GET,'r') as file: i = 0 j = 0 dict = {} list = [] for line in file: if line.strip == " " or line == "\n" or line == " \n": continue try: dict[i]=json.loads(line) i+=1 except Exception as e: j+=1 with open (ERROR_FILE,'a+') as file: title = "----------\t\t第%d条报错信息\t\t---------"%(j) + "\n" #info = "报错信息:" + traceback.format_exc() + "\n" info = "报错信息:" + repr(e) + "\n" mation = "报错行:" + line + "\n" file.writelines(title) file.writelines(info) file.writelines(mation) continue #获取所有的key,然后进行循环遍历,获取每个域名的区域ID keys_list = dict.keys() for key in keys_list: ID=dict[key]['result']['id'] NAME=dict[key]['result']['name'] with open (PWD_ZONE,'a+') as file: line = NAME + " " + ID + "\n" file.writelines(line)
filter_dns_id.py
#!/usr/bin/env python #*- coding:utf-8 -* #打开文件读取cloudflare返回的DNS ID import json import os PWD = os.getcwd() PWD_GET = PWD+'/get_dns_id.cf' PWD_DNS = PWD+'/dns_id.cf' #读取获取DNS ID时返回的字符串,并且循环写入字典,以便于过滤出DNS ID with open (PWD_GET,'r') as file: i = 0 dict = {} for line in file: if line.strip == " " or line == "\n" or line == " \n": continue dict[i]=json.loads(line) i+=1 key_list = dict.keys() for key in key_list: list = dict[key]['result'] #一个域名可能有多个DNS记录,cloudflare返回的是一个列表,列表中是一个一个的字典, #一个key-values代表一个DNS记录,所以这里需要遍历列表中的字典 #通过取列表的索引值,循环每一个位置的字典 for i in list: with open (PWD_DNS,'a+') as file: line = i['name'] + " " + i['id'] + "\n" file.writelines(line)
也可以使用若海的思路,SHELL批量删除Cloudfalre解析记录:
创建一个有DNS写权限的API_TOKEN(操作地址 https://dash.cloudflare.com/profile/api-tokens)
查看待操作域名的 ZONE_ID(在域名概要页面右下角可以看到)
修改下面SHELL脚本对应变量后,多运行几次。
# Set your Cloudflare API key API_TOKEN="xxxxxxx" # Set the zone ID for the domain you want to manage ZONE_ID="yyyyyyy" # Get all DNS records for the zone curl -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -s -o records.json # Parse the JSON response to get the record IDs RECORD_IDS=$(jq -r '.result[].id' records.json) # Loop through the record IDs and delete each one for RECORD_ID in $RECORD_IDS; do curl -X DELETE "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -s done
还有这个脚本:https://github.com/lstty/cloudflare_batch_add_domain
暂无评论内容