HTTP: support http forward proxying
吕海涛
ihaitao at qq.com
Tue Jul 14 03:41:03 UTC 2020
Hello, nginx,
This is my first try for adding the http forward proxying feature to nginx.
The http forward proxy protocol runs on plain http connection, which has big privacy issue.
So web browsers like chrome and firefox has added support for forward proxy over https.
We use nginx as https server, and we also need the forward proxy feature over https on the same host.
Nginx does not support forward proxying now. If we launch another security http proxy server, we
have no choice but stop the nginx server, because both the proxy server and nginx will listen on the
same 443 port.
So, the http forward proxying is need for nginx for us.
After investigate the nginx source, I found that almost all features needed for http forward proxying
are there. If we implement this feature as a dedicate module, we have to duplicate too many code
from the http_proxy_module and http_upstream_module. So I propose to make small modification to the
exists code base.
I use this implementation for weeks and it works fine.
If you want to test this feature, you need add the following conf to your *default* server.
server {
listen 443 ssl;
# ssl conf
location / {
resolver 8.8.8.8;
if ($host != $server_name) {
auth_basic "forward proxy auth";
auth_basic_user_file /path/to/htpasswd;
proxy_pass forward;
}
try_files $uri $uri/ =404;
}
}
The current implementation does not support proxying over http2. As we cannot set the Host header to
the virtual server name, it is impossible to support forward proxying on non-default server, as well.
Here is the patch. Please make your comment. Thank you.
commit 89877712df3e769f4d0acb8fdcefcd351b31eaad
Author: 吕海涛 <ihaitao at qq.com>
Date: Sat Jun 6 12:16:42 2020 +0800
support http forward proxy
diff --git a/auto/configure b/auto/configure
index 7e6e33a7..1ee0bfaa 100755
--- a/auto/configure
+++ b/auto/configure
@@ -92,6 +92,9 @@ have=NGX_HTTP_CLIENT_TEMP_PATH value="\"$NGX_HTTP_CLIENT_TEMP_PATH\""
. auto/define
have=NGX_HTTP_PROXY_TEMP_PATH value="\"$NGX_HTTP_PROXY_TEMP_PATH\""
. auto/define
+if [ "$HTTP_PROXY_FORWARD" = "YES" ]; then
+ have=NGX_HTTP_PROXY_FORWARD . auto/have
+fi
have=NGX_HTTP_FASTCGI_TEMP_PATH value="\"$NGX_HTTP_FASTCGI_TEMP_PATH\""
. auto/define
have=NGX_HTTP_UWSGI_TEMP_PATH value="\"$NGX_HTTP_UWSGI_TEMP_PATH\""
diff --git a/auto/options b/auto/options
index 521c9768..833ac06f 100644
--- a/auto/options
+++ b/auto/options
@@ -82,6 +82,7 @@ HTTP_SPLIT_CLIENTS=YES
HTTP_REFERER=YES
HTTP_REWRITE=YES
HTTP_PROXY=YES
+HTTP_PROXY_FORWARD=NO
HTTP_FASTCGI=YES
HTTP_UWSGI=YES
HTTP_SCGI=YES
@@ -245,6 +246,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated"
--with-http_secure_link_module) HTTP_SECURE_LINK=YES ;;
--with-http_degradation_module) HTTP_DEGRADATION=YES ;;
--with-http_slice_module) HTTP_SLICE=YES ;;
+ --with-http_proxy_forward) HTTP_PROXY_FORWARD=YES ;;
--without-http_charset_module) HTTP_CHARSET=NO ;;
--without-http_gzip_module) HTTP_GZIP=NO ;;
@@ -460,6 +462,7 @@ cat << END
--with-http_degradation_module enable ngx_http_degradation_module
--with-http_slice_module enable ngx_http_slice_module
--with-http_stub_status_module enable ngx_http_stub_status_module
+ --with-http_proxy_forward enable http forward proxy support
--without-http_charset_module disable ngx_http_charset_module
--without-http_gzip_module disable ngx_http_gzip_module
diff --git a/src/http/modules/ngx_http_auth_basic_module.c b/src/http/modules/ngx_http_auth_basic_module.c
index ed9df343..39914e2f 100644
--- a/src/http/modules/ngx_http_auth_basic_module.c
+++ b/src/http/modules/ngx_http_auth_basic_module.c
@@ -36,7 +36,7 @@ static char *ngx_http_auth_basic_user_file(ngx_conf_t *cf, ngx_command_t *cmd,
static ngx_command_t ngx_http_auth_basic_commands[] = {
{ ngx_string("auth_basic"),
- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF
|NGX_CONF_TAKE1,
ngx_http_set_complex_value_slot,
NGX_HTTP_LOC_CONF_OFFSET,
@@ -44,7 +44,7 @@ static ngx_command_t ngx_http_auth_basic_commands[] = {
NULL },
{ ngx_string("auth_basic_user_file"),
- NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF
|NGX_CONF_TAKE1,
ngx_http_auth_basic_user_file,
NGX_HTTP_LOC_CONF_OFFSET,
@@ -343,6 +343,13 @@ ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm)
r->headers_out.www_authenticate->value.data = basic;
r->headers_out.www_authenticate->value.len = len;
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (r->method == NGX_HTTP_CONNECT) {
+ ngx_str_set(&r->headers_out.www_authenticate->key, "Proxy-Authenticate");
+ return NGX_HTTP_PROXY_AUTH_REQUIRED;
+ }
+#endif
+
return NGX_HTTP_UNAUTHORIZED;
}
diff --git a/src/http/modules/ngx_http_chunked_filter_module.c b/src/http/modules/ngx_http_chunked_filter_module.c
index 4d6fd3ee..50ab3513 100644
--- a/src/http/modules/ngx_http_chunked_filter_module.c
+++ b/src/http/modules/ngx_http_chunked_filter_module.c
@@ -77,6 +77,9 @@ ngx_http_chunked_header_filter(ngx_http_request_t *r)
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (r->http_version >= NGX_HTTP_VERSION_11
+#if (NGX_HTTP_PROXY_FORWARD)
+ && r->method != NGX_HTTP_CONNECT
+#endif
&& clcf->chunked_transfer_encoding)
{
if (r->expect_trailers) {
diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c
index 6cf2cbde..389f3e05 100644
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -84,6 +84,9 @@ typedef struct {
ngx_http_proxy_vars_t vars;
ngx_flag_t redirect;
+#if (NGX_HTTP_PROXY_FORWARD)
+ ngx_flag_t forward;
+#endif
ngx_uint_t http_version;
@@ -125,6 +128,7 @@ static ngx_int_t ngx_http_proxy_eval(ngx_http_request_t *r,
static ngx_int_t ngx_http_proxy_create_key(ngx_http_request_t *r);
#endif
static ngx_int_t ngx_http_proxy_create_request(ngx_http_request_t *r);
+static ngx_int_t ngx_http_proxy_create_empty_request(ngx_http_request_t *r);
static ngx_int_t ngx_http_proxy_reinit_request(ngx_http_request_t *r);
static ngx_int_t ngx_http_proxy_body_output_filter(void *data, ngx_chain_t *in);
static ngx_int_t ngx_http_proxy_process_status_line(ngx_http_request_t *r);
@@ -226,7 +230,6 @@ static ngx_conf_bitmask_t ngx_http_proxy_next_upstream_masks[] = {
{ ngx_null_string, 0 }
};
-
#if (NGX_HTTP_SSL)
static ngx_conf_bitmask_t ngx_http_proxy_ssl_protocols[] = {
@@ -926,6 +929,19 @@ ngx_http_proxy_handler(ngx_http_request_t *r)
u->accel = 1;
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (plcf->forward && r->method == NGX_HTTP_CONNECT) {
+ u->forward = 1;
+ u->create_request = ngx_http_proxy_create_empty_request;
+
+ r->main->count++;
+
+ ngx_http_upstream_init(r);
+
+ return NGX_DONE;
+ }
+#endif
+
if (!plcf->upstream.request_buffering
&& plcf->body_values == NULL && plcf->upstream.pass_request_body
&& (!r->headers_in.chunked
@@ -1145,6 +1161,16 @@ ngx_http_proxy_create_key(ngx_http_request_t *r)
#endif
+#if (NGX_HTTP_PROXY_FORWARD)
+static ngx_int_t
+ngx_http_proxy_create_empty_request(ngx_http_request_t *r)
+{
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "create empty http proxy request");
+
+ return NGX_OK;
+}
+#endif
static ngx_int_t
ngx_http_proxy_create_request(ngx_http_request_t *r)
@@ -2885,6 +2911,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_t *cf)
* conf->body_values = NULL;
* conf->body_source = { 0, NULL };
* conf->redirects = NULL;
+ * conf->forward = 0;
* conf->ssl = 0;
* conf->ssl_protocols = 0;
* conf->ssl_ciphers = { 0, NULL };
@@ -3646,6 +3673,11 @@ ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
ngx_http_core_loc_conf_t *clcf;
ngx_http_script_compile_t sc;
+#if (NGX_HTTP_PROXY_FORWARD)
+ static ngx_str_t forward_proxy_url =
+ ngx_string("http://$http_host$uri$is_args$args");
+#endif
+
if (plcf->upstream.upstream || plcf->proxy_lengths) {
return "is duplicate";
}
@@ -3662,6 +3694,13 @@ ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
url = &value[1];
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (ngx_strncasecmp(url->data, (u_char *) "forward", 7) == 0) {
+ plcf->forward = 1;
+ url = &forward_proxy_url;
+ }
+#endif
+
n = ngx_http_script_variables_count(url);
if (n) {
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
index 3671558d..c0638579 100644
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -1938,13 +1938,17 @@ ngx_http_auth_basic_user(ngx_http_request_t *r)
return NGX_DECLINED;
}
- if (r->headers_in.authorization == NULL) {
+ if (r->headers_in.authorization != NULL) {
+ encoded = r->headers_in.authorization->value;
+#if (NGX_HTTP_PROXY_FORWARD)
+ } else if (r->headers_in.proxy_authorization != NULL) {
+ encoded = r->headers_in.proxy_authorization->value;
+#endif
+ } else {
r->headers_in.user.data = (u_char *) "";
return NGX_DECLINED;
}
- encoded = r->headers_in.authorization->value;
-
if (encoded.len < sizeof("Basic ") - 1
|| ngx_strncasecmp(encoded.data, (u_char *) "Basic ",
sizeof("Basic ") - 1)
@@ -4420,6 +4424,9 @@ static ngx_http_method_name_t ngx_methods_names[] = {
{ (u_char *) "LOCK", (uint32_t) ~NGX_HTTP_LOCK },
{ (u_char *) "UNLOCK", (uint32_t) ~NGX_HTTP_UNLOCK },
{ (u_char *) "PATCH", (uint32_t) ~NGX_HTTP_PATCH },
+#if (NGX_HTTP_PROXY_FORWARD)
+ { (u_char *) "CONNECT", (uint32_t) ~NGX_HTTP_CONNECT },
+#endif
{ NULL, 0 }
};
diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c
index cfc42f9d..f2eabd23 100644
--- a/src/http/ngx_http_parse.c
+++ b/src/http/ngx_http_parse.c
@@ -108,6 +108,9 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
sw_start = 0,
sw_method,
sw_spaces_before_uri,
+#if (NGX_HTTP_PROXY_FORWARD)
+ sw_spaces_after_connect,
+#endif
sw_schema,
sw_schema_slash,
sw_schema_slash_slash,
@@ -241,6 +244,14 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
break;
case 7:
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (ngx_str7_cmp(m, 'C', 'O', 'N', 'N', 'E', 'C', 'T', ' '))
+ {
+ r->method = NGX_HTTP_CONNECT;
+ break;
+ }
+#endif
+
if (ngx_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', ' '))
{
r->method = NGX_HTTP_OPTIONS;
@@ -267,6 +278,13 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
}
state = sw_spaces_before_uri;
+
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (r->method == NGX_HTTP_CONNECT) {
+ state = sw_spaces_after_connect;
+ }
+#endif
+
break;
}
@@ -342,6 +360,19 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
}
break;
+#if (NGX_HTTP_PROXY_FORWARD)
+ /* space* after CONNECT method */
+ case sw_spaces_after_connect:
+
+ if (ch == ' ') {
+ break;
+ }
+
+ state = sw_host_start;
+
+ /* fall through */
+#endif
+
case sw_host_start:
r->host_start = p;
@@ -715,6 +746,13 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
switch (ch) {
case '/':
state = sw_first_major_digit;
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (r->method == NGX_HTTP_CONNECT) {
+ r->uri_start = p;
+ r->uri_end = p + 1;
+ }
+#endif
+
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
@@ -838,6 +876,13 @@ done:
return NGX_HTTP_PARSE_INVALID_09_METHOD;
}
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (r->http_version < NGX_HTTP_VERSION_11 && r->method == NGX_HTTP_CONNECT)
+ {
+ return NGX_HTTP_PARSE_INVALID_METHOD;
+ }
+#endif
+
return NGX_OK;
}
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
index 6feb6cc3..8cf24298 100644
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -158,6 +158,12 @@ ngx_http_header_t ngx_http_headers_in[] = {
offsetof(ngx_http_headers_in_t, authorization),
ngx_http_process_unique_header_line },
+#if (NGX_HTTP_PROXY_FORWARD)
+ { ngx_string("Proxy-Authorization"),
+ offsetof(ngx_http_headers_in_t, proxy_authorization),
+ ngx_http_process_unique_header_line },
+#endif
+
{ ngx_string("Keep-Alive"), offsetof(ngx_http_headers_in_t, keep_alive),
ngx_http_process_header_line },
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
index 70c2d424..0b04f21c 100644
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -41,6 +41,7 @@
#define NGX_HTTP_UNLOCK 0x2000
#define NGX_HTTP_PATCH 0x4000
#define NGX_HTTP_TRACE 0x8000
+#define NGX_HTTP_CONNECT 0x10000
#define NGX_HTTP_CONNECTION_CLOSE 1
#define NGX_HTTP_CONNECTION_KEEP_ALIVE 2
@@ -92,6 +93,7 @@
#define NGX_HTTP_FORBIDDEN 403
#define NGX_HTTP_NOT_FOUND 404
#define NGX_HTTP_NOT_ALLOWED 405
+#define NGX_HTTP_PROXY_AUTH_REQUIRED 407
#define NGX_HTTP_REQUEST_TIME_OUT 408
#define NGX_HTTP_CONFLICT 409
#define NGX_HTTP_LENGTH_REQUIRED 411
@@ -207,6 +209,9 @@ typedef struct {
#endif
ngx_table_elt_t *authorization;
+#if (NGX_HTTP_PROXY_FORWARD)
+ ngx_table_elt_t *proxy_authorization;
+#endif
ngx_table_elt_t *keep_alive;
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
index 47f98ccb..9b227adf 100644
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -45,6 +45,10 @@ static ngx_int_t ngx_http_upstream_send_request_body(ngx_http_request_t *r,
ngx_http_upstream_t *u, ngx_uint_t do_write);
static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r,
ngx_http_upstream_t *u);
+#if (NGX_HTTP_PROXY_FORWARD)
+static void ngx_http_upstream_send_connected_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u);
+#endif
static void ngx_http_upstream_read_request_handler(ngx_http_request_t *r);
static void ngx_http_upstream_process_header(ngx_http_request_t *r,
ngx_http_upstream_t *u);
@@ -1560,6 +1564,12 @@ ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u)
u->write_event_handler = ngx_http_upstream_send_request_handler;
u->read_event_handler = ngx_http_upstream_process_header;
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (u->forward) {
+ u->write_event_handler = ngx_http_upstream_send_connected_handler;
+ }
+#endif
+
c->sendfile &= r->connection->sendfile;
u->output.sendfile = c->sendfile;
@@ -1643,6 +1653,13 @@ ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u)
#endif
+#if (NGX_HTTP_PROXY_FORWARD)
+ if (u->forward) {
+ ngx_http_upstream_send_connected_handler(r, u);
+ return;
+ }
+#endif
+
ngx_http_upstream_send_request(r, u, 1);
}
@@ -2255,6 +2272,40 @@ ngx_http_upstream_send_request_handler(ngx_http_request_t *r,
ngx_http_upstream_send_request(r, u, 1);
}
+#if (NGX_HTTP_PROXY_FORWARD)
+static void
+ngx_http_upstream_send_connected_handler(ngx_http_request_t *r,
+ ngx_http_upstream_t *u)
+{
+ ngx_connection_t *c;
+
+ if (u->header_sent) {
+ return;
+ }
+
+ c = u->peer.connection;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+ "http upstream send connected handler");
+
+ if (c->write->timedout) {
+ ngx_http_upstream_finalize_request(r, u, NGX_HTTP_BAD_GATEWAY);
+ return;
+ }
+
+ if (u->state->connect_time == (ngx_msec_t) -1) {
+ u->state->connect_time = ngx_current_msec - u->start_time;
+ }
+
+ r->headers_out.status = NGX_HTTP_OK;
+ r->chunked = 0;
+
+ u->upgrade = 1;
+
+ ngx_http_upstream_send_response(r, u);
+}
+#endif
+
static void
ngx_http_upstream_read_request_handler(ngx_http_request_t *r)
@@ -3377,19 +3428,20 @@ ngx_http_upstream_process_upgraded(ngx_http_request_t *r,
do_write = 1;
}
- if (b->start == NULL) {
- b->start = ngx_palloc(r->pool, u->conf->buffer_size);
- if (b->start == NULL) {
- ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
- return;
- }
+ }
- b->pos = b->start;
- b->last = b->start;
- b->end = b->start + u->conf->buffer_size;
- b->temporary = 1;
- b->tag = u->output.tag;
+ if (b->start == NULL) {
+ b->start = ngx_palloc(r->pool, u->conf->buffer_size);
+ if (b->start == NULL) {
+ ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
+ return;
}
+
+ b->pos = b->start;
+ b->last = b->start;
+ b->end = b->start + u->conf->buffer_size;
+ b->temporary = 1;
+ b->tag = u->output.tag;
}
for ( ;; ) {
diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h
index fd642c2d..b4fcbabc 100644
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -397,6 +397,9 @@ struct ngx_http_upstream_s {
unsigned request_body_sent:1;
unsigned request_body_blocked:1;
unsigned header_sent:1;
+#if (NGX_HTTP_PROXY_FORWARD)
+ unsigned forward:1;
+#endif
};
More information about the nginx-devel
mailing list