利用腾讯云API网关和云函数来保护CDN流量不被恶意刷量

2021-09-02 18:11:02 浏览数 (1)

CDN的被刷流量一直是很多站长头疼的问题,一旦被刷流量, 只需要一晚上就能产生少则几百多则上万的账单。由于自己博客的图片用的也是腾讯云的COS CDN,为了防止“睡了一晚上,早上起来房子归腾讯了”的情况发生,所以就一直在思考怎么解决这个问题,要不然哪一天被恶意刷了流量,房东可不会让我卖他的房子的。

刚开始想的是在腾讯云的CDN中设置最高带宽以及限频,后来群里有小伙伴提醒只设置这个是不够的,因为这个最高带宽以及限频是针对单个节点的,别人可以分别攻击不同的节点来消耗CDN流量。但是腾讯云的CDN有鉴权的功能,利用鉴权的功能对url进行签名,生成一个具有有效期(比如2秒)的临时链接,然后对鉴权的接口做限频就行了。

在这个流程中,用户想要访问图片资源,需要先访问鉴权服务器,鉴权服务器承担了限频的作用。比如说,鉴权服务器地址是 https://sign.foo.bar/, cdn的地址是 https://sign.cdn.foo.bar。如果我们想访问 /static/logo.png,整个请求流程就是就如下图所示

上述过程虽然看起来比较麻烦,但是都是由浏览器自动完成的,对普通用户来说是无感知的。 由于鉴权服务器返回的CDN链接是带有效期的(这个有效期自己可以在CDN的后台进行设置,比如设置为2秒),因此,别人想访问CDN资源,就必须重新请求鉴权服务器获取新的CDN链接,鉴权服务器就可以根据IP以及访问频次等因素来决定是否响应这次请求,从而保护CDN的流量不被盗刷。在这种模式下,一方面鉴权服务器需要自己来开发,另一方面,因为所有对资源的请求都需要先经过鉴权服务器,因此鉴权服务器的的性能也决定了用户的体验。

刚开始我也是按照这种流程,在自己的图床中集成了鉴权的功能,但是流量控制这方面做的比较薄弱。直到前两天偶然间看到群里的小伙伴说到了腾讯云的API网关功能,瞬间想到了这个问题,是否可以利用API网关自带的流量控制来做鉴权呢?经过一番实验,是完全可行的。大概思路是利用云函数来生成带签名的CDN链接,然后用API网关来代理云函数。云函数和API网关都有免费的额度,大概100万次,足够一般博客使用,因此,基本上可以说是零成本了。利用云函数和API网关的流程如下图所示

下面是详细过程。

在腾讯云CDN后台开启鉴权功能

进入到腾讯云CDN的后台,打开域名管理,然后进入到对应域名的管理界面,在访问控制中,开始鉴权功能

目前腾讯云CDN支持多种鉴权模式,不同模式生成的链接和签名算法也略有不同,这里,我选用了TypeA模式,如果小伙伴有开发能力的话,也可以选择别的鉴权模式。有效时间我设置成了2秒,基本上也够用了。注意,打开此开关以后,直接访问CDN的链接会报403错误,如果你的博客有存量的CDN图片的话,就要想办法把原来的图片链接都更新成鉴权链接了。记住这里的鉴权密钥和签名参数,后面会用到。

创建云函数

访问CloudBase,开通CloudBase功能。这个功能我早就开通了,因此不清楚未开通的界面是什么样子的,应该很简单。新用户可以创建一个免费的环境,创建环境以后,点击新创建的环境,进入到这个环境的管理界面,点击左侧菜单的【云函数】,然后创建一个新的云函数。

云函数支持的运行环境还是挺多的,有nodeJS、Go、php、python以及java。小伙伴可以选择自己擅长的语言来进行云函数编写。我对Go语言稍微熟悉一些,因此这个地方选的是Go环境,后面的代码我也用Go语言进行演示。函数内存选择最小的128MB就行了,然后点击【下一步】,再点击【下一步】,这个函数就创建完成了。

