最近无意中发现自家的项目在搭配 OSS 使用的时候有较为严重的安全漏洞,花了些时间,总算是把他们都处理掉了。这篇文章简单总结一下 OSS 服务在 Rails 项目中的最佳实践(自以为的)。原文链接: https://step-by-step.tech/posts/oss-best-practice-in-rails
几乎所有静态资源都是托管到 OSS 服务上的,那么如何跟它打交道是一门艺术。
一般我们都不会采用 OSS 直连下载的方式去分享上面的资源,因为这样一来会面临两个问题
为了解决这个问题,一般都会采用CDN 加速 OSS的方式。这样一来
这里我就拿阿里云的 CDN 与 OSS 服务来作为教材。由上述描述可得知,我们的 OSS 服务已经完全私有化了,只能够通过 CDN 的方式来访问 OSS 的内容。这种时候我们很容易一个不小心就给 CDN 完全管理 OSS 服务的权限
另外,在 Rails 的 ActiveStorage 中,搭配上李华顺维护的activestorage-aliyun适配器,简直可以说是无缝衔接。配置大概如下
aliyun:
service: Aliyun
access_key_id: "your-oss-access-key-id"
access_key_secret: "your-oss-access-key-secret"
bucket: "bucket-name"
endpoint: "https://file.myhost.com"
public: false
# Enable cname to use custom domain
cname: true
这样一来问题就大了。配置里面的endpoint
其实就是自定义的 CDN 服务域名,假设资源链接如下https://file.myhost.com/resources/d2616414adfe8e76adf3c5997445b40d
,那么访问资源内容的时候就可以
> curl https://huiliu-staging-resources.huiliu.net/resources/rftxt8qbepch388qtgyqr5vdkmhg
还可以通过以下方式来推送数据
> curl -X PUT -d 'data' https://huiliu-staging-resources.huiliu.net/resources/rftxt8qbepch388qtgyqr5vdkmhg
任何人都能往这个 URL 推送内容。要是这个漏洞给有心人发现,后果就不堪设想了,一个脚本就能摧毁平台辛苦运营的静态资源。
笔者尝试了以下几种方式都无法根治问题
思前想后最好的做法还是,CDN 只拥有 OSS 服务的回源与只读权限,如果要上传文件,只能通过 OSS 提供的接口来上传。其实这样才是比较正确的做法,阿里云官方也不建议用 CDN 加速来做上传,因为上传大小有限制,且没有加速效果。那么在 Rails 里面,权限收回之后代码大概就是这个样子。
aliyun:
service: Aliyun
access_key_id: "your-oss-access-key-id"
access_key_secret: "your-oss-access-key-secret"
bucket: "bucket-name"
endpoint: "https://oss-cn-beijing.aliyuncs.com"
# path prefix, default: /
path: "my-app-files"
# Bucket public: true/false, default: true, for generate public/private URL.
public: true
别忘了收回 CDN 的权限
其实就是activestorage-aliyun这个仓库最原始的例子,相当于我们 Rails 的后台只通过 OSS 服务所提供的 endpoint 与该服务打交道。那么就剩下一个问题了,那就是现在当我们要访问资源的时候,依旧会用到 OSS 的链接。
> ActiveStorage::Blob.first.service_url
=> "https://bucket-name.oss-cn-beijing.aliyuncs.com/my-app-files/4p8xt1zztz5prt98q9xjjx9hgk8s"
要解决这个问题也挺简单,简单地复写一下 ActiveStorage 仓库里面的service_url
方法就可以了。我这里提供一下我的做法
Rails.application.config.after_initialize do
ActiveStorage::Blob.class_eval do
# 备份原来的方法
alias_method :backup_service_url, :service_url
# 设置一个已经CNAME到对应OSS服务的URL的域名
def cdn_host
"file.myhost.com"
end
def service_url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline, filename: nil, **options)
url = backup_service_url(expires_in: expires_in, disposition: disposition, filename: filename, **options) # 使用原来的方法获取一个URL
url_object = URI(url)
url_object.host = cdn_host # 把OSS域名替换成CDN域名
url_object.to_s
end
end
end
看看现在的效果
> ActiveStorage::Blob.first.service_url
=> "https://file.myhost.com/my-app-files/4p8xt1zztz5prt98q9xjjx9hgk8s"
上传的时候还是老样子,使用 ActiveStorage 提供的方法ActiveStorage::Blob.create_after_upload!
就好了,具体就不演示了。
在 Rails 的后台跟 OSS 服务打交道的时候使用的是 OSS 服务提供的 endpoint,而当面向客户,向用户提供访问链接的时候则提供 CDN 服务映射的 URL。简单来说后者是公开的,然而由于我们前面已经取消了 CDN 服务对 OSS 服务的管理权限,目前只剩下只读权限,因此有心人也无法通过该访问链接篡改资源的内容。即便是通过 CDN 的 URL 推算出对应 OSS 服务的 URL,又因为我们的 OSS 服务已经设置成私有的了,需要账号 + 密码才能对里面的内容进行管理,在某种意义上来说是安全的。
这篇文章简单总结了一下笔者这段时间对自家项目依赖的 OSS 服务所做的整改。最开始的 CDN 服务拥有管理 OSS 服务的权限,并可以借用 CDN 的 URL 篡改 OSS 的内容。后面咨询了不少朋友以及阿里云官方客服,才有了如今的做法。采用 CDN 的方式(回源)来读取 OSS 服务的内容,走 CDN 下行流量,不仅速度可观而且价格便宜。然而以 CDN 的方式来上传文件确实会有问题,没有加速效果,大小限制不说,还不得不对 CDN 服务授予不必要的权限,于是乎针对上传来说还是用 OSS 原生的做法比较好。这样不仅 OSS 可以设置为私有的,也不需要给 CDN 服务授予不必要的权限,在一定程度上能提高服务的安全性。