caddy: PATH_INFO is empty with basic fastcgi Caddyfile

Hey everyone, I’m trying to move from Caddy 1 to Caddy 2 and was in the process of installing a Moodle server (PHP) when I encountered the following issue:

moodle.domain {
    root * /datapool/www/moodle_base/moodle

    php_fastcgi 127.0.0.1:9000 {
        env SERVER_SOFTWARE Apache
    }
}

When doing a GET request with path https://moodle.domain/lib/javascript.php/1599824490/lib/requirejs/require.min.js the PATH_INFO Variable (should be /1599824490/lib/requirejs/require.min.js) is not set correctly:

{"level":"debug","ts":1599828392.7683938,"logger":"http.reverse_proxy.transport.fastcgi","msg":"roundtrip","request":{"remote_addr":"12.34.56.78:1234","proto":"HTTP/2.0","method":"GET","host":"moodle.domain","uri":"/lib/javascript.php","headers":{"Dnt":["1"],"Upgrade-Insecure-Requests":["1"],"User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36"],"Sec-Fetch-Dest":["document"],"Cookie":["MoodleSession=sfenfg19jlpj4mt1gm9um41jtn"],"Cache-Control":["max-age=0"],"Sec-Fetch-User":["?1"],"X-Forwarded-Proto":["https"],"Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"],"Accept-Language":["de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7"],"X-Forwarded-For":["12.34.56.78"],"Sec-Fetch-Site":["none"],"Accept-Encoding":["gzip, deflate, br"],"Sec-Fetch-Mode":["navigate"]},"tls":{"resumed":false,"version":772,"cipher_suite":4865,"proto":"h2","proto_mutual":true,"server_name":"moodle.domain"}},"dial":"127.0.0.1:9000","env":{"AUTH_TYPE":"","CONTENT_LENGTH":"","CONTENT_TYPE":"","DOCUMENT_ROOT":"/datapool/www/moodle_base/moodle","DOCUMENT_URI":"/lib/javascript.php","GATEWAY_INTERFACE":"CGI/1.1","HTTPS":"on","HTTP_ACCEPT":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9","HTTP_ACCEPT_ENCODING":"gzip, deflate, br","HTTP_ACCEPT_LANGUAGE":"de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7","HTTP_CACHE_CONTROL":"max-age=0","HTTP_COOKIE":"MoodleSession=sfenfg19jlpj4mt1gm9um41jtn","HTTP_DNT":"1","HTTP_HOST":"moodle.domain","HTTP_SEC_FETCH_DEST":"document","HTTP_SEC_FETCH_MODE":"navigate","HTTP_SEC_FETCH_SITE":"none","HTTP_SEC_FETCH_USER":"?1","HTTP_UPGRADE_INSECURE_REQUESTS":"1","HTTP_USER_AGENT":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_16_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36","HTTP_X_FORWARDED_FOR":"12.34.56.78","HTTP_X_FORWARDED_PROTO":"1234","PATH_INFO":"","QUERY_STRING":"","REMOTE_ADDR":"12.34.56.78","REMOTE_HOST":"12.34.56.78","REMOTE_IDENT":"","REMOTE_PORT":"1234","REMOTE_USER":"","REQUEST_METHOD":"GET","REQUEST_SCHEME":"https","REQUEST_URI":"/lib/javascript.php/1599824490/lib/requirejs/require.min.js","SCRIPT_FILENAME":"/datapool/www/moodle_base/moodle/lib/javascript.php","SCRIPT_NAME":"/lib/javascript.php","SERVER_NAME":"moodle.domain","SERVER_PROTOCOL":"HTTP/2.0","SERVER_SOFTWARE":"Apache","SSL_CIPHER":"TLS_AES_128_GCM_SHA256","SSL_PROTOCOL":"TLSv1.3"}}

While debugging I saw that the URL.Path is already just /lib/javascript.php here and the rest is missing: https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go#L197

I temporarily fixed this by using the origRequest, but this causes other issues as the (correct) rewrite of / to /index.php fails now.

        origReq, ok := r.Context().Value(caddyhttp.OriginalRequestCtxKey).(http.Request)
        if !ok {
                // some requests, like active health checks, don't add this to
                // the request context, so we can just use the current URL
                origReq = *r
        }
        fpath := origReq.URL.Path

