Project

General

Profile

[Solved] mod_extforward X-Forwarded-Proto behavior different after using url.rewrite?

Added by lapo over 1 year ago

My lighttpd 1.4.55 receives requests via a reverse proxy, which is properly setting X-Forwarded-Proto header.

When landing on a PHP page $_SERVER[HTTPS]=on, but when landing on PHP via a rewrite, it is not defined.

File info.php copntains:

<?php
header('Content-Type: text/plain');
print_r($_SERVER);
?>

Domain is configured thus:

$HTTP["host"] == "php74.lapo.it" {
  server.document-root = "/usr/local/www/default/" 
  fastcgi.server = ( ".php" => (( "host" => "10.1.1.2", "port" => "9000" ))) # php74
  $HTTP["url"] == "/b" { # just for test, not originally in the example that produced the bug
    setenv.add-environment = (
      "HTTPS" => "on",
      "REQUEST_SCHEME"=> "https" 
    )
  }
  url.rewrite-if-not-file = ( "^/.*$" => "/info.php/$0" )
}

When visiting https://php74.lapo.it/info.php I get:

    [HTTPS] => on
    [REQUEST_SCHEME] => https
    [REQUEST_URI] => /info.php
    [PHP_SELF] => /info.php

When visiting https://php74.lapo.it/a I get:

    [REQUEST_SCHEME] => http
    [REDIRECT_URI] => /info.php/a
    [REQUEST_URI] => /a
    [PHP_SELF] => /info.php/a

When visiting https://php74.lapo.it/b I get:

    [HTTPS] => on
    [REQUEST_SCHEME] => https
    [REDIRECT_URI] => /info.php/b
    [REQUEST_URI] => /b
    [PHP_SELF] => /info.php/b


Replies (7)

RE: FastCGI behavior different when using rewrite? - Added by gstrauss over 1 year ago

I think that you're confused about two issues.

First, HTTPS=on is set by lighttpd in the FastCGI environment if the client connection to the server was made over TLS. You do not need to use setenv.add-environment to set HTTPS=on.

Second, after a URL rewrite, lighttpd re-processes the request, including applying config rules to the new URL. Config rules that were previously applied to the old URL (before the rewrite) do not apply to the new URL. It is not cumulative. Never has been.

I would recommend allowing lighttpd to set HTTPS=on, and have your backend PHP set REQUEST_SCHEME=https if HTTPS=on.

However, if you're using a reverse proxy in front of lighttpd, and I am going to guess that the reverse proxy is not connecting to lighttpd via HTTPS, you might consider looking at mod_extforward to transmit this information to lighttpd. lighttpd mod_extforward supports the RFC 7239 Forwarded header. lighttpd also supports the HAProxy PROXY protocol.

If you must fudge those environment variables manually, you can force the env variables you want set for the extension you are directing to your fastcgi server, e.g. .php

    $HTTP["url"] =~ "\.php(/|$)" {
        setenv.add-environment = ( ... )
    }

RE: FastCGI behavior different when using rewrite? - Added by lapo over 1 year ago

Yeah I know setting the env is a dirty hack, I tried it for test "/b" just to check that it reached PHP that way, but my real use-case test is "/a" so let's just forget about "/b", that is:

$HTTP["host"] == "php74.lapo.it" {
  server.document-root = "/usr/local/www/default/" 
  fastcgi.server = ( ".php" => (( "host" => "10.1.1.2", "port" => "9000" ))) # php74
  url.rewrite-if-not-file = ( "^/.*$" => "/info.php/$0" )
}

Connection from reverse proxy (which listens on 433) to lighttpd (which listens on 127.0.0.1:80) is always in http, but as X-Forwarded-Proto=https is properly set in request headers by the reverse proxy, lighttpd does the right thing ans sets HTTPS=on… except when doing a rewrite.

I understand that rewrite produces a wholly new request, but the new requests has the same HTTP headers the original request had (including "[HTTP_X_FORWARDED_PROTO] => https" as can be seen by PHP dump at the URLs above), so I'd thought that the same logic that made lighttpd recognize the protocol the first time would work also after a rewrite.

RE: FastCGI behavior different when using rewrite? - Added by lapo over 1 year ago

PS: I forgot to mention that I'm using mod_extforward already (and that's how the requests without a rewrite do get HTTPS=on).

RE: FastCGI behavior different when using rewrite? - Added by gstrauss over 1 year ago

PS: I forgot to mention that I'm using mod_extforward already (and that's how the requests without a rewrite do get HTTPS=on).

That's not an "oopsie". That's a gross oversight.

Please read How to get support and then update this post with better info.

RE: FastCGI behavior different when using rewrite? - Added by lapo over 1 year ago

