David Lee

一个爱做梦的人在这里自说自话

  • 主页
  • 随笔
所有文章 友链 关于我

David Lee

一个爱做梦的人在这里自说自话

  • 主页
  • 随笔

使用nginx+lua实现对阿里云OSS文件的实时解密

2017-11-07

临时接到一个需求,有一个巨大的资源池,传到阿里云的OSS上了,坑爹的是资源池内的文件全是加密的.临上线了才发现这个坑.为了安全也不能把未加密的文件对外传输.所以就得想办法实时解密.

首先想到的就是通过一个反向代理来转发,代理收到内容后,解密然后再回包.

所以首选Nginx了, 虽然我们一直用Nginx作为网关,但是我对其还处于完全不了解的状态,趁机恶补一下知识, 花了2个小时看了淘宝团队写的Nginx开发从入门到精通,最后发现,添加一个module竟然要重新编译nginx?? What the fuck!这不科学…

于是寻寻觅觅,发现了lua-nginx-module,进而发现了已经打包好的openresty.

之前接触过云风大神写的skynet游戏服务端框架,也是使用lua作为开发语言,使用lua的协程特性可以很方便的把异步代码”逻辑同步化”,不过我这次需要实现的解密功能,是属于典型的CPU密集型的应用,和同步异步没啥关系…
如果用脚本语言实现加解密算法,性能可想而知,不过还好,发现了一个叫lua-resty-nettle的加解密库,加解密算法都是native实现的,lua只是作为胶水.所以性能基本上放心了.
再加上找京东的同事考证了一下,他们内部确实也使用了这个东西,所以稳定性应该是没有问题.

技术方案定了,接下来就是写代码了.

看了一下官方文档,lua-nginx-module提供了很多钩子,可以让你通过lua代码去处理,找出我现在最需要的几个钩子:

  • body_filter_by_lua
    把反向代理获取到的body传入自己编写的lua代码中进行处理,需要注意的是这里传入的是一个一个”chunk”,载入一个页面,可能会分为好几次调用过来,所以我们编写的lua代码需要处理这种情况
  • rewrite_by_lua_block
    可以使用lua代码对url进行重写,需要用这个的原因是因为资源池以前是在windows上,之前大小写不敏感,导致很多地方的url引用不区分大小写也可以正常使用, 所以现在迁移到OSS后,大小写敏感了.为了兼容这种情况,只能把所有的文件名全部转换为小写,然后把所有转发请求的url都rewrite为小写
  • header_filter_by_lua_block
    可以使用lua代码重写http头.因为回包经过解密后length变了,所以需要把content_length设为空,这样浏览器就可以识别body是chunk的方式传输的,还需要把content_encoding设为nil,否则oss会根据大部分浏览器的请求,返回一个gzip压缩过的数据,为了处理方便,这里就强行把gzip给关了当然,通过这个钩子只能处理被代理方回包的http头,具体如何强行关闭gzip,后面再说.

具体配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
worker_processes 1;
error_log logs/error.log info;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://资源池的地址;
proxy_set_header Accept-Encoding "";
rewrite_by_lua_block {
ngx.req.set_uri(string.lower(ngx.var.uri))
}
header_filter_by_lua_block {
ngx.header.content_length = nil
ngx.header.content_encoding = nil
}
body_filter_by_lua_file /path/to/lua/resource_decrypt.lua;
}
}
}

注意这一行:

1
proxy_set_header Accept-Encoding "";

使用 proxy_set_header 功能,强行屏蔽掉浏览器gzip压缩格式的请求.所以到被代理方,也就是资源池的请求,永远是不压缩的.

因为lua代码比较长,所以为了方便把它放到一个文件里面了, 然后通过body_filter_by_lua_file 指定文件,效果和body_filter_by_lua是一样的