I guess there is something going on with the default rewrites as there already is a split_path option there.

(caddy versions affected: 2.1.1, 2.2.0-rc.1, master)

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 30 (13 by maintainers)

Most upvoted comments

I can confirm that using the binary from 3e577ef works with my Moodle setup using the default php_fastcgi directive 😃

Excellent!

It’s slightly too bad about the potential edge case(s) the fix introduces, but probably a worthwhile trade-off, especially if the edge cases involves the user configuring something weird, right?

Looking forward to getting it in as one of the first commits for the 2.3 tree, after 2.2 is released, hopefully this week.

Edit: Heh, it was the last commit for the 2.3 tree.

Hi @francislavoie, I’ve update the “test suite” by using index.php as fallback resource in each web server instance: the only difference now in Caddy is on PATH_INFO being always defined but empty just for both /foo and /foo.php/foo which looks like not an issue in PHP: those using empty() will be fine and those using isset() will have an empty value.

@SteffenDE, would you mind to give caddy_Linux_go1.14_3e577ef (from https://github.com/caddyserver/caddy/actions/runs/263950314) a try in your setup w/o changing anything of your configuration but the caddy binary? TIA!

HTH, Matteo

Hi @francislavoie, great news!

I did some tests and I can confirm it: I’m preparing a rough test suite and will share via my GH account.

HTH, Matteo

Hi @SteffenDE; thanks for your quick try&reply: that was expected, just a small step ahead.

Next step: create a docker compose with caddy, apache and nginx serving the same sample, info.php and index.php (<?php phpinfo(INFO_VARIABLES);), via the same PHP-FPM engine instance, to look for any difference among the different vendors to help @francislavoie (and @mholt).

HTH, Matteo

I greatly appreciate that someone is looking into this, as I sadly don’t have the time to investigate this myself. For the meantime, I’m running Caddy v1 and proxy the Moodle requests from Caddy 2 to the old Caddy instance 😅

In the past, I’ve been running an extra Apache just for Moodle, but after discovering that setting the environment variable to Apache saves me from doing this, Moodle was running perfectly for the last two (or so) years on Caddy. Great work and a huge thank you to both the Moodle and Caddy team!

Okay, I see what’s going on. This is pretty tricky.

So, I’m comparing with the code from Caddy v1 which apparently did work correctly: https://github.com/caddyserver/caddy/blob/v1/caddyhttp/fastcgi/fastcgi.go

The actual fastcgi code is pretty much the same in v2, but the difference comes in how the request prep happens in v2 compared to v1.

In v1, this wasn’t a problem because the index.php fallback wasn’t handled via a rewrite internally beforehand, it was just directly handled in ServeHTTP without updating the requested path.

In v2, instead it works by delegating the decision for index.php to the built-in try_files behaviour of the php_fastcgi directive. See the expanded form to see the stuff it’s doing: https://caddyserver.com/docs/caddyfile/directives/php_fastcgi#expanded-form

As you said, we could use the original URL to solve this case with .php being found in the URL, and this does work (tried moving the origReq block of code up and using that instead of fpath when splitting), but I’m pretty worried about edgecases.

I tried doing some research for documentation to figure out how PATH_INFO is meant to be handled depending on the original URL from the browser. E.g. we know that /index.php/foo should have PATH_INFO set to /foo, but should the request to /foo also have PATH_INFO set to /foo, since the server rewrites it to be handled by index.php? I’m leaning towards no, but it’s unclear, and the RFC https://tools.ietf.org/html/rfc3875#section-4.1.5 is not specific about that.

Also, with that change, it would also make requests like /foo.php/foo that get rewritten to index.php also have /foo in PATH_INFO even though /index.php is what actually gets run, not /foo.php. I think we might need some extra logic to only split if the index file is found in the original path? 🤔

I would have to spin up an environment with nginx and/or apache with mod_cgi to verify how they behave. My env is kinda in a state that would make that tricky right now though, so I haven’t done that yet. I wouldn’t mind help there if you’re capable of testing that our quickly.

There’s also security implications with any change we make here, so I’m extra nervous about it.

Hmm, it’s funky that they use PHP to serve JS, but okay 😛

I’ll need to take a closer look at all this, I’ll come back to this one this weekend. Thanks for the report!