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