Cloudflare 作为世界上最大的互联网慈善组织,为开发者提供了极其完整的 API 支持。本文关注于 Cloudflare 的 DNS API 部分。

申请 API 令牌(Token)

这是 API 使用过程中唯一图形化的部分了。我们要在 用户 API 令牌 创建一个令牌。对于我们要操作的 DNS,可以选择编辑区域 DNS 模板,然后填入我们要操作的二级域名(和 IP 地址白名单)。为了最小权限原理,建议填写白名单。

生成界面

然后就生成了我们的 API Token!注意保存以及保存的安全性,因为离开页面后再也见不到它啦。如果弄丢了只能重新生成部署了。

Token 界面

建议在对应服务器上明文储存,反正有 ssh 帮你拦着风险。

然后就可以尝试验证一下 Token。

curl "https://api.cloudflare.com/client/v4/user/tokens/verify" \
     -H "Authorization: Bearer <API_TOKEN>"

正常情况下应当返回

{
  "result": {
    "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "status": "active"
  },
  "success": true,
  "errors": [],
  "messages": [
    {
      "code": 10000,
      "message": "This API Token is valid and active",
      "type": null
    }
  ]
}

DNS Record API

Cloudflare 的 API 采用 RESTful 风格,默认基于 JSON (Content-Type: application/json) 传递数据。

在 DNS 记录方面,Cloudflare 提供了 9 个 API

DNS Record

列举 Zone

API: GET https://api.cloudflare.com/client/v4/zones

对于 DNS 来说,Cloudflare 里有一个参数叫做 Zone ID(zone_identifier)。一个 Zone 对应着一个你账号内的二级域名,我们需要事前确定这个 Zone ID。
另一个可能会用到的是 Record ID(identifier)。一个 Record ID 对应着一条域名记录。对于同一个子域名的不同记录对应着不同 Record ID,即使他们都是 A 记录。
获取前者的方法是 GET curl "https://api.cloudflare.com/client/v4/zones" -H "Authorization: Bearer ${CF_Token}"

示例结果

{
  "result": [
    {
      "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "name": "DOMAIN1",
      "status": "active",
      "paused": false,
      "type": "full",
      "owner": { "id": null, "type": "user", "email": null },
      "account": {
        "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "name": "ACCOUNT NAME"
      },
      "permissions": ["#dns_records:edit", "#dns_records:read", "#zone:read"],
      "plan": {...}
    },
    {
      "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "name": "DOMAIN2",
      "status": "active",
      "paused": false,
      "type": "full",
      ...
      "owner": { "id": null, "type": "user", "email": null },
      "account": {
        "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "name": "ACCOUNT NAME"
      },
      "permissions": ["#dns_records:edit", "#dns_records:read", "#zone:read"],
      "plan": {...}
    },
    ...
  ],
  "result_info": {
    "page": 1,
    "per_page": 20,
    "total_pages": 5726,
    "count": 20,
    "total_count": 114514
  },
  "success": true,
  "errors": [],
  "messages": []
}

我们可以通过 curl "https://api.cloudflare.com/client/v4/zones" -H "Authorization: Bearer ${CF_Token}" | jq '.result[] | if .name == "${Goal_Domain}" then .id else null end' 轻松筛选出目标的域名 id。下面默认设置了一个 CF_Zone 的环境变量,存储目标域名的 Zone ID。

如果你有很多很多域名,可能会分页,那么可以通过 https://api.cloudflare.com/client/v4/zones?page=xx 来逐页获取,或是增大 per_page 参数。

列举 DNS 记录

API: GET https://api.cloudflare.com/client/v4/zones/{zone_identifier}/dns_records

这里就用上了上一步获得的 Zone ID。我们直接 curl https://api.cloudflare.com/client/v4/zones/${CF_Zone}/dns_records -H "Authorization: Bearer ${CF_Token}"

示例结果

