使用OpenResty无感访问私有读阿里云OSS
本文最后更新于 2024-07-06,文章内容可能已经过时。
问题描述
今天一大早公司的项目群里接到了省里的风险报告需要整改。其中有一项是:
风险名称 | OSS读写权限风险 |
---|---|
风险描述 | 所有人都可以对Object进行匿名读写、读操作,可能导致bucket存储空间中被写入"恶意"文件,或者敏感文件泄露。 |
修复建议 | 1.关闭公共读写、读权限,修改为私有权限; 2.采用带签名的方式进行oss bucket中文件对象的读取; |
原来是我们有一个项目的OSS是公共读,项目前端直接使用OSS地址所导致的。这里比较麻烦,因为如果设置成了私有读的,资源连接将会变成:https://xxx.aliyuncs.com/temp.jpg?Expires=xxx&OSSAccessKeyId=xxx&Signature=xxx
,后面的三个参数都为动态获取的,所有的连接都是具有时效性的,属实不好操作。
Nginx有四个主流分支,其中OpenResty分支就是在普通的Nginx上增加了lua脚本的支持,这里我们是否可以通过lua脚本达到无感的操作,所以百度了一下很简单的就找到了对应的方法。
准备工作
- 使用OpenResty替换为原来的Nginx
- 准备好OSS的基本信息
具体操作
安装OpenResty
具体的安装资料可以在OpenResty官方获取到,我这里是CentOS7,这里直接使用官方提供的预编译包了。
# 对于 CentOS 8 或以上版本,应将下面的 yum 都替换成 dnf
# 添加yum源
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/
# 更新
sudo yum check-update
# 安装OpenResty
yum install -y openresty
安装后工作目录在:/usr/local/openresty/nginx
添加lua脚本
这里我直接在OpenResty工作目录下面增加了lua文件夹。
cd /usr/local/openresty/nginx
mkdir lua
cd lua
touch oss_auth.lua
将一些脚本内容拷贝进去
-- has been sorted in alphabetical order
local signed_subresources = {
'acl',
'append',
'bucketInfo',
'cname',
'commitTransition',
'comp',
'cors',
'delete',
'lifecycle',
'location',
'logging',
'mime',
'notification',
'objectInfo',
'objectMeta',
'partData',
'partInfo',
'partNumber',
'policy',
'position',
'referer',
'replication',
'replicationLocation',
'replicationProgress',
'requestPayment',
'response-cache-control',
'response-content-disposition',
'response-content-encoding',
'response-content-language',
'response-content-type',
'response-expires',
'restore',
'security-token',
'tagging',
'torrent',
'uploadId',
'uploads',
'versionId',
'versioning',
'versions',
'website'
}
function string.startswith(s, start)
return string.sub(s, 1, string.len(start)) == start
end
local function get_canon_sub_resource()
local args = ngx.req.get_uri_args()
-- lower keys
local keys = {}
for k, v in pairs(args) do
keys[k:lower()] = v
end
-- make resource string
local s = ''
local sep = '?'
for i, k in ipairs(signed_subresources) do
v = keys[k]
if v then
-- sub table
v = type(v) == 'table' and v[1] or v
s = s .. string.format("%s%s=%s", sep, k, v)
sep = '&'
end
end
return s
end
local function get_canon_resource()
resource = ''
object = ngx.unescape_uri(ngx.var.uri)
sub = get_canon_sub_resource()
return string.format("/%s%s%s", ngx.var.oss_bucket, object, sub)
end
local function get_canon_headers()
-- default: <lowerkey, value>
local headers = ngx.req.get_headers()
local keys = {}
for k, v in pairs(headers) do
if string.startswith(k, 'x-oss-') then
-- client must assemble the same header keys
if type(v) ~= 'string' then return nil end
table.insert(keys, k)
end
end
-- sorted in alphabetical order
table.sort(keys)
for i, key in ipairs(keys) do
keys[i] = key .. ':' .. headers[key] .. '\n'
end
return table.concat(keys)
end
local function calc_sign(key, method, md5, type_, date, oss_headers, resource)
-- string_to_sign:
-- method + '\n' + content_md5 + '\n' + content_type + '\n'
-- + date + '\n' + canonicalized_oss_headers + canonicalized_resource
local sign_str = string.format('%s\n%s\n%s\n%s\n%s%s',
method, md5, type_,
date, oss_headers, resource)
ngx.log(ngx.ERR, "SignStr:", sign_str, "\n")
local sign_result = ngx.encode_base64(ngx.hmac_sha1(key, sign_str))
return sign_result, sign_str
end
local function oss_auth()
-- ngx.log(ngx.INFO, 'auth')
--local method = ngx.var.request_method
local method = ngx.req.get_method()
local content_md5 = ngx.var.http_content_md5 or ''
local content_type = ngx.var.http_content_type or ''
-- get date
local date = ngx.var.http_x_oss_date or ngx.var.http_date or ''
if date == '' then
date = ngx.http_time(ngx.time())
-- ngx.log(ngx.INFO, 'Date:', date)
ngx.req.set_header('Date', date)
end
local resource = get_canon_resource()
local canon_headers = get_canon_headers()
local sign_result, sign_str = calc_sign(ngx.var.oss_auth_key, method, content_md5,
content_type, date, canon_headers, resource)
-- ngx.log(ngx.INFO, 'sign string:', sign_str)
-- ngx.log(ngx.INFO, 'sign string len:', string.len(sign_str))
local auth = string.format("OSS %s:%s", ngx.var.oss_auth_id, sign_result)
ngx.req.set_header('Authorization', auth)
ngx.exec("@oss")
end
-- main
res = oss_auth()
if res then
ngx.exit(res)
end
脚本中ngx.exec("@oss")
的@oss
就是到时候OpenResty配置文件中的标识,可以不用替换。
更改OpenResty配置文件
默认的主配置文件在:/usr/local/openresty/nginx/conf/nginx.conf
,可以重新include
之前的配置文件。下面贴一个示例的配置文件:
server {
listen 80;
server_name localhost;
charset utf-8;
client_max_body_size 100m;
#access_log logs/host.access.log main;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
#gzip_http_version 1.0;
gzip_comp_level 8;
gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
location / {
# 下面四个参数改成你自己的
set $oss_bucket "预设OSS路径";
set $oss_auth_id "AccessKeyId";
set $oss_auth_key "AccessKeySecret";
rewrite_by_lua_file "/usr/local/openresty/nginx/lua/oss_auth.lua";
}
# 脚本中ngx.exec("@oss") 双引号中的标识
location @oss {
# OSS的域名
proxy_pass http://xxx.aliyuncs.com;
# #表示最大上传10M,需要多大设置多大。
client_max_body_size 10m;
if ( $request_method = 'OPTIONS' ) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'POST,GET,PUT,OPTIONS';
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Allow-Headers 'Origin,X-Requested-With,Content-Type,Accept,Authorization,sourcetype,token';
add_header Access-Control-Allow-Credentials true;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 200;
}
proxy_redirect off;
# Host要修改为OSS的域名或OSS控制台绑定的域名,否则OSS无法识别会报错
proxy_set_header Host xxx.aliyuncs.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_max_temp_file_size 0;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
}
}
重启OpenResty
systemctl restart openresty
测试
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 舟涯
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果