前言
事情的起因还得追溯到给博客加了 全站加速。
依靠全站加速,还减轻了一次网站攻击,详见:记一次网站被 DDoS(CC)攻击 的过程和应对方案
在设置了全站加速之后,通过域名默认会走阿里云的 DCDN 节点,只有在未缓存的时候会访问源站。
此时,源站就会遇到一个需求:最好仅允许阿里云的 DCDN 节点进行访问。
也就是说,需要获取到阿里云的 DCDN 节点的 IP 列表,以添加到白名单中。
就此,开始了折腾之路。
开始
由于之前已经用过阿里云 OpenAPI 进行 CDN 的管理(如何通过阿里云 OpenAPI 管理 CDN),所以这一次也采用了阿里云 OpenAP,从而实现自动化设置。
翻阅全站加速相关文档可以看到一个接口:DescribeDcdnL2Ips – 查询 L2 节点 IP 地址
这正是我需要的。
由于文档里没有说前置要求,所以我想当然的认为只要提交工单就可以获得权限。
中道崩阻
结果:
不是,大哥,你在 DCDN 的文档中又没有说明前置要求,没写就是没要求才对。
再者,CDN 节点的 IP 是什么需要藏着掖着的东西吗?你不公开出来我怎么设置白名单?
隔壁 Cloudflare 就是把 IP 地址范围全部公布出来的,例如 IP 地址范围。
所以什么要设置个门槛?不是很能理解。
峰回路转
但很快啊,我又发现另一个接口:DescribeDcdnIpInfo – 验证 IP 节点
虽然没法直接获取 DCDN 节点的 IP ,但是可以验证一个 IP 是不是阿里云 DCDN 节点。
解决问题
所以接下来解决方案就很简单了。
我手动把 IP 地址验证一遍不就完事了?
当然了,考虑到 IPv4 一共有 42 亿个 IP,显然是不可能穷举一遍的。
那么我就得找个地方获取访问 IP。
获取 IP 地址
而 NGINX 的日志,就是一个获取 IP 的好地方。
以下是一个从日志中获取 IP 地址的 TypeScript 函数:
// 定义提取 IP 地址的函数
async function extractUniqueIps(logFilePath: string) {
// 使用 Set 来存储唯一的 IP 地址
const uniqueIps = new Set<string>()
// 使用流式读取日志文件
const readStream = fs.createReadStream(logFilePath, { encoding: 'utf-8' })
const rl = createInterface({ input: readStream })
// 逐行处理日志文件
rl.on('line', (line) => {
// 使用正则表达式提取 IP 地址
const ipPattern = /\b([0-9]{1,3}\.){3}[0-9]{1,3}\b/
const match = line.match(ipPattern)
if (match) {
uniqueIps.add(match[0])
}
})
// 返回一个 Promise,当读取结束时,解析为唯一的 IP 地址数组
return new Promise<string[]>((resolve) => {
rl.on('close', () => {
resolve([...uniqueIps])
})
})
}
采用流式读取是因为日志文件可能会有点大,全部读取进来有点占内存。
经过这一通折腾,我拿到了一个约 28000 个 IP 地址。
有一说一,这个量还是有点大,50 次/秒的话要跑 560 秒,约 9.3 分钟,这个时间就有点久了。
所以,可以采用 IP 属地进行初次过滤。
获取 IP 属地
通过 lionsoul2014/ip2region 这个项目可以拿到所有 IP 的属地。
虽然数据上可能有点过时,但用来初步筛选已经够了。
以下是一个使用 JavaScript 实现的查询脚本(参考:ip2region nodejs 查询客户端实现)
// 导入包
const Searcher = require('.')
// 指定ip2region数据文件路径
const dbPath = 'ip2region.xdb file path'
try {
// 创建searcher对象
const searcher = Searcher.newWithFileOnly(dbPath)
// 查询
const data = await searcher.search('218.4.167.70')
// data: {region: '中国|0|江苏省|苏州市|电信', ioCount: 3, took: 1.342389}
} catch(e) {
console.log(e)
}
然后从中筛选出 region 为 阿里云
或 阿里巴巴
的 IP。
接下来,再判断是否为阿里云 DCDN 节点。
判断 DCDN 节点
接下来就是调用 DescribeDcdnIpInfo
接口来验证是否为阿里云 DCDN 节点了。
以下是一个用 TypeScript 实现的 Client 封装类。
// This file is auto-generated, don't edit it
// 依赖的模块可通过下载工程中的模块依赖文件或右上角的获取 SDK 依赖信息查看
import dcdn20180115, * as $dcdn20180115 from '@alicloud/dcdn20180115'
import OpenApi, * as $OpenApi from '@alicloud/openapi-client'
import Util, * as $Util from '@alicloud/tea-util'
export default class Client {
/**
* @remarks
* 使用AK&SK初始化账号Client
* @returns Client
*
* @throws Exception
*/
static createClient(): dcdn20180115 {
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
// 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378664.html。
const config = new $OpenApi.Config({
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
accessKeyId: process.env['ALIBABA_CLOUD_ACCESS_KEY_ID'],
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
accessKeySecret: process.env['ALIBABA_CLOUD_ACCESS_KEY_SECRET'],
})
// Endpoint 请参考 https://api.aliyun.com/product/dcdn
config.endpoint = 'dcdn.aliyuncs.com'
return new (dcdn20180115 as any).default(config)
}
static async DescribeDcdnIpInfoRequest(IP: string) {
const client = Client.createClient()
const describeDcdnIpInfoRequest = new $dcdn20180115.DescribeDcdnIpInfoRequest({
IP,
})
const runtime = new $Util.RuntimeOptions({})
try {
// 复制代码运行请自行打印 API 的返回值
return (await client.describeDcdnIpInfoWithOptions(describeDcdnIpInfoRequest, runtime)).body
} catch (error) {
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
// 错误 message
console.log(error.message)
// 诊断地址
console.log(error.data['Recommend'])
}
}
}
最后,把是 阿里云 DCDN 的 IP 写入到文件。
至此,就大功告成了!
生成网段
不过,单纯的用 IP 的话实际上容易误判,原因是阿里云 DCDN 的 IP 还是挺多的,靠从 NGINX 日志获取的方法效率低不说,还很滞后。
所以,将 IP 改为网段更加合理一些。
以下是一个从 IP 地址列表生成 IP 网段的 TypeScript 脚本。
/**
*
*
* @author CaoMeiYouRen
* @date 2024-09-16
* @param ips IP地址
* @param [netMask=24] 子网掩码位数
*/
function getSubnetCounts(ips: string[], netMask = 24) {
// 创建 Map 来存储网段及其出现频率
const subnetCounts = new Map<string, number>()
// 遍历每个 IP 地址
for (const ip of ips) {
// 提取前 n 段作为网段
const segments = ip.split('.')
const subnetSegments = segments.slice(0, netMask / 8)
const subnet = `${subnetSegments.join('.')}${'.0'.repeat(4 - subnetSegments.length)}/NULL`
// 如果网段不存在于 subnetCounts 中,初始化为 0
if (!subnetCounts.has(subnet)) {
subnetCounts.set(subnet, 0)
}
// 增加网段的计数
subnetCounts.set(subnet, subnetCounts.get(subnet) + 1)
}
return [...subnetCounts].map(([subnet, count]) => ({ subnet, count }))
}
然后就可以生成 NGINX 的白名单了,生成的结果如下:
allow 101.133.199.0/24; # count: 66
allow 101.200.20.0/24; # count: 58
allow 101.37.183.0/24; # count: 2
allow 106.11.37.0/24; # count: 23
allow 112.124.132.0/24; # count: 30
allow 114.215.0.0/16; # count: 108
allow 114.215.72.0/24; # count: 108
allow 118.190.214.0/24; # count: 22
allow 118.190.218.0/24; # count: 14
allow 119.23.123.0/24; # count: 39
allow 119.23.91.0/24; # count: 41
allow 120.27.78.0/24; # count: 43
allow 121.199.80.0/24; # count: 37
allow 121.89.252.0/24; # count: 6
allow 139.129.78.0/24; # count: 6
allow 139.196.128.0/24; # count: 46
allow 163.181.0.0/16; # count: 562
allow 163.181.1.0/24; # count: 10
allow 163.181.126.0/24; # count: 38
allow 163.181.128.0/24; # count: 2
allow 163.181.130.0/24; # count: 22
allow 163.181.131.0/24; # count: 12
allow 163.181.132.0/24; # count: 1
allow 163.181.140.0/24; # count: 14
allow 163.181.143.0/24; # count: 3
allow 163.181.145.0/24; # count: 19
allow 163.181.146.0/24; # count: 36
allow 163.181.154.0/24; # count: 11
allow 163.181.160.0/24; # count: 16
allow 163.181.164.0/24; # count: 10
allow 163.181.166.0/24; # count: 9
allow 163.181.18.0/24; # count: 2
allow 163.181.199.0/24; # count: 20
allow 163.181.201.0/24; # count: 15
allow 163.181.22.0/24; # count: 12
allow 163.181.23.0/24; # count: 5
allow 163.181.35.0/24; # count: 24
allow 163.181.37.0/24; # count: 5
allow 163.181.38.0/24; # count: 10
allow 163.181.39.0/24; # count: 10
allow 163.181.42.0/24; # count: 11
allow 163.181.49.0/24; # count: 5
allow 163.181.50.0/24; # count: 6
allow 163.181.52.0/24; # count: 2
allow 163.181.57.0/24; # count: 12
allow 163.181.66.0/24; # count: 17
allow 163.181.67.0/24; # count: 34
allow 163.181.70.0/24; # count: 2
allow 163.181.77.0/24; # count: 23
allow 163.181.78.0/24; # count: 10
allow 163.181.79.0/24; # count: 39
allow 163.181.81.0/24; # count: 24
allow 163.181.82.0/24; # count: 4
allow 163.181.85.0/24; # count: 31
allow 163.181.88.0/24; # count: 3
allow 163.181.89.0/24; # count: 1
allow 163.181.90.0/24; # count: 14
allow 163.181.92.0/24; # count: 14
allow 163.181.94.0/24; # count: 4
allow 39.100.171.0/24; # count: 62
allow 39.103.26.0/24; # count: 1
allow 39.108.196.0/24; # count: 9
allow 39.96.152.0/24; # count: 52
allow 47.102.25.0/24; # count: 45
allow 47.104.50.0/24; # count: 46
allow 47.117.201.0/24; # count: 56
allow 47.118.223.0/24; # count: 51
allow 47.120.0.0/16; # count: 129
allow 47.120.230.0/24; # count: 77
allow 47.120.92.0/24; # count: 52
allow 47.123.117.0/24; # count: 26
allow 47.246.0.0/16; # count: 189
allow 47.246.2.0/24; # count: 14
allow 47.246.20.0/24; # count: 14
allow 47.246.22.0/24; # count: 14
allow 47.246.23.0/24; # count: 15
allow 47.246.24.0/24; # count: 14
allow 47.246.36.0/24; # count: 4
allow 47.246.37.0/24; # count: 7
allow 47.246.38.0/24; # count: 4
allow 47.246.4.0/24; # count: 28
allow 47.246.42.0/24; # count: 13
allow 47.246.44.0/24; # count: 8
allow 47.246.45.0/24; # count: 3
allow 47.246.46.0/24; # count: 7
allow 47.246.48.0/24; # count: 14
allow 47.246.49.0/24; # count: 9
allow 47.246.50.0/24; # count: 21
allow 8.132.0.0/16; # count: 206
allow 8.132.18.0/24; # count: 132
allow 8.132.19.0/24; # count: 74
allow 8.141.176.0/24; # count: 50
注释里面的 count 是该网段有多少个阿里云 DCDN IP。
以上结果仅供参考,如有需要,可以参考这些 IP 网段。
可以看到 NGINX 的白名单中有部分还采用了 16 位子网掩码,将 IP 范围进一步扩大到 65536 个。
总结
以上就是如何通过阿里云 OpenAPI 获取 DCDN 节点 IP 白名单的具体方法。
如果你达到了调用 DescribeDcdnL2Ips - 查询L2节点IP地址
接口的要求,那么实际上直接调用这个接口会更加方便。
如果你没达到要求的话,可以参考本文的实现方案,虽然不够优雅,但应该够用了。
喜欢的话就点个关注吧~
欢迎在评论区评论~
- 本文链接: https://wp.cmyr.ltd/archives/how-to-obtain-the-whitelist-of-dcdn-node-ip-addresses-through-alibaba-cloud
- 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
欢迎关注我的其它发布渠道
发表回复
要发表评论,您必须先登录。