使用nginx的lua验证GitHub Webhooks和删除cron-lock-file

我有什么:

  • GNU/Linux主机
  • nginx已运行
  • 定时任务计划在特定文件被删除后立即运行(类似于run-crons)
  • 当有人向存储库推送时,GitHub会发送Webhook

我想要什么:

我现在想要运行Lua或任何类似于解析GitHub请求并验证它,然后删除文件(如果请求是有效的)的内容。

最好所有这些都可以在不需要维护额外的PHP安装(当前没有)或使用fcgiwrap或类似工具的情况下完成。

模板:

在nginx方面,我有等效于

location /deploy {
    # 在这里执行lua(或等效物)
}
点赞
用户3369597
用户3369597

这个解决方案没有针对GitHub的hooks实现验证,假设您已安装了lua扩展和cjson模块:

location = /location {
    default_type 'text/plain';

    content_by_lua_block {
        local cjson = require "cjson.safe"
        ngx.req.read_body()
        local data = ngx.req.get_body_data()

        if
            data
        then
            local obj = cjson.decode(data)

            if
                # 哈希校验应该在这里进行
                (obj and obj.repository and obj.repository.full_name) == "user/reponame"
            then
                local file = io.open("<your file>","w")

                if
                    file
                then
                    file:close()

                    ngx.say("success")
                else
                    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
                end
            else
                ngx.exit(ngx.HTTP_UNAUTHORIZED)
            end
        else
            ngx.exit(ngx.HTTP_NOT_ALLOWED)
        end
    }
}
2016-05-15 02:47:48
用户4716629
用户4716629

要读取 GH Webhook 的 JSON 体,您需要使用 JSON4Lua 库,并使用 luacrypto 验证 HMAC 签名。

预配置

安装所需模块

$ sudo luarocks install JSON4Lua
$ sudo luarocks install luacrypto

在 Nginx 中定义部署位置

  location /deploy {
    client_body_buffer_size 3M;
    client_max_body_size  3M;

    content_by_lua_file /path/to/handler.lua;
  }

max_body_sizebody_buffer_size 应该相等以防止错误

request body in temp file not supported

https://github.com/openresty/lua-nginx-module/issues/521


处理 Webhook

获取请求有效数据,并检查正确性

ngx.req.read_body()
local data = ngx.req.get_body_data()

if not data then
    ngx.log(ngx.ERR, "failed to get request body")
    return ngx.exit (ngx.HTTP_BAD_REQUEST)
end

使用 luacrypto 验证 GH 签名

local function verify_signature (hub_sign, data)
    local sign = 'sha1=' .. crypto.hmac.digest('sha1', data, secret)
    -- this is simple comparison, but it's better to use a constant time comparison
    return hub_sign == sign
end

-- validate GH signature
if not verify_signature(headers['X-Hub-Signature'], data) then
    ngx.log(ngx.ERR, "wrong webhook signature")
    return ngx.exit (ngx.HTTP_FORBIDDEN)
end

解析数据为 JSON 并检查是否为主分支,以进行部署

data = json.decode(data)
-- on master branch
if data['ref'] ~= branch then
    ngx.say("Skip branch ", data['ref'])
    return ngx.exit (ngx.HTTP_OK)
end

如果一切正确,则调用部署功能

local function deploy ()
    -- run command for deploy
    local handle = io.popen("cd /path/to/repo && sudo -u username git pull")
    local result = handle:read("*a")
    handle:close()

    ngx.say (result)
    return ngx.exit (ngx.HTTP_OK)
end

示例

示例常量时间字符串比较

local function const_eq (a, b)
    -- Check is string equals, constant time exec
    getmetatable('').__index = function (str, i)
        return string.sub(str, i, i)
    end

    local diff = string.len(a) == string.len(b)
    for i = 1, math.min(string.len(a), string.len(b)) do
        diff = (a[i] == b[i]) and diff
    end
    return diff
end

我在 GitHub Gist 中使用它的完整示例 https://gist.github.com/Samael500/5dbdf6d55838f841a08eb7847ad1c926

2017-03-31 17:43:21