注意不要开通HTTP访问服务,除非你清楚你在做什么

点击这个云函数,进入到配置页面的【函数代码】页面,可以看到有一个在线编辑器。如果使用的是nodejs或者python以及php的话,应该就可以直接在这个在线编辑器里修改代码了,当然也可以用更方便的CLI工具来进行本地编辑并上传,这个不是这篇文章的重点。这里我遇到了一个很坑的地方就是go的话只能本地编译好然后用提交方法选【本地zip包上传】,在线编辑是不生效的。

下面是golang的生成鉴权url的代码,至于代码是怎么写的,小伙伴有兴趣可以自己去看一下云函数的相关文档。

代码语言:javascript复制
func signUrl(ctx context.Context, event events.APIGatewayRequest) (interface{}, error) {
	path := event.Path
	resourcePath := path[5:]
	ts := time.Now().Unix()
	rand_str := uuid.NewString()[:8]
	signStr := fmt.Sprintf("%s-%d-%s-%d-%s", resourcePath, ts, rand_str, 0, signKey)
	sign := md5.Sum([]byte(signStr))
	queryParams := fmt.Sprintf("%s=%s", signName, fmt.Sprintf("%d-%s-%d-%x", ts, rand_str, 0, sign))
	resp := &events.APIGatewayResponse{
		StatusCode: 302,
		Headers: map[string]string{
			"Location": fmt.Sprintf("%s%s?%s", domain, resourcePath, queryParams),
		},
	}
	return resp, nil
}

全部代码点击这里查看,或者直接附件:点击这里下载我打包好的压缩包,用【本地zip包上传】上传就可以使用。生成签名的时候需要【鉴权密钥】和【签名参数】,当然为了能够返回CDN的地址,还需要直到CDN的域名。因为云函数支持配置环境变量,因此我是通过环境变量的方式来配置和读取这些值的。在云函数的【函数配置】页面可以配置环境变量,在我的例子中,主要需要配置 domain(CDN的域名)、signName(签名参数)、signKey(签名参数)

配置了环境变量以后,在云函数代码中就可以从环境变量中读取这些配置项了。把压缩包上传,然后就可以测试一下云函数了,点击当前页面右上角的【测试】按钮,在输入框中输入下面的信息

代码语言:javascript复制
{    "path": "/sign/a/b/c/d/a.jpg"}

然后点击执行,如果返回200,代表成功

云函数创建成功以后,就需要开通API网关,然后通过API网关来访问我们的云函数。因此下一步就是开通API网关并创建对应的API。

开启以及配置API网关

访问腾讯云的API网关并开启API网关功能, 然后点击左侧菜单栏中的服务,然后点新建服务。这里要注意以下,服务的区域要和云函数环境的区域选择一致,否则后面在添加云函数的时候就找不到对应的云函数。前端类型根据自己需要的协议来选,访问方式选择 公网,虽然下面显示了费用,但是每个月是有100万次免费的调用次数和免费的1GB流量,由于我们这里只返回状态码和带签名的CDN地址,因此需要的流量也非常少。

创建了服务以后,点击进入服务的管理页面,新建一个【通用API】,根据需要填写信息,如果需要使用我上面创建的云函数的代码的话,路径必须填写/sign/,请求方法填写GET,鉴权类型选择免认证,然后点击下一步,这一步非常重要,后端类型选择云函数SCF,云函数选择【事件函数】,如果服务所在的区域和云函数所在的区域相同的话,这里就可以看到前面创建的云函数了,选择它就行了。然后,响应集成这个必须打上勾,选中了响应集成,API网关才会去解析云函数的结果并作出响应的响应,否则就是简单的结果输出了。这里配置好以后,选择【下一步】,下一步没什么好配置的,点击【完成】

选择【完成】以后,会弹出对应的对话框,这里直接选择【发布】,然后选择【发布服务】

服务发布完成后,我们在管理API中可以看到我们新创建的API,

