WebP Cloud 使用 Cloudflare Workers 回源以保护回源服务器并开始提供回源时间信息
This article is also available in English, at Incorporate Cloudflare Workers for origin fetching to safeguard the origin fetcher’s IP.
WebP Cloud Services 的两大服务——Public Service 和 WebP Cloud 均使用了 Cloudflare 的 CDN,用于获得一个全球相对好看的延迟响应以及保护我们的基础设施(WAF,隐藏源站 IP,Rate limit 等等),但是在整个步骤中还有缺失的一环是没有被 Cloudflare 保护的,即我们的回源请求。
从上图中我们可以看到,当访客访问了一个不在 WebP Cloud 上缓存的图片,WebP Cloud 会先访问源站拿到对应的原图,并在本地转换后输出图片,这一步对于 WebP Cloud 而言就是直接对源站发起了一个 HTTP 请求,而这里则会暴露 WebP Cloud 用于回源的机器的 IP,如果有恶意攻击者尝试攻击这个 IP 导致路由被黑洞,那对于后续请求而言 WebP Cloud 将无法正确回源,间接导致了我们服务不可用。
安全是我们平台的第一要务,保护基础设施不被攻击也是安全的一部分,对于这个问题在设计之初便被考虑,我们尝试过以下方式:
- 用 Cloudflare WARP 回源
- 发现 WARP 的配置较为麻烦,不能直接运行在容器中,如果需要运行在 Host 上的话 WARP 提供的 SOCKS Port 监听在 Host 上的
127.0.0.1
,需要额外的socat
容器转发到我们的环境中,使用起来有过几次掉线的经历
- 发现 WARP 的配置较为麻烦,不能直接运行在容器中,如果需要运行在 Host 上的话 WARP 提供的 SOCKS Port 监听在 Host 上的
- 用商业 VPN 回源
- 不是非常稳定,商业 VPN 似乎并不是为了 24*7 连接设计的,使用过程中多次断线经历感觉不太友好
- 用 Shadowsocks 回源
- 这是我们在使用 Worker 回源前的方式,我们有多台机器运行了 Shadowsocks Server ,并在我们的容器内配置了
http_proxy=socks5://shadowsocks:1080
环境变量,由于我们的程序是用 Go 编写的,在加入了这个环境变量之后所有的对外请求都会使用代理访问,但我们回源的机器 IP 还是会暴露出来,所以依旧不是很完美
- 这是我们在使用 Worker 回源前的方式,我们有多台机器运行了 Shadowsocks Server ,并在我们的容器内配置了
最终我们考虑使用 Workers 来回源,具体的流程是:
- 编写一个 Worker 并部署到 Cloudflare
- Worker 接受一个 POST 请求关于需要请求的源站信息
- Worker 解析对应的请求并向源站发起 HTTP 请求拿到图片
- Worker 将图片返回给 WebP Cloud
在这个逻辑下,源站能看到的回源 IP 仅仅是 Cloudflare Worker 的 IP,例如一个可能的 Header 如下:
Received a request from: 162.158.163.132
Host: test.nova.moe
Connection: Keep-Alive
accept-encoding: gzip
X-Forwarded-For: 2a06:98c0:3600::103
CF-RAY: 81d0f5b4853a5f3b-SIN
X-Forwarded-Proto: http
CF-Visitor: {"scheme":"http"}
CF-EW-Via: 15
CDN-Loop: cloudflare; subreqs=1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
User-Agent: WebP Cloud Services - Dev
cf-worker: webp.se
CF-Connecting-IP: 2a06:98c0:3600::103
Worker 的写法也很简单,只要在接受了 POST 请求之后解析一下并传入对应的 Header 即可,一个简化的示例如下:
async function handleProxy(post_body) {
const headers = {
"Accept": post_body.accept,
"User-Agent": post_body.user_agent
};
const response = await fetch(post_body.origin_url, {
method: post_body.request_name,
headers: headers
});
if (response.ok) {
const res = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
return res;
} else {
return new Response(response.statusText || "Unknown Error", {
status: response.status,
statusText: response.statusText
});
}
}
export default {
async fetch(request, env, ctx) {
// {
// "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
// "user_agent": "WebP Cloud Services/1.0",
// "origin_url": "https://docs.webp.sh/images/webp_server.jpg",
// "request_name": "GET"
// }
try {
const post_body = await request.json();
return handleProxy(post_body);
} catch (error) {
return new Response("Invalid JSON data", { status: 400 });
}
},
};
有了 Worker 回源之后,我们的基础设施便全部隐藏在 Cloudflare 的网络内了,又减少了一个潜在的攻击面,也让我们回源请求变得更加稳定。
此外,最近有客户向我们咨询每个请求的回源时间是多少并希望可以用此来了解整个请求的生命周期(从浏览器发出请求,经过 Cloudflare 到达 WebP Cloud ,在 WebP Cloud 回源完成后转换图片并返回),这次我们也加入了对应的调试信息,目前我们在返回数据加入了两个额外的 Header:
x-webpcloud-cost
x-webpcloud-fetch-cost
一个请求示例如下:
curl -I -H "Accept: image/webp" https://1303b8f.webp.li/pics/fk7-model3/a052-3.JPG
HTTP/2 200
...
content-type: image/webp
content-length: 514932
access-control-allow-origin: *
x-powered-by: WebP Cloud Services (HIO)
x-webpcloud-cache: Miss
x-webpcloud-cost: 906
x-webpcloud-fetch-cost: 593
etag: W/"516265-4FEF7623"
...
x-webpcloud-cost
表示 WebP Cloud 在接受到请求后直到完成响应的耗时,其中 x-webpcloud-fetch-cost
表示我们的服务回源的耗时,单位均为毫秒(ms)。
WebP Cloud Services 团队是一个来自上海和赫尔辛堡的三人小团队,由于我们不融资,且没有盈利压力 ,所以我们会坚持做我们认为正确的事情,力求在我们的资源和能力允许范围内尽量把事情做到最好, 同时也会在不影响对外提供的服务的情况下整更多的活,并在我们产品上实践各种新奇的东西。
如果你觉得我们的这个服务有意思或者对我们服务感兴趣,欢迎登录 WebP Cloud Dashboard 来体验,如果你好奇它还有哪些神奇的功能,可以来看看我们的文档 WebP Cloud Services Docs,希望大家玩的开心~
Discuss on Hacker News