Rails Rails 搭配 OSS 最佳实践

lanzhiheng · 2022年07月20日 · 最后由 oatw 回复于 2022年07月21日 · 672 次阅读

最近无意中发现自家的项目在搭配 OSS 使用的时候有较为严重的安全漏洞,花了些时间,总算是把他们都处理掉了。这篇文章简单总结一下 OSS 服务在 Rails 项目中的最佳实践(自以为的)。原文链接: https://step-by-step.tech/posts/oss-best-practice-in-rails

几乎所有静态资源都是托管到 OSS 服务上的,那么如何跟它打交道是一门艺术。

前言

一般我们都不会采用 OSS 直连下载的方式去分享上面的资源,因为这样一来会面临两个问题

  1. 必须要开放 OSS 资源的公共读权限,这样其他人才能够直接通过 OSS 的 URL 来下载对应的数据。
  2. OSS 下行流量会相对比较昂贵。

为了解决这个问题,一般都会采用CDN 加速 OSS的方式。这样一来

  1. OSS 服务可以完全私有化。
  2. CDN 下行流量比 OSS 下行流量要便宜,并且 CDN 还有缓存机制,访问速度能加快。

CDN 权限过度配给

这里我就拿阿里云的 CDN 与 OSS 服务来作为教材。由上述描述可得知,我们的 OSS 服务已经完全私有化了,只能够通过 CDN 的方式来访问 OSS 的内容。这种时候我们很容易一个不小心就给 CDN 完全管理 OSS 服务的权限

Screen Shot 2022-07-17 at 20_mosaic.png

另外,在 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 推送内容。要是这个漏洞给有心人发现,后果就不堪设想了,一个脚本就能摧毁平台辛苦运营的静态资源。

解决方案

笔者尝试了以下几种方式都无法根治问题

  1. Referer 防盗链:这种方法是根据 HTTP 请求的 Referer 头部字段来实现防盗链的功能,然而笔者的主营产品是 APP + 小程序,这种做法其实并无效果。
  2. URL/远程鉴权方式:这种做法需要有额外的鉴权服务,会拖慢CDN的访问速度,配置相对麻烦。而且无法单独针对PUT/GET请求(因为我只想针对PUT请求做鉴权)。
  3. UA 黑/白名单:这种做法主要是利用 UserAgent 字段来限制黑/白名单。在这里笔者只能用白名单的方式,然而笔者公司客户端众多(UA 很多),需要维护的白名单列表就很长,很容易就会被仿造,故而这种做法也不保险。

思前想后最好的做法还是,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 的权限

Screen Shot 2022-07-17 at 20_mosaic (1).png

其实就是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 服务授予不必要的权限,在一定程度上能提高服务的安全性。

搞得太复杂,头发容易掉光呀

比较恶心人的场景是 —— 资源又要写又要读,且都跟用户权限相关,且用户量又比较大……Umm,那不就类似网盘了?感觉不是小厂搞得起的哦,所以好奇找了点资料,蹭楼搬运一下。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号