然后点击右侧的【更多】按钮,选择【调试】,在路径中,将/sign/*(这里跟创建API时填写的路径一样,我填写的是/sign/)修改为/sign/a/b/c.jpg,

点击发送请求。然后右边的返回结果如果返回码是302,就代表我们已经快成功了

API配置成功以后,到【基础配置】面板,右上方有一个【网络信息】卡片,复制里面的【公网地址】,公网地址是下面这样子一个链接

https://service-xxxxxx-xxxxx.sh.apigw.tencentcs.com:443

然后,在这个链接后面加上 /sign/{cdn资源路径},例如,我的CDN的地址是 https://cdn.foo.com/static/logo.png,那么我们要访问的地址就是 https://service-xxxxxx-xxxxx.sh.apigw.tencentcs.com:443/sign/static/logo.png,在浏览器中输入这个地址,回车,如果一切都没问题的话,我们就可以访问到我们的资源了。这时候请注意地址栏的链接,会变成CDN资源带签名的链接。等待前面设置CDN鉴权时候设置的有效期的时间后,再刷新页面,这时候请求就被拒绝了

刷新页面后,请求就被拒绝了。

从上也可以看出,浏览器是可以自动处理302的重定向的,我们的博客只需要插入这样的图片链接https://service-xxxxxx-xxxxx.sh.apigw.tencentcs.com:443/sign/static/logo.png即可。当然也可以绑定域名,不过绑定域名涉及到证书的申请,篇幅会比较多,可以自己研究一下怎么绑定,也很简单。

配置流量控制

到了这里,就差最后的流量控制,即限频了。通过流量控制,可以设置某段时间内最大总请求数以及每个IP的最大访问数。选择API网关页面左侧菜单中的【插件】,点击【新建】,类型选择【基础流量控制】,控制时长根据自己需要选择填写,比如这里选择 1小时,API流控值填写10000,客户端IP流控制填写100, 代表这个API一小时内只允许被调用10000次,每个IP每小时只允许调用这个API100次。

创建了插件以后,需要到API接口中去绑定这个插件。进入到前面创建的API的控制面板,点击上方菜单中的【插件管理】,绑定这个插件即可

除了这个地方可以设置限频以外,在服务的【基础配置】以及【策略】中也可以设置不同维度的流量控制,在【使用计划】中甚至可以设置配额,将配额设置为免费的1000000次,我们就不用担心使用超过配额从而产生额外的费用了。

价格计算

到这里,本篇文章配置过程基本上就结束了。最后我们来算一下使用费用。整个过程涉及到的费用由云函数的【资源使用量】、API网关的【调用次数】和【外网出流量】。云函数到API网关这一部分走的是内网,应该不会产生流量费用。

API网关免费调用次数是100万次,针对我们的场景,每次调用API网关大概会产生1k的流量(根据调试API时候返回的信息估算),那么免费的1GB刚好可以支撑1024*1024也就是100万次调用。超出额度的话,大概3到6分钱1万次调用, 8毛钱1GB流量,约等于不要钱。

云函数的话,免费额度是1000GBs,我们选择的最小的内存是0.128GB,虽然每次调用只需要1ms,但是它计费是最低100ms的,即使只用了1ms,也按照100ms来计算,那么每次调用,花费0.128乘以0.1就是0.0128GBs,1000GBs最多能够调用8000次左右,超过的部分只能自己额外去购买。云函数【资源使用量】的定价是0.00011108元1GBs,那么1元钱除以 0.00011108再除以0.0128得703321,即1元钱就可以调用云函数70万次,约等于不要钱。

综合算下来,即使我们使用超过了免费的额度,按量付费的费用总体大概是6元钱100万次,可以说是非常便宜了。

总结

上面就是我想出的怎么用云函数和API网关以及CDN的鉴权功能来防止CDN流量被额外刷欠费的情况。个人能力有限,如果小伙伴们看出上述流程由什么漏洞的话,欢迎在评论中指出喔

0 人点赞