如何配置Nginx代理Minio流量并代为处理Minio认证以避免泄露密钥
这种场景我在团队协作中太熟悉了——存储团队要把Minio的密钥牢牢攥在手里,不让开发侧接触到,同时还要让各个应用能正常访问自己的对象资源,Nginx确实是实现这个需求的完美中间层。不过Minio用的是AWS SigV4签名认证,不像Basic Auth那样能直接静态设置Authorization头,得用动态生成签名的方式来处理,下面给你一步步讲具体的实现方法:
核心原理说明
Minio的认证依赖AWS Signature Version 4(SigV4),这个签名是根据请求的HTTP方法、路径、时间戳、请求头等动态计算出来的,没法像Basic Auth那样提前生成一个固定的Header值。所以我们需要在Nginx里用Lua脚本实时生成这个签名,然后添加到请求头里转发给Minio。
前置准备
推荐使用OpenResty(它是Nginx的增强版,集成了LuaJIT和常用的Lua库,比如resty.hmac),这样不需要额外安装依赖。如果用标准Nginx,需要确保安装了ngx_http_lua_module,并手动安装lua-resty-hmac库。
第一步:编写SigV4签名生成脚本
创建一个Lua脚本(比如放在/etc/nginx/minio_sign.lua),脚本里嵌入Minio的Access Key和Secret Key,然后实现SigV4签名的生成逻辑。下面是一个可用的示例脚本:
local access_key = "YOUR_MINIO_ACCESS_KEY" local secret_key = "YOUR_MINIO_SECRET_KEY" local region = "us-east-1" -- 换成你的Minio集群区域,默认是us-east-1 local service = "s3" -- 获取请求时间戳(UTC,格式为YYYYMMDD'T'HHMMSS'Z') local function get_timestamp() return os.date("!%Y%m%dT%H%M%SZ") end -- 获取日期(用于签名的日期部分,格式为YYYYMMDD) local function get_date(timestamp) return string.sub(timestamp, 1, 8) end -- HMAC-SHA256加密函数 local function hmac_sha256(key, data) local hmac = require "resty.hmac" local h, err = hmac:new(key, hmac.ALGOS.SHA256) if not h then ngx.log(ngx.ERR, "Failed to create HMAC: ", err) return nil end local ok = h:update(data) if not ok then ngx.log(ngx.ERR, "Failed to update HMAC") return nil end local digest = h:final() return digest end -- 生成签名密钥 local function get_signing_key(secret_key, date, region, service) local k_date = hmac_sha256("AWS4" .. secret_key, date) local k_region = hmac_sha256(k_date, region) local k_service = hmac_sha256(k_region, service) local k_signing = hmac_sha256(k_service, "aws4_request") return k_signing end -- 生成规范请求字符串 local function get_canonical_request(method, uri, query_string, headers, signed_headers, payload_hash) local canonical_headers = "" for _, key in ipairs(signed_headers) do canonical_headers = canonical_headers .. string.lower(key) .. ":" .. headers[key] .. "\n" end return method .. "\n" .. uri .. "\n" .. query_string .. "\n" .. canonical_headers .. "\n" .. table.concat(signed_headers, ";") .. "\n" .. payload_hash end -- 生成待签名字符串 local function get_string_to_sign(timestamp, date, region, service, canonical_request_hash) return "AWS4-HMAC-SHA256\n" .. timestamp .. "\n" .. date .. "/" .. region .. "/" .. service .. "/aws4_request\n" .. canonical_request_hash end -- 主逻辑 local method = ngx.req.get_method() local uri = ngx.var.uri local query_string = ngx.var.args or "" -- 获取或设置X-Amz-Date头 local timestamp = get_timestamp() ngx.req.set_header("X-Amz-Date", timestamp) local date = get_date(timestamp) -- 获取请求头,这里只保留必要的头,你可以根据需求调整 local headers = { ["host"] = ngx.var.host, ["x-amz-date"] = timestamp } -- 如果是PUT/POST请求,需要计算payload的哈希,这里简化处理用UNSIGNED-PAYLOAD(Minio支持) local payload_hash = "UNSIGNED-PAYLOAD" -- 签名的头列表 local signed_headers = {"host", "x-amz-date"} -- 生成规范请求和哈希 local canonical_request = get_canonical_request(method, uri, query_string, headers, signed_headers, payload_hash) local canonical_request_hash = ngx.encode_base64(ngx.sha256_bin(canonical_request)) -- 生成待签名字符串 local string_to_sign = get_string_to_sign(timestamp, date, region, service, canonical_request_hash) -- 生成签名密钥和最终签名 local signing_key = get_signing_key(secret_key, date, region, service) local signature = ngx.encode_base64(hmac_sha256(signing_key, string_to_sign)) -- 设置Authorization头 local auth_header = string.format( "AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=%s, Signature=%s", access_key, date, region, service, table.concat(signed_headers, ";"), signature ) ngx.req.set_header("Authorization", auth_header)
注意:把脚本里的YOUR_MINIO_ACCESS_KEY和YOUR_MINIO_SECRET_KEY替换成实际的Minio密钥,region换成你的Minio集群配置的区域(默认是us-east-1)。
第二步:配置Nginx反向代理
在Nginx的配置文件中添加一个server块,用来代理Minio的流量,同时调用上面的Lua脚本生成签名。示例配置如下:
server { listen 80; server_name minio-proxy.example.com; # 换成你的代理域名 # 限制应用只能访问指定的bucket/path,比如只允许访问app1的bucket location /app1-bucket/ { # 调用Lua脚本生成签名 access_by_lua_file /etc/nginx/minio_sign.lua; # 转发请求到Minio集群 proxy_pass http://your-minio-cluster:9000; # 换成你的Minio集群地址 # 传递必要的请求头 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 禁用缓存,避免签名失效 proxy_buffering off; proxy_cache off; } # 可以添加多个location块,对应不同的应用bucket location /app2-bucket/ { access_by_lua_file /etc/nginx/minio_sign.lua; proxy_pass http://your-minio-cluster:9000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_buffering off; proxy_cache off; } }
关键注意事项
- 时间同步:Nginx服务器和Minio集群的系统时间必须保持同步(误差不能超过15分钟),否则SigV4签名会被Minio拒绝。可以用NTP服务来同步时间。
- 权限安全:确保Nginx的配置文件和Lua脚本的权限设置为
600,只有存储团队的用户能读取,避免密钥泄露。 - 请求方法覆盖:如果你的应用需要使用PUT/DELETE等修改类请求,要确保Lua脚本里的逻辑能正确处理这些方法(上面的脚本已经支持所有HTTP方法)。
- 密钥轮换:定期轮换Minio的密钥时,记得同步更新Lua脚本里的密钥,然后重启Nginx生效。
验证配置
用curl测试一下代理是否正常工作:
curl http://minio-proxy.example.com/app1-bucket/path/to/your/object
如果能正常返回对象内容,说明配置生效了。同时可以查看Minio的访问日志,确认请求是通过SigV4认证成功的。
备注:内容来源于stack exchange,提问作者h.safe