lua代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
local data, eof = ngx.arg[1], ngx.arg[2]
local des = require "resty.nettle.des"
local buffered = ngx.ctx.buffered
local bufferedSize = ngx.ctx.bufferedSize
local packageSize = 2048000 -- 2m
if not bufferedSize then
bufferedSize = 0
ngx.ctx.bufferedSize = bufferedSize
end
if ngx.ctx.bufferedSize <= packageSize then
if not buffered then
buffered = {}
ngx.ctx.buffered = buffered
end
if data ~= "" then
buffered[#buffered+1] = data
bufferedSize = bufferedSize + #data
if bufferedSize > packageSize then
ngx.arg[1] = table.concat(buffered)
ngx.ctx.buffered = nil
if eof then
ngx.ctx.bufferedSize = nil
end
return
else
ngx.arg[1] = nil
end
end
else
ngx.arg[1] = data
end
if eof then
local whole = table.concat(buffered)
ngx.ctx.buffered = nil
ngx.ctx.bufferedSize = nil
local ds, wk = des.new("password", "cbc", "iv")
local raw = ds:decrypt(whole)
ngx.arg[1] = string.sub(raw, 1, #raw - string.byte(raw, #raw)) -- 需要取最后一个字符作为长度,在解密的字符串中删除掉这个长度的字符数
ngx.arg[2] = true
end

ngx.arg[1]表示当前数据 ngx.arg[2]表示当前数据是否是最后一个chunk.
对ngx.arg[1]赋值表示要传给最终用户(浏览器)的数据,所以处理完数据后别忘了赋值给ngx.arg[1]
因为数据不是一次性发过来的,所以需要进行拼包,如果发现某个文件过大,就不解密直接raw过去
lua代码中用到了ngx.ctx.*, 这个是lua-nginx-module提供的一个global的table,可以用来暂存任意数据.当一个完整的文件没有传送完成时,之前传送的部分就存在这里.

为了解密des,用到了一个第三方lua库,openresty提供了包管理器,可以使用以下命令安装:

1
sudo opm get bungle/lua-resty-nettle

还有一个需要注意的是,des解密后出来的文件总是对不上,后面会莫名其妙多出几个byte,查阅资料后发现,原来PKCS5Padding会填充长度为8的倍数,处理这个问题也很简单,把最后一个byte拿出来作为冗余数据的长度,从解密数据中删除尾部的这个长度的数据即可.

花了一天从确定技术方案到开发完成,保证了项目上线时间.还是蛮有成就感的.

赏

谢谢你请我吃糖

微信
  • nginx
  • lua
  • openresty
  • des
  • 阿里云
  • oss

扫一扫,分享到微信

微信分享二维码
初次使用golang和mongodb开发一个完整产品的一些笔记
通过使用C++变参模板动态生成重载函数来解决Tars框架的消息转发问题
© 2019 David Lee
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链
  • 关于我

tag:

  • stl
  • cpp11
  • cpp14
  • rvalue
  • std::move
  • 右值引用
  • 性能优化
  • map
  • vector
  • perfect forward
  • emplace
  • 随笔
  • 字体
  • ubuntu
  • linux
  • fc-cache
  • emacs
  • 编辑器
  • ide
  • mysql
  • crash
  • libmysqlclient
  • vim
  • 抓包
  • tcpdump
  • wireshark
  • nginx
  • lua
  • openresty
  • des
  • 阿里云
  • oss
  • golang
  • mongodb
  • wechat
  • python
  • celery
  • redis
  • 微信公众号
  • 微信支付
  • pika
  • apt-get
  • cpp
  • tars
  • proxy
  • template
  • variadic template

    缺失模块。
    1、请确保node版本大于6.2
    2、在博客根目录(注意不是yilia根目录)执行以下命令:
    npm i hexo-generator-json-content --save

    3、在根目录_config.yml里添加配置:

      jsonContent:
        meta: false
        pages: false
        posts:
          title: true
          date: true
          path: true
          text: false
          raw: false
          content: false
          slug: false
          updated: false
          comments: false
          link: false
          permalink: false
          excerpt: false
          categories: false
          tags: true
    

  • C++11的右值引用和move语义

    2019-07-30

    #stl#cpp11#cpp14#rvalue#std::move#右值引用#性能优化

  • Modern C++ 插入元素到容器的正确做法

    2019-04-25

    #stl#cpp11#cpp14#map#vector#perfect forward#emplace

  • 端口明明已经监听了为什么还是访问不了呢

    2019-04-09

  • 阿里云redis迁移到pika

    2018-11-02

    #阿里云#redis#pika

  • 初次使用golang和mongodb开发一个完整产品的一些笔记

    2018-11-01

    #golang#mongodb#wechat#python#celery#redis#微信公众号#微信支付

  • 使用nginx+lua实现对阿里云OSS文件的实时解密

    2017-11-07

    #nginx#lua#openresty#des#阿里云#oss

  • 通过使用C++变参模板动态生成重载函数来解决Tars框架的消息转发问题

    2017-11-02

    #cpp14#cpp#tars#proxy#template#variadic template

  • 使用tcpdump抓包并通过wireshark图形化工具来分析网络问题

    2017-09-28

    #抓包#tcpdump#wireshark

  • 解决libmysqlclient在多线程环境下初始化会有几率crash的问题

    2017-08-31

    #随笔#ubuntu#mysql#crash#libmysqlclient

  • 找到某个命令依赖的软件包名称

    2017-08-28

    #ubuntu#linux#apt-get

  • 解决libmysqlclient 在多线程下调用mysql_options 函数出现Segmentation fault的问题

    2017-08-28

    #随笔#ubuntu#mysql#crash#libmysqlclient

  • 在ubuntu上安装source-code-pro字体

    2017-08-25

    #随笔#字体#ubuntu#linux#fc-cache

  • 在emacs中跳转光标到之前所在位置的几个方法

    2017-08-22

    #随笔#emacs#编辑器#ide

  • 开了一个新的博客,在这里记录一些想法

    2017-08-21

    #随笔#emacs#编辑器#ide#vim

  • 同性交友
留坑待填