diff --git a/gateway/src/resty/http/proxy.lua b/gateway/src/resty/http/proxy.lua index 8f644869d..ba2a04900 100644 --- a/gateway/src/resty/http/proxy.lua +++ b/gateway/src/resty/http/proxy.lua @@ -13,108 +13,6 @@ local function default_port(uri) return uri.port or resty_url.default_port(uri.scheme) end -local function connect_direct(httpc, request) - local uri = request.uri - local host = uri.host - local ip, port = httpc:resolve(host, nil, uri) - -- #TODO: This logic may no longer be needed as of PR#1323 and should be reviewed as part of a refactor - local options = { pool = format('%s:%s', host, port) } - local ok, err = httpc:connect(ip, port or default_port(uri), options) - - if not ok then return nil, err end - - ngx.log(ngx.DEBUG, 'connection to ', host, ':', httpc.port, ' established', - ', reused times: ', httpc:get_reused_times()) - - if uri.scheme == 'https' then - ok, err = httpc:ssl_handshake(nil, host, request.ssl_verify) - if not ok then return nil, err end - end - - -- use correct host header - httpc.host = host - - return httpc -end - -local function _connect_tls_direct(httpc, request, host, port) - - local uri = request.uri - - local ok, err = httpc:ssl_handshake(nil, uri.host, request.ssl_verify) - if not ok then return nil, err end - - return httpc -end - -local function _connect_proxy_https(httpc, request, host, port) - -- When the connection is reused the tunnel is already established, so - -- the second CONNECT request would reach the upstream instead of the proxy. - if httpc:get_reused_times() > 0 then - return httpc, 'already connected' - end - - local uri = request.uri - - local res, err = httpc:request({ - method = 'CONNECT', - path = format('%s:%s', host, port or default_port(uri)), - headers = { - ['Host'] = request.headers.host or format('%s:%s', uri.host, default_port(uri)), - ['Proxy-Authorization'] = request.proxy_auth or '' - } - }) - if not res then return nil, err end - - if res.status < 200 or res.status > 299 then - return nil, "failed to establish a tunnel through a proxy: " .. res.status - end - - res, err = httpc:ssl_handshake(nil, uri.host, request.ssl_verify) - if not res then return nil, err end - - return httpc -end - -local function connect_proxy(httpc, request, skip_https_connect) - -- target server requires hostname not IP and DNS resolution is left to the proxy itself as specified in the RFC #7231 - -- https://httpwg.org/specs/rfc7231.html#CONNECT - local uri = request.uri - local proxy_uri = request.proxy - - if proxy_uri.scheme ~= 'http' then - return nil, 'proxy connection supports only http' - else - proxy_uri.port = default_port(proxy_uri) - end - - local port = default_port(uri) - - -- TLS tunnel is verified only once, so we need to reuse connections only for the same Host header - local options = { pool = format('%s:%s:%s:%s', proxy_uri.host, proxy_uri.port, uri.host, port) } - local ok, err = httpc:connect(proxy_uri.host, proxy_uri.port, options) - if not ok then return nil, err end - - ngx.log(ngx.DEBUG, 'connection to ', proxy_uri.host, ':', proxy_uri.port, ' established', - ', pool: ', options.pool, ' reused times: ', httpc:get_reused_times()) - - ngx.log(ngx.DEBUG, 'targeting server ', uri.host, ':', uri.port) - - if uri.scheme == 'http' then - -- http proxy needs absolute URL as the request path - request.path = format('%s://%s:%s%s', uri.scheme, uri.host, uri.port, uri.path or '/') - return httpc - elseif uri.scheme == 'https' and skip_https_connect then - request.path = format('%s://%s:%s%s', uri.scheme, uri.host, uri.port, request.path or '/') - return _connect_tls_direct(httpc, request, uri.host, uri.port) - elseif uri.scheme == 'https' then - return _connect_proxy_https(httpc, request, uri.host, uri.port) - - else - return nil, 'invalid scheme' - end -end - local function parse_request_uri(request) local uri = request.uri or resty_url.parse(request.url) request.uri = uri @@ -131,17 +29,81 @@ local function find_proxy_url(request) end local function connect(request, skip_https_connect) - local httpc = http.new() + local httpc = http.new() local proxy_uri = find_proxy_url(request) + local uri = request.uri + local host = uri.host + local port = default_port(uri) + -- set ssl_verify: lua-resty-http set ssl_verify to true by default if scheme is https, whereas + -- openresty treat nil as false, so we need to explicitly set ssl_verify to false if nil + local ssl_verify = request.options and request.options.ssl and request.options.ssl.verify or false - request.ssl_verify = request.options and request.options.ssl and request.options.ssl.verify + -- request.ssl_verify = request.options and request.options.ssl and request.options.ssl.verify request.proxy = proxy_uri + local options = { + scheme = uri.scheme, + host = host, + port = uri.port, + ssl_server_name=host, + ssl_verify = ssl_verify, + } + if proxy_uri then - return connect_proxy(httpc, request, skip_https_connect) + -- target server requires hostname not IP and DNS resolution is left to the proxy itself as specified in the RFC #7231 + -- https://httpwg.org/specs/rfc7231.html#CONNECT + + if proxy_uri.scheme ~= 'http' then + return nil, 'proxy connection supports only http' + else + proxy_uri.port = default_port(proxy_uri) + end + + if uri.scheme == 'http' then + request.path = uri.path or '/' + options.proxy_opts = { + http_proxy = format("%s://%s:%s", proxy_uri.scheme, proxy_uri.host, proxy_uri.port), + http_proxy_authorization = request.proxy_auth + } + elseif uri.scheme == 'https' and skip_https_connect then + request.path = request.path or '/' + + -- The new lua-resty-http connect method does not allow skipping CONNECT when proxying https request + -- so keep the old code here + -- + -- TLS tunnel is verified only once, so we need to reuse connections only for the same Host header + local options = { pool = format('%s:%s:%s:%s', proxy_uri.host, proxy_uri.port, uri.host, port) } + local ok, err = httpc:connect(proxy_uri.host, proxy_uri.port, options) + if not ok then return nil, err end + + ngx.log(ngx.DEBUG, 'connection to ', proxy_uri.host, ':', proxy_uri.port, ' established', + ', pool: ', options.pool, ' reused times: ', httpc:get_reused_times()) + + ngx.log(ngx.DEBUG, 'targeting server ', host, ':', port) + + local ok, err = httpc:ssl_handshake(nil, host, ssl_verify) + if not ok then return nil, err end + + return httpc + elseif uri.scheme == 'https' then + options.proxy_opts = { + https_proxy = format("%s://%s:%s", proxy_uri.scheme, proxy_uri.host, proxy_uri.port), + https_proxy_authorization = request.proxy_auth + } + else + return nil, 'invalid scheme' + end else - return connect_direct(httpc, request) + local ip, resolved_port = httpc:resolve(host, nil, uri) + -- #TODO: This logic may no longer be needed as of PR#1323 and should be reviewed as part of a refactor + options.host = ip + options.port = resolved_port end + + local ok, err = httpc:connect(options) + if not ok then return nil, err end + + return httpc end function _M.env() diff --git a/gateway/src/resty/http_ng/backend/async_resty.lua b/gateway/src/resty/http_ng/backend/async_resty.lua index 36e9bd35a..c5147b928 100644 --- a/gateway/src/resty/http_ng/backend/async_resty.lua +++ b/gateway/src/resty/http_ng/backend/async_resty.lua @@ -45,22 +45,21 @@ _M.async = function(request) end end - local ok, err = httpc:connect(host, port) + local verify = request.options and request.options.ssl and request.options.ssl.verify + if type(verify) == 'nil' then verify = true end - if not ok then - return response.error(request, err) - end - - if scheme == 'https' then - local verify = request.options and request.options.ssl and request.options.ssl.verify - if type(verify) == 'nil' then verify = true end + local options = { + scheme = scheme, + host = host, + port = port, + ssl_server_name=host, + ssl_verify = verify + } - local session - session, err = httpc:ssl_handshake(false, host, verify) + local ok, err = httpc:connect(options) - if not session then - return response.error(request, err) - end + if not ok then + return response.error(request, err) end local res diff --git a/gateway/src/resty/resolver/http.lua b/gateway/src/resty/resolver/http.lua index 4af497855..d68efde51 100644 --- a/gateway/src/resty/resolver/http.lua +++ b/gateway/src/resty/resolver/http.lua @@ -38,7 +38,7 @@ function _M:resolve(host, port, options) return ip, port end -function _M.connect(self, host, port, ...) +local function v0_15_connect(self, host, port, ...) local ip, real_port = self:resolve(host, port) local ok, err = resty_http.connect(self, ip, real_port, ...) @@ -52,4 +52,14 @@ function _M.connect(self, host, port, ...) return ok, err end +function _M.connect(self, options, ...) + if type(options) == "table" then + return resty_http.connect(self, options) + else + -- backward compatible + return v0_15_connect(self, options, ...) + end +end + + return _M