{
  "result": [
    {
      "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1",
      "zone_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "zone_name": "DOMAIN",
      "name": "SUB1.DOMAIN",
      "type": "A",
      "content": "1.14.5.14",
      "proxiable": true,
      "proxied": false,
      "ttl": 1,
      "locked": false,
      "meta": {
        "auto_added": false,
        "managed_by_apps": false,
        "managed_by_argo_tunnel": false,
        "source": "primary"
      },
      "comment": null,
      "tags": [],
      "created_on": "2023-01-01T00:00:00.000000Z",
      "modified_on": "2023-01-01T00:00:00.000000Z"
    },
    {
      "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx2",
      "zone_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "zone_name": "DOMAIN",
      "name": "SUB2.DOMAIN",
      "type": "AAAA",
      "content": "1919::810",
      "proxiable": true,
      "proxied": true,
      "ttl": 1,
      "locked": false,
      "meta": {
        "auto_added": false,
        "managed_by_apps": false,
        "managed_by_argo_tunnel": false,
        "source": "primary"
      },
      "comment": null,
      "tags": [],
      "created_on": "2023-01-01T00:00:00.000000Z",
      "modified_on": "2023-01-01T00:00:00.000000Z"
    }
  ],
  "success": true,
  "errors": [],
  "messages": [],
  "result_info": {
    "page": 1,
    "per_page": 100,
    "count": 100,
    "total_count": 1919810,
    "total_pages": 1920
  }
}

我们可以在此之中找到我们需要的 Record id(即每一项中的 .id)备用。

新建 DNS 记录

API: POST https://api.cloudflare.com/client/v4/zones/{zone_identifier}/dns_records

话不多说,直接上代码。

curl -X POST https://api.cloudflare.com/client/v4/zones/${CF_Zone}/dns_records\
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer ${CF_Token}' \
  --data '{
  "content": "1.14.5.14",
  "name": "SUBDOMAIN",
  "proxied": false,
  "type": "A",
  "comment": "COMMENT",
  "tags": [
    "TAG"
  ],
  "ttl": 3600
}'

示例返回结果如下

{
  "errors": [],
  "messages": [],
  "result": {
    // 重复一遍你的 data
    "created_on": "2023-01-01T00:00:00.000000Z",
    "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "locked": false,
    "meta": {
      "auto_added": true,
      "source": "primary"
    },
    "modified_on": "2023-01-01T00:00:00.000000Z",
    "zone_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "zone_name": "DOMAIN"
  },
  "success": true
}

这里的 .result.id 是这个记录的 Record ID,可以保存以后用。

删除记录

API: DELETE https://api.cloudflare.com/client/v4/zones/{zone_identifier}/dns_records/{identifier}

其中的 identifier 就是 Record ID。你可以通过 curl -X DELETE -H 'Authorization: Bearer ${CF_Token}' https://api.cloudflare.com/client/v4/zones/${CF_Zone}/dns_records/${CF_Record} 来轻松删除一条记录。

更新记录

API: PUT https://api.cloudflare.com/client/v4/zones/{zone_identifier}/dns_records/{identifier}

内容、用法和 创建记录 一模一样,返回值也一模一样。唯一的不同就是调用链接加上了 Record ID,方法变成了 PUT。

实用

来撸点脚本吧。

IPv6 DDNS 脚本

一键 DDNS 脚本(每小时刷新),依赖 jq

# 配置变量
API='https://ddnsip.cn/'
name='myddns'
CF_Zone='...'
CF_Token='...'
# 新建记录
IP=$(curl $API -6 -s)
Record_ID=$(curl -s -X POST https://api.cloudflare.com/client/v4/zones/${CF_Zone}/dns_records\
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${CF_Token}" \
  --data "{
  \"content\": \"${IP}\",
  \"name\": \"${name}\",
  \"proxied\": false,
  \"type\": \"AAAA\",
  \"comment\": \"DDNS initialized on $(date "+%Y/%m/%d %H:%M:%S")\",
  \"ttl\": 1800
}" | jq -j '.result.id')

# 更新
sudo tee /etc/cron.hourly/ddns6.sh > /dev/null <<EOF
#!/bin/bash
IP=\$(curl $API -6 -s)
curl -s -X PUT https://api.cloudflare.com/client/v4/zones/${CF_Zone}/dns_records/${Record_ID}\
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${CF_Token}" \
  --data "{
  \"content\": \"\${IP}\",
  \"name\": \"${name}\",
  \"proxied\": false,
  \"type\": \"AAAA\",
  \"comment\": \"DDNS updated on \$(date "+%Y/%m/%d %H:%M:%S")\",
  \"ttl\": 1800
}" >/dev/null
EOF

如果你有公网 v4 也可以把上面的 6 全改成 4,AAAA 改成 A,v4 ddns 脚本就新鲜出炉啦!