Skip to content

Instantly share code, notes, and snippets.

@pohmelie
Last active December 19, 2022 08:31
Show Gist options
  • Select an option

  • Save pohmelie/d1f3ae729472aa652177c2f954bbee08 to your computer and use it in GitHub Desktop.

Select an option

Save pohmelie/d1f3ae729472aa652177c2f954bbee08 to your computer and use it in GitHub Desktop.

Revisions

  1. pohmelie revised this gist Dec 5, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion nginx-openresty reverse proxy ntlm support.md
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@ Put `ntlm.lua` to `lualib` path of openresty.
    ### Linux
    You need to install [lua-http-parser](https://github.com/brimworks/lua-http-parser) into openresty `lualib` path with `luarocks`.
    ### Windows
    You can use binaries above (probably you need to rename `.dll` to `.so`, since openresty luajit have some problems with import paths) and put them into `lualib`. Binaries tested with `openresty-1.13.6.2-win64`.
    You can use binaries below (probably you need to rename `.dll` to `.so`, since openresty luajit have some problems with import paths) and put them into `lualib`. Binaries tested with `openresty-1.13.6.2-win64`.
    ## Usage
    ```
    location /foobar {
  2. pohmelie renamed this gist Dec 5, 2018. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. pohmelie revised this gist Dec 5, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion nginx-openresty reverse proxy ntlm support.md
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@ Put `ntlm.lua` to `lualib` path of openresty.
    ### Linux
    You need to install [lua-http-parser](https://github.com/brimworks/lua-http-parser) into openresty `lualib` path with `luarocks`.
    ### Windows
    You can use binaries above (probably you need to rename `.dll` to `.so`, since openresty luajit have some problems with import paths) and put them into `lualib`.
    You can use binaries above (probably you need to rename `.dll` to `.so`, since openresty luajit have some problems with import paths) and put them into `lualib`. Binaries tested with `openresty-1.13.6.2-win64`.
    ## Usage
    ```
    location /foobar {
  4. pohmelie revised this gist Dec 5, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion nginx-openresty reverse proxy ntlm support.md
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,7 @@ Put `ntlm.lua` to `lualib` path of openresty.
    ### Linux
    You need to install [lua-http-parser](https://github.com/brimworks/lua-http-parser) into openresty `lualib` path with `luarocks`.
    ### Windows
    You can use binaries below (probably you need to rename `.dll` to `.so`, since openresty luajit have some problems with import paths) and put them into `lualib`.
    You can use binaries above (probably you need to rename `.dll` to `.so`, since openresty luajit have some problems with import paths) and put them into `lualib`.
    ## Usage
    ```
    location /foobar {
  5. Nikita Melentev revised this gist Dec 5, 2018. 1 changed file with 0 additions and 0 deletions.
    Binary file added http.zip
    Binary file not shown.
  6. pohmelie revised this gist Dec 5, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion nginx-openresty reverse proxy ntlm support.md
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@ Implement nginx-like stream proxy, but parse http to understand end of sequence
    ## Installation
    Put `ntlm.lua` to `lualib` path of openresty.
    ### Linux
    You need to install (lua-http-parser)[https://github.com/brimworks/lua-http-parser] into openresty `lualib` path with `luarocks`.
    You need to install [lua-http-parser](https://github.com/brimworks/lua-http-parser) into openresty `lualib` path with `luarocks`.
    ### Windows
    You can use binaries below (probably you need to rename `.dll` to `.so`, since openresty luajit have some problems with import paths) and put them into `lualib`.
    ## Usage
  7. pohmelie renamed this gist Dec 5, 2018. 1 changed file with 0 additions and 0 deletions.
  8. pohmelie created this gist Dec 5, 2018.
    20 changes: 20 additions & 0 deletions description.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,20 @@
    # nginx/openresty reverse proxy ntlm support
    ## Problem
    This code allows you to pass ntlm auth in nginx reverse proxy mode. The problem with plain nginx is that ntlm requires one tcp connection for multiple http requests. Even if browser respect this behaviour, nginx will create/took new connection for each request to ntlm-awared server.
    ## Solution
    Implement nginx-like stream proxy, but parse http to understand end of sequence (first request after ntlm auth). We need end of sequence, since browser can reuse opened tcp connection and send another request, which will be passed to ntlm-aware server and this is not you expect.
    ## Installation
    Put `ntlm.lua` to `lualib` path of openresty.
    ### Linux
    You need to install (lua-http-parser)[https://github.com/brimworks/lua-http-parser] into openresty `lualib` path with `luarocks`.
    ### Windows
    You can use binaries below (probably you need to rename `.dll` to `.so`, since openresty luajit have some problems with import paths) and put them into `lualib`.
    ## Usage
    ```
    location /foobar {
    access_by_lua_block {require("ntlm").passthrough("1.2.3.4", 5678, 10)}
    }
    ```
    ## Limitations
    1. This code do not change http headers and body, so if your openresty location do not mimic ntlm-aware server, then this will not work. But, since there is strong parser this can be imporved, but requires much work.
    2. This code uses timeouts for socket read operations, so any request can't be shorter in time, than this timeout. This is solvable part, but requires more digging into http and will increase code complexity.
    137 changes: 137 additions & 0 deletions ntlm.lua
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,137 @@
    local ngx = require("ngx")
    local lhp = require("http.parser")
    local M = {}


    local function log(level, t, ...)
    local m = string.format(t, ...)
    ngx.log(level, m)
    return m
    end


    M.RequestWatcher = {}
    function M.RequestWatcher.new(self, url)
    local o = {
    url = url,
    end_of_sequence = false,
    }
    setmetatable(o, self)
    self.__index = self
    o.parser = lhp.request({
    on_url=function(...) o:on_url(...) end,
    })
    return o
    end
    function M.RequestWatcher.on_url(self, url)
    log(ngx.DEBUG, "request watcher: check url")
    if self.url ~= url then
    log(ngx.DEBUG, "request watcher: url mismatch, expect '%s', but got '%s'", self.url, url)
    self.end_of_sequence = true
    end
    end
    function M.RequestWatcher.execute(self, bytes)
    return self.parser:execute(bytes)
    end


    M.ResponseWatcher = {}
    function M.ResponseWatcher.new(self)
    local o = {
    non_auth_code = false,
    end_of_sequence = false,
    }
    setmetatable(o, self)
    self.__index = self
    o.parser = lhp.response({
    on_status=function(...) o:on_status(...) end,
    on_message_complete=function(...) o:on_message_complete(...) end,
    })
    return o
    end
    function M.ResponseWatcher.on_message_complete(self)
    log(ngx.DEBUG, "response watcher: message complete")
    if self.non_auth_code then
    self.end_of_sequence = true
    end
    end
    function M.ResponseWatcher.on_status(self, code, text)
    log(ngx.DEBUG, "response watcher: %s %s", code, text)
    if code ~= 401 then
    self.non_auth_code = true
    end
    end
    function M.ResponseWatcher.execute(self, bytes)
    return self.parser:execute(bytes)
    end


    local function transfer(source, destination, prefix, watcher)
    while true do
    local data, receive_err, partial = source:receive(8192)
    if receive_err and receive_err ~= "timeout" then
    log(ngx.ERR, "transfer[%s]: read error %s", prefix, receive_err)
    end
    for i, d in pairs({data, partial}) do
    if #d ~= 0 then
    log(ngx.DEBUG, "transfer[%s]: sending %d bytes", prefix, #d)
    local bytes, send_err = destination:send(d)
    if send_err then
    log(ngx.ERR, "transfer[%s]: write error %s", prefix, send_err)
    return receive_err, send_err
    else
    log(ngx.DEBUG, "transfer[%s]: %d bytes sent", prefix, bytes)
    end
    if watcher then
    watcher:execute(d)
    if watcher.end_of_sequence then
    return
    end
    end
    end
    end
    if receive_err and receive_err ~= "timeout" then
    return receive_err, send_err
    end
    end
    end


    function M.passthrough(host, port, timeout, location_prefix_to_strip)
    log(ngx.INFO, "passthrough[%s:%s]: started", host, port)
    local url = ngx.var.uri
    local req_sock, err = ngx.req.socket(true)
    if err then
    return log(ngx.ERR, "get req_sock error %s", err)
    end
    if timeout then
    req_sock:settimeouts(10000, 10000, timeout)
    end
    local resp_sock = ngx.socket.tcp()
    local ok, err = resp_sock:connect(host, port)
    if not ok then
    return log(ngx.ERR, "connect to %s:%s failed cause %s", host, port, err)
    end
    if timeout then
    resp_sock:settimeouts(10000, 10000, timeout)
    end
    local headers = ngx.req.raw_header()
    if location_prefix_to_strip then
    headers = string.gsub(ngx.req.raw_header(), location_prefix_to_strip, "", 1)
    end
    local bytes, err = resp_sock:send(headers)
    if err then
    return log(ngx.ERR, "send to %s:%s failed cause %s", host, port, err)
    end
    local request_watcher = M.RequestWatcher:new(url)
    request_watcher:execute(headers)
    local request_coroutine = ngx.thread.spawn(transfer, req_sock, resp_sock, "request", request_watcher)
    local response_coroutine = ngx.thread.spawn(transfer, resp_sock, req_sock, "response", M.ResponseWatcher:new())
    ngx.thread.wait(request_coroutine, response_coroutine)
    ngx.thread.kill(request_coroutine)
    ngx.thread.kill(response_coroutine)
    resp_sock:close()
    log(ngx.INFO, "passthrough[%s:%s]: done", host, port)
    end

    return M