Environment:

# uname -a
FreeBSD lighttpd3.lapo.it 12.1-RELEASE-p2 FreeBSD 12.1-RELEASE-p2 GENERIC  amd64
# lighttpd -v
lighttpd/1.4.55 (ssl) - a light and fast webserver
# lighttpd -tt -f /usr/local/etc/lighttpd/lighttpd.conf
# lighttpd -p -f /usr/local/etc/lighttpd/lighttpd.conf
config {
    var.PID                 = 41907
    var.CWD                 = "/root" 
    server.modules          = ("mod_rewrite", "mod_fastcgi", "mod_extforward")
    var.log_root            = "/var/log/lighttpd" 
    server.document-root    = "/usr/local/www/default/" 
    server.errorlog         = "/var/log/lighttpd/error.log" 
    server.event-handler    = "freebsd-kqueue" 
    extforward.forwarder    = (
        "127.0.0.1" => "trust",
        "10.1.0.3"  => "trust",
        # 2
    )
    fastcgi.server          = (
        ".php" => (
            (
                "host" => "10.1.1.2",
                "port" => "9000",
                # 2
            ),
        ),
    )
    url.rewrite-if-not-file = (
        "^/a$" => "/info.php/$0",
    )
    url.rewrite-once        = (
        "^/b$" => "/info.php/$0",
    )

}

How to reproduce:

% curl -s https://topic9293.lapo.it/info.php | egrep 'REQUEST_SCHEME|HTTPS'
    [HTTPS] => on
    [REQUEST_SCHEME] => https
% curl -s https://topic9293.lapo.it/a | egrep 'REQUEST_SCHEME|HTTPS'
    [REQUEST_SCHEME] => http
% curl -s https://topic9293.lapo.it/b | egrep 'REQUEST_SCHEME|HTTPS'
    [HTTPS] => on
    [REQUEST_SCHEME] => https

It seems to me that there is some strange interaction between mod_extforward and mod_rewrite.

RE: FastCGI behavior different when using rewrite? - Added by gstrauss over 1 year ago

Thank you for providing basic info with your question.

It seems to me that there is some strange interaction between mod_extforward and mod_rewrite.

It seems that you have re-assessed, since you did not mention mod_extforward before I called it out.

mod_extforward conceptually operates per-connection.

Were you using mod_extforward with HAProxy PROXY protocol, mod_extforward would have preserved the "https" protocol even after the url.rewrite.

You did not initially share a config which reproduced your problem. Now that you have done so, I see that you are using mod_extforward differently, and not using the HAProxy PROXY protocol.

There is an edge case in mod_extforward whereby X-Forwarded-Proto is not re-processed (under certain conditions) after a request is restarted with the internal code HANDLER_COMEBACK -- e.g. restarted by mod_rewrite, mod_magnet (MAGNET_RESTART_REQUEST), mod_cgi (cgi.local-redir), reconnecting to a gw_backend, or GW_AUTHORIZER. Other headers are re-processed after an internal request restart, but X-Forwarded-Proto is not reprocessed when IPs are listed in extforward.forwarder. (extforward.forwarder = ("all" => "trust") does not have this issue, but is also not recommended, due to being too trusting (unsafe))

I'll add a patch to the next lighttpd release, but your workaround is as I suggested above: isolate your hack to specifically set the value you want for your backend PHP.

$HTTP["url"] =~ "\.php(/|$)" {
    setenv.add-environment = ( ... )
}   

If you prefer to patch lighttpd-1.4.55 source code for mod_extforward:

--- a/src/mod_extforward.c
+++ b/src/mod_extforward.c
@@ -571,9 +571,11 @@ static void mod_extforward_set_proto(server *srv, connection *con, const char *p
                        http_header_env_set(con, CONST_STR_LEN("_L_EXTFORWARD_ACTUAL_PROTO"), CONST_BUF_LEN(con->uri.scheme));
                }
                if (buffer_eq_icase_ss(proto, protolen, CONST_STR_LEN("https"))) {
+                       buffer_copy_string_len(con->proto, CONST_STR_LEN("https"));
                        buffer_copy_string_len(con->uri.scheme, CONST_STR_LEN("https"));
                        config_cond_cache_reset_item(srv, con, COMP_HTTP_SCHEME);
                } else if (buffer_eq_icase_ss(proto, protolen, CONST_STR_LEN("http"))) {
+                       buffer_copy_string_len(con->proto, CONST_STR_LEN("http"));
                        buffer_copy_string_len(con->uri.scheme, CONST_STR_LEN("http"));
                        config_cond_cache_reset_item(srv, con, COMP_HTTP_SCHEME);
                }

    (1-7/7)