Caddy: Caddy speaks plain HTTP to FastCGI backend

Created on 11 Jul 2020  路  4Comments  路  Source: caddyserver/caddy

I've recently converted my setup from Caddy 1 to Caddy 2. I have a custom application that speaks FastCGI, but it's not a PHP application.

I noticed that attempting to access this application just resulted in a hang. I've put together what I think is a minimal reproducer and stuck it, along with output when I run it, at https://github.com/gmacon/fcgi-repro. If you look at the packet capture of the traffic between Caddy and the backend, you'll see that Caddy tries to speak what looks like plain HTTP.

If you have caddy, tcpdump, and python3 on your $PATH, you should be able to reproduce the issue by cloning that repo and running ./repro.sh.

I just reproduced using a Caddy 2.1.1 binary from the release page.

(Aside: The contributing guidelines say to fill out the issue template when reporting a bug, but I can't tell that there is an issue template...)

Edit: I realize I should say: I've worked around this issue for now by switching to the HTTP transport.

bug good first issue

Most helpful comment

I haven't touched that code at all @mholt I've only touched the php_fastcgi directive but not the fastcgi transport.

Anyways, the bug seems to be in the Caddyfile parsing, if the transport doesn't have any options set then it doesn't set the transport.

For example, this does work:

{
    debug
}

http://127.0.0.1:8080 {
    reverse_proxy 127.0.0.1:65535 {
        transport fastcgi {
            env FOO BAR
        }
    }
}

Adapted JSON:

{
  "logging": {
    "logs": {
      "default": {
        "level": "DEBUG"
      }
    }
  },
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":8080"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "127.0.0.1"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "transport": {
                            "env": {
                              "FOO": "BAR"
                            },
                            "protocol": "fastcgi"
                          },
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:65535"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ],
          "automatic_https": {
            "skip": [
              "127.0.0.1"
            ]
          }
        }
      }
    }
  }
}

So for now you can put a dummy env just to get the transport to get set, but I'll fix this for the next release.

All 4 comments

Only had a short chance for a quick look. The adapted JSON does not yield a fastcgi transport configuration:

{
  "logging": {
    "logs": {
      "default": {
        "level": "DEBUG"
      }
    }
  },
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":8080"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "127.0.0.1"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:65535"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ],
          "automatic_https": {
            "skip": [
              "127.0.0.1"
            ]
          }
        }
      }
    }
  }
}

@francislavoie was the last contributor to touch that part of the code; Francis could you look into this?

I haven't touched that code at all @mholt I've only touched the php_fastcgi directive but not the fastcgi transport.

Anyways, the bug seems to be in the Caddyfile parsing, if the transport doesn't have any options set then it doesn't set the transport.

For example, this does work:

{
    debug
}

http://127.0.0.1:8080 {
    reverse_proxy 127.0.0.1:65535 {
        transport fastcgi {
            env FOO BAR
        }
    }
}

Adapted JSON:

{
  "logging": {
    "logs": {
      "default": {
        "level": "DEBUG"
      }
    }
  },
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":8080"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "127.0.0.1"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "reverse_proxy",
                          "transport": {
                            "env": {
                              "FOO": "BAR"
                            },
                            "protocol": "fastcgi"
                          },
                          "upstreams": [
                            {
                              "dial": "127.0.0.1:65535"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ],
          "automatic_https": {
            "skip": [
              "127.0.0.1"
            ]
          }
        }
      }
    }
  }
}

So for now you can put a dummy env just to get the transport to get set, but I'll fix this for the next release.

Okay confirmed the issue.

https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/caddyfile.go#L564

This DeepEqual comparison of the transport returns true because the fastcgi transport doesn't have any options set, so it's the same as the identity.

This comparison is meant for skipping a redundant item in the JSON config specifying the transport as HTTP because that's the default, but there are other transports, so it breaks in those cases. The condition just needs to be fixed to only apply if the transport is HTTP. I figure this is just transportModuleName == "http" && but I'll let @mholt make the fix.

As I said above, a fine workaround is just to set a dummy env for now so that the transport has at least one option.

Ah, thanks for finding that out! I was wrong, as it was a different part of the file that you worked on.

Will try to get around to it soon. If someone beats me to it, feel free to submit a PR.

Was this page helpful?
0 / 5 - 0 ratings