Skip to content

Commit fb46928

Browse files
committed
Merge branch 'bungle-feat/proxy-authorization'
2 parents e5deba5 + 87c6bbe commit fb46928

File tree

3 files changed

+252
-6
lines changed

3 files changed

+252
-6
lines changed

README.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,15 @@ An optional Lua table can be specified as the last argument to this method to sp
164164

165165
## connect_proxy
166166

167-
`syntax: ok, err = httpc:connect_proxy(proxy_uri, scheme, host, port)`
167+
`syntax: ok, err = httpc:connect_proxy(proxy_uri, scheme, host, port, proxy_authorization)`
168168

169169
Attempts to connect to the web server through the given proxy server. The method accepts the following arguments:
170170

171171
* `proxy_uri` - Full URI of the proxy server to use (e.g. `http://proxy.example.com:3128/`). Note: Only `http` protocol is supported.
172172
* `scheme` - The protocol to use between the proxy server and the remote host (`http` or `https`). If `https` is specified as the scheme, `connect_proxy()` makes a `CONNECT` request to establish a TCP tunnel to the remote host through the proxy server.
173173
* `host` - The hostname of the remote host to connect to.
174174
* `port` - The port of the remote host to connect to.
175+
* `proxy_authorization` - The `Proxy-Authorization` header value sent to the proxy server via `CONNECT` when the `scheme` is `https`.
175176

176177
If an error occurs during the connection attempt, this method returns `nil` with a string describing the error. If the connection was successfully established, the method returns `1`.
177178

@@ -221,7 +222,9 @@ In case of success, returns `1`. In case of errors, returns `nil, err`. In the c
221222
Configure an http proxy to be used with this client instance. The `opts` is a table that accepts the following fields:
222223

223224
* `http_proxy` - an URI to a proxy server to be used with http requests
225+
* `http_proxy_authorization` - a default `Proxy-Authorization` header value to be used with `http_proxy`, e.g. `Basic ZGVtbzp0ZXN0`, which will be overriden if the `Proxy-Authorization` request header is present.
224226
* `https_proxy` - an URI to a proxy server to be used with https requests
227+
* `https_proxy_authorization` - as `http_proxy_authorization` but for use with `https_proxy`.
225228
* `no_proxy` - a comma separated list of hosts that should not be proxied.
226229

227230
Note that proxy options are only applied when using the high-level `request_uri()` API.

lib/resty/http.lua

+23-5
Original file line numberDiff line numberDiff line change
@@ -829,7 +829,16 @@ function _M.request_uri(self, uri, params)
829829
local c, err
830830

831831
if proxy_uri then
832-
c, err = self:connect_proxy(proxy_uri, scheme, host, port)
832+
local proxy_authorization
833+
if scheme == "https" then
834+
if params.headers and params.headers["Proxy-Authorization"] then
835+
proxy_authorization = params.headers["Proxy-Authorization"]
836+
else
837+
proxy_authorization = self.proxy_opts.https_proxy_authorization
838+
end
839+
end
840+
841+
c, err = self:connect_proxy(proxy_uri, scheme, host, port, proxy_authorization)
833842
else
834843
c, err = self:connect(host, port)
835844
end
@@ -854,9 +863,17 @@ function _M.request_uri(self, uri, params)
854863
else
855864
params.path = scheme .. "://" .. host .. ":" .. port .. path
856865
end
857-
end
858866

859-
if scheme == "https" then
867+
if self.proxy_opts.http_proxy_authorization then
868+
if not params.headers then
869+
params.headers = {}
870+
end
871+
872+
if not params.headers["Proxy-Authorization"] then
873+
params.headers["Proxy-Authorization"] = self.proxy_opts.http_proxy_authorization
874+
end
875+
end
876+
elseif scheme == "https" then
860877
-- don't keep this connection alive as the next request could target
861878
-- any host and re-using the proxy tunnel for that is not possible
862879
self.keepalive = false
@@ -1051,7 +1068,7 @@ function _M.get_proxy_uri(self, scheme, host)
10511068
end
10521069

10531070

1054-
function _M.connect_proxy(self, proxy_uri, scheme, host, port)
1071+
function _M.connect_proxy(self, proxy_uri, scheme, host, port, proxy_authorization)
10551072
-- Parse the provided proxy URI
10561073
local parsed_proxy_uri, err = self:parse_uri(proxy_uri, false)
10571074
if not parsed_proxy_uri then
@@ -1082,7 +1099,8 @@ function _M.connect_proxy(self, proxy_uri, scheme, host, port)
10821099
method = "CONNECT",
10831100
path = destination,
10841101
headers = {
1085-
["Host"] = destination
1102+
["Host"] = destination,
1103+
["Proxy-Authorization"] = proxy_authorization,
10861104
}
10871105
})
10881106

