前言
事情的起因還得追溯到給博客加了 全站加速晕讲。
依靠全站加速登下,還減輕了一次網(wǎng)站攻擊陶舞,詳見:記一次網(wǎng)站被 DDoS(CC)攻擊 的過程和應(yīng)對(duì)方案
在設(shè)置了全站加速之后,通過域名默認(rèn)會(huì)走阿里云的 DCDN 節(jié)點(diǎn)遭铺,只有在未緩存的時(shí)候會(huì)訪問源站絮记。
此時(shí)瞪讼,源站就會(huì)遇到一個(gè)需求:最好僅允許阿里云的 DCDN 節(jié)點(diǎn)進(jìn)行訪問。
也就是說蛛壳,需要獲取到阿里云的 DCDN 節(jié)點(diǎn)的 IP 列表杏瞻,以添加到白名單中所刀。
就此,開始了折騰之路捞挥。
開始
由于之前已經(jīng)用過阿里云 OpenAPI 進(jìn)行 CDN 的管理(如何通過阿里云 OpenAPI 管理 CDN)浮创,所以這一次也采用了阿里云 OpenAP,從而實(shí)現(xiàn)自動(dòng)化設(shè)置砌函。
翻閱全站加速相關(guān)文檔可以看到一個(gè)接口:DescribeDcdnL2Ips - 查詢 L2 節(jié)點(diǎn) IP 地址
這正是我需要的斩披。
由于文檔里沒有說前置要求,所以我想當(dāng)然的認(rèn)為只要提交工單就可以獲得權(quán)限胸嘴。
中道崩阻
結(jié)果:
不是雏掠,大哥,你在 DCDN 的文檔中又沒有說明前置要求劣像,沒寫就是沒要求才對(duì)乡话。
再者,CDN 節(jié)點(diǎn)的 IP 是什么需要藏著掖著的東西嗎耳奕?你不公開出來我怎么設(shè)置白名單绑青?
隔壁 Cloudflare 就是把 IP 地址范圍全部公布出來的,例如 IP 地址范圍屋群。
所以什么要設(shè)置個(gè)門檻闸婴?不是很能理解。
峰回路轉(zhuǎn)
但很快啊芍躏,我又發(fā)現(xiàn)另一個(gè)接口:DescribeDcdnIpInfo - 驗(yàn)證 IP 節(jié)點(diǎn)
雖然沒法直接獲取 DCDN 節(jié)點(diǎn)的 IP 邪乍,但是可以驗(yàn)證一個(gè) IP 是不是阿里云 DCDN 節(jié)點(diǎn)。
解決問題
所以接下來解決方案就很簡單了对竣。
我手動(dòng)把 IP 地址驗(yàn)證一遍不就完事了庇楞?
當(dāng)然了,考慮到 IPv4 一共有 42 億個(gè) IP否纬,顯然是不可能窮舉一遍的吕晌。
那么我就得找個(gè)地方獲取訪問 IP。
獲取 IP 地址
而 NGINX 的日志临燃,就是一個(gè)獲取 IP 的好地方睛驳。
以下是一個(gè)從日志中獲取 IP 地址的 TypeScript 函數(shù):
// 定義提取 IP 地址的函數(shù)
async function extractUniqueIps(logFilePath: string) {
// 使用 Set 來存儲(chǔ)唯一的 IP 地址
const uniqueIps = new Set<string>()
// 使用流式讀取日志文件
const readStream = fs.createReadStream(logFilePath, { encoding: 'utf-8' })
const rl = createInterface({ input: readStream })
// 逐行處理日志文件
rl.on('line', (line) => {
// 使用正則表達(dá)式提取 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])
}
})
// 返回一個(gè) Promise,當(dāng)讀取結(jié)束時(shí)膜廊,解析為唯一的 IP 地址數(shù)組
return new Promise<string[]>((resolve) => {
rl.on('close', () => {
resolve([...uniqueIps])
})
})
}
采用流式讀取是因?yàn)槿罩疚募赡軙?huì)有點(diǎn)大乏沸,全部讀取進(jìn)來有點(diǎn)占內(nèi)存。
經(jīng)過這一通折騰溃论,我拿到了一個(gè)約 28000 個(gè) IP 地址屎蜓。
有一說一,這個(gè)量還是有點(diǎn)大钥勋,50 次/秒的話要跑 560 秒炬转,約 9.3 分鐘辆苔,這個(gè)時(shí)間就有點(diǎn)久了。
所以扼劈,可以采用 IP 屬地進(jìn)行初次過濾驻啤。
獲取 IP 屬地
通過 lionsoul2014/ip2region 這個(gè)項(xiàng)目可以拿到所有 IP 的屬地。
雖然數(shù)據(jù)上可能有點(diǎn)過時(shí)荐吵,但用來初步篩選已經(jīng)夠了骑冗。
以下是一個(gè)使用 JavaScript 實(shí)現(xiàn)的查詢腳本(參考:ip2region nodejs 查詢客戶端實(shí)現(xiàn))
// 導(dǎo)入包
const Searcher = require('.')
// 指定ip2region數(shù)據(jù)文件路徑
const dbPath = 'ip2region.xdb file path'
try {
// 創(chuàng)建searcher對(duì)象
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 節(jié)點(diǎn)贼涩。
判斷 DCDN 節(jié)點(diǎn)
接下來就是調(diào)用 DescribeDcdnIpInfo
接口來驗(yàn)證是否為阿里云 DCDN 節(jié)點(diǎn)了。
以下是一個(gè)用 TypeScript 實(shí)現(xiàn)的 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初始化賬號(hào)Client
* @returns Client
*
* @throws Exception
*/
static createClient(): dcdn20180115 {
// 工程代碼泄露可能會(huì)導(dǎo)致 AccessKey 泄露遥倦,并威脅賬號(hào)下所有資源的安全性。以下代碼示例僅供參考占锯。
// 建議使用更安全的 STS 方式袒哥,更多鑒權(quán)訪問方式請(qǐng)參見:https://help.aliyun.com/document_detail/378664.html。
const config = new $OpenApi.Config({
// 必填消略,請(qǐng)確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_ID堡称。
accessKeyId: process.env['ALIBABA_CLOUD_ACCESS_KEY_ID'],
// 必填,請(qǐng)確保代碼運(yùn)行環(huán)境設(shè)置了環(huán)境變量 ALIBABA_CLOUD_ACCESS_KEY_SECRET艺演。
accessKeySecret: process.env['ALIBABA_CLOUD_ACCESS_KEY_SECRET'],
})
// Endpoint 請(qǐng)參考 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 {
// 復(fù)制代碼運(yùn)行請(qǐng)自行打印 API 的返回值
return (await client.describeDcdnIpInfoWithOptions(describeDcdnIpInfoRequest, runtime)).body
} catch (error) {
// 此處僅做打印展示却紧,請(qǐng)謹(jǐn)慎對(duì)待異常處理,在工程項(xiàng)目中切勿直接忽略異常胎撤。
// 錯(cuò)誤 message
console.log(error.message)
// 診斷地址
console.log(error.data['Recommend'])
}
}
}
最后啄寡,把是 阿里云 DCDN 的 IP 寫入到文件。
至此哩照,就大功告成了!
生成網(wǎng)段
不過懒浮,單純的用 IP 的話實(shí)際上容易誤判飘弧,原因是阿里云 DCDN 的 IP 還是挺多的,靠從 NGINX 日志獲取的方法效率低不說砚著,還很滯后次伶。
所以,將 IP 改為網(wǎng)段更加合理一些稽穆。
以下是一個(gè)從 IP 地址列表生成 IP 網(wǎng)段的 TypeScript 腳本冠王。
/**
*
*
* @author CaoMeiYouRen
* @date 2024-09-16
* @param ips IP地址
* @param [netMask=24] 子網(wǎng)掩碼位數(shù)
*/
function getSubnetCounts(ips: string[], netMask = 24) {
// 創(chuàng)建 Map 來存儲(chǔ)網(wǎng)段及其出現(xiàn)頻率
const subnetCounts = new Map<string, number>()
// 遍歷每個(gè) IP 地址
for (const ip of ips) {
// 提取前 n 段作為網(wǎng)段
const segments = ip.split('.')
const subnetSegments = segments.slice(0, netMask / 8)
const subnet = `${subnetSegments.join('.')}${'.0'.repeat(4 - subnetSegments.length)}/${netMask}`
// 如果網(wǎng)段不存在于 subnetCounts 中,初始化為 0
if (!subnetCounts.has(subnet)) {
subnetCounts.set(subnet, 0)
}
// 增加網(wǎng)段的計(jì)數(shù)
subnetCounts.set(subnet, subnetCounts.get(subnet) + 1)
}
return [...subnetCounts].map(([subnet, count]) => ({ subnet, count }))
}
然后就可以生成 NGINX 的白名單了,生成的結(jié)果如下:
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 是該網(wǎng)段有多少個(gè)阿里云 DCDN IP舌镶。
以上結(jié)果僅供參考柱彻,如有需要豪娜,可以參考這些 IP 網(wǎng)段。
可以看到 NGINX 的白名單中有部分還采用了 16 位子網(wǎng)掩碼哟楷,將 IP 范圍進(jìn)一步擴(kuò)大到 65536 個(gè)瘤载。
總結(jié)
以上就是如何通過阿里云 OpenAPI 獲取 DCDN 節(jié)點(diǎn) IP 白名單的具體方法。
如果你達(dá)到了調(diào)用 DescribeDcdnL2Ips - 查詢L2節(jié)點(diǎn)IP地址
接口的要求卖擅,那么實(shí)際上直接調(diào)用這個(gè)接口會(huì)更加方便鸣奔。
如果你沒達(dá)到要求的話,可以參考本文的實(shí)現(xiàn)方案惩阶,雖然不夠優(yōu)雅挎狸,但應(yīng)該夠用了。
喜歡的話就點(diǎn)個(gè)關(guān)注吧~
歡迎在評(píng)論區(qū)評(píng)論~
本文作者:草梅友仁
本文地址:https://blog.cmyr.ltd/archives/8c61e292.html
版權(quán)聲明:轉(zhuǎn)載請(qǐng)注明出處断楷!