t/16-http-proxy.t

+225
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ no_long_string();
2626
run_tests();
2727

2828
__DATA__
29+
2930
=== TEST 1: get_proxy_uri returns nil if proxy is not configured
3031
--- http_config eval: $::HttpConfig
3132
--- config
@@ -44,6 +45,8 @@ nil
4445
[error]
4546
[warn]
4647

48+
49+
4750
=== TEST 2: get_proxy_uri matches no_proxy hosts correctly
4851
--- http_config eval: $::HttpConfig
4952
--- config
@@ -111,6 +114,8 @@ scheme: http, host: notexample.com, no_proxy: example.com, proxy_uri: http://htt
111114
[error]
112115
[warn]
113116

117+
118+
114119
=== TEST 3: get_proxy_uri returns correct proxy URIs for http and https URIs
115120
--- http_config eval: $::HttpConfig
116121
--- config
@@ -157,6 +162,8 @@ scheme: https, host: example.com, http_proxy: http_proxy, https_proxy: nil, prox
157162
[error]
158163
[warn]
159164

165+
166+
160167
=== TEST 4: request_uri uses http_proxy correctly for non-standard destination ports
161168
--- http_config
162169
lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;";
@@ -200,6 +207,8 @@ GET /lua
200207
[error]
201208
[warn]
202209

210+
211+
203212
=== TEST 5: request_uri uses http_proxy correctly for standard destination port
204213
--- http_config
205214
lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;";
@@ -246,6 +255,8 @@ GET /lua
246255
[error]
247256
[warn]
248257

258+
259+
249260
=== TEST 6: request_uri makes a proper CONNECT request when proxying https resources
250261
--- http_config eval: $::HttpConfig
251262
--- config
@@ -295,3 +306,217 @@ GET /lua
295306
--- no_error_log
296307
[error]
297308
[warn]
309+
310+
311+
312+
=== TEST 7: request_uri uses http_proxy_authorization option
313+
--- http_config
314+
lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;";
315+
error_log logs/error.log debug;
316+
resolver 8.8.8.8;
317+
server {
318+
listen *:8080;
319+
320+
location / {
321+
content_by_lua_block {
322+
ngx.print(ngx.var.http_proxy_authorization or "no-header")
323+
}
324+
}
325+
}
326+
--- config
327+
location /lua {
328+
content_by_lua_block {
329+
local http = require "resty.http"
330+
local httpc = http.new()
331+
httpc:set_proxy_options({
332+
http_proxy = "http://127.0.0.1:8080",
333+
http_proxy_authorization = "Basic ZGVtbzp0ZXN0",
334+
https_proxy = "http://127.0.0.1:8080",
335+
https_proxy_authorization = "Basic ZGVtbzpwYXNz"
336+
})
337+
338+
-- request should go to the proxy server
339+
local res, err = httpc:request_uri("http://127.0.0.1/")
340+
if not res then
341+
ngx.log(ngx.ERR, err)
342+
return
343+
end
344+
345+
-- the proxy echoed the proxy authorization header
346+
-- to the test harness
347+
ngx.status = res.status
348+
ngx.say(res.body)
349+
}
350+
}
351+
--- request
352+
GET /lua
353+
--- response_body
354+
Basic ZGVtbzp0ZXN0
355+
--- no_error_log
356+
[error]
357+
[warn]
358+
359+
360+
361+
=== TEST 8: request_uri uses https_proxy_authorization option
362+
--- http_config eval: $::HttpConfig
363+
--- config
364+
location /lua {
365+
content_by_lua_block {
366+
local http = require "resty.http"
367+
local httpc = http.new()
368+
httpc:set_proxy_options({
369+
http_proxy = "http://127.0.0.1:12345",
370+
http_proxy_authorization = "Basic ZGVtbzp0ZXN0",
371+
https_proxy = "http://127.0.0.1:12345",
372+
https_proxy_authorization = "Basic ZGVtbzpwYXNz"
373+
})
374+
375+
-- Slight Hack: temporarily change the module global user agent to make it
376+
-- predictable for this test case
377+
local ua = http._USER_AGENT
378+
http._USER_AGENT = "test_ua"
379+
local res, err = httpc:request_uri("https://127.0.0.1/target?a=1&b=2")
380+
http._USER_AGENT = ua
381+
382+
if not err then
383+
-- The proxy request should fail as the TCP server listening returns
384+
-- 403 response. We cannot really test the success case here as that
385+
-- would require an actual reverse proxy to be implemented through
386+
-- the limited functionality we have available in the raw TCP sockets
387+
ngx.log(ngx.ERR, "unexpected success")
388+
return
389+
end
390+
391+
ngx.status = 403
392+
ngx.say(err)
393+
}
394+
}
395+
--- tcp_listen: 12345
396+
--- tcp_query eval
397+
qr/CONNECT 127.0.0.1:443 HTTP\/1.1\r\n.*Proxy-Authorization: Basic ZGVtbzpwYXNz\r\n.*Host: 127.0.0.1:443\r\n.*/s
398+
399+
# The reply cannot be successful or otherwise the client would start
400+
# to do a TLS handshake with the proxied host and that we cannot
401+
# do with these sockets
402+
--- tcp_reply
403+
HTTP/1.1 403 Forbidden
404+
Connection: close
405+
406+
--- request
407+
GET /lua
408+
--- error_code: 403
409+
--- no_error_log
410+
[error]
411+
[warn]
412+
413+
414+
415+
=== TEST 9: request_uri does not use http_proxy_authorization option when overridden
416+
--- http_config
417+
lua_package_path "$TEST_NGINX_PWD/lib/?.lua;;";
418+
error_log logs/error.log debug;
419+
resolver 8.8.8.8;
420+
server {
421+
listen *:8080;
422+
423+
location / {
424+
content_by_lua_block {
425+
ngx.print(ngx.var.http_proxy_authorization or "no-header")
426+
}
427+
}
428+
}
429+
--- config
430+
location /lua {
431+
content_by_lua_block {
432+
local http = require "resty.http"
433+
local httpc = http.new()
434+
httpc:set_proxy_options({
435+
http_proxy = "http://127.0.0.1:8080",
436+
http_proxy_authorization = "Basic ZGVtbzp0ZXN0",
437+
https_proxy = "http://127.0.0.1:8080",
438+
https_proxy_authorization = "Basic ZGVtbzpwYXNz"
439+
})
440+
441+
-- request should go to the proxy server
442+
local res, err = httpc:request_uri("http://127.0.0.1/", {
443+
headers = {
444+
["Proxy-Authorization"] = "Basic ZGVtbzp3b3Jk"
445+
}
446+
})
447+
if not res then
448+
ngx.log(ngx.ERR, err)
449+
return
450+
end
451+
452+
-- the proxy echoed the proxy authorization header
453+
-- to the test harness
454+
ngx.status = res.status
455+
ngx.say(res.body)
456+
}
457+
}
458+
--- request
459+
GET /lua
460+
--- response_body
461+
Basic ZGVtbzp3b3Jk
462+
--- no_error_log
463+
[error]
464+
[warn]
465+
466+
467+
468+
=== TEST 10: request_uri does not use https_proxy_authorization option when overridden
469+
--- http_config eval: $::HttpConfig
470+
--- config
471+
location /lua {
472+
content_by_lua_block {
473+
local http = require "resty.http"
474+
local httpc = http.new()
475+
httpc:set_proxy_options({
476+
http_proxy = "http://127.0.0.1:12345",
477+
http_proxy_authorization = "Basic ZGVtbzp0ZXN0",
478+
https_proxy = "http://127.0.0.1:12345",
479+
https_proxy_authorization = "Basic ZGVtbzpwYXNz"
480+
})
481+
482+
-- Slight Hack: temporarily change the module global user agent to make it
483+
-- predictable for this test case
484+
local ua = http._USER_AGENT
485+
http._USER_AGENT = "test_ua"
486+
local res, err = httpc:request_uri("https://127.0.0.1/target?a=1&b=2", {
487+
headers = {
488+
["Proxy-Authorization"] = "Basic ZGVtbzp3b3Jk"
489+
}
490+
})
491+
http._USER_AGENT = ua
492+
493+
if not err then
494+
-- The proxy request should fail as the TCP server listening returns
495+
-- 403 response. We cannot really test the success case here as that
496+
-- would require an actual reverse proxy to be implemented through
497+
-- the limited functionality we have available in the raw TCP sockets
498+
ngx.log(ngx.ERR, "unexpected success")
499+
return
500+
end
501+
502+
ngx.status = 403
503+
ngx.say(err)
504+
}
505+
}
506+
--- tcp_listen: 12345
507+
--- tcp_query eval
508+
qr/CONNECT 127.0.0.1:443 HTTP\/1.1\r\n.*Proxy-Authorization: Basic ZGVtbzp3b3Jk\r\n.*Host: 127.0.0.1:443\r\n.*/s
509+
510+
# The reply cannot be successful or otherwise the client would start
511+
# to do a TLS handshake with the proxied host and that we cannot
512+
# do with these sockets
513+
--- tcp_reply
514+
HTTP/1.1 403 Forbidden
515+
Connection: close
516+
517+
--- request
518+
GET /lua
519+
--- error_code: 403
520+
--- no_error_log
521+
[error]
522+
[warn]

0 commit comments

Comments
 (0)