Skip to content

FR: Support stripping HTTP request paths from funnel proxy routes #6571

@mvdan

Description

@mvdan

What are you trying to do?

I'm using Funnel to replace ngrok with tailscale. So far it's been working well!

Right now, I have set it up as follows:

# tailscale serve status
https://p14s.blue-toad.ts.net (Funnel on)
|-- / proxy http://127.0.0.1:10080

Right now I am using this to test HTTP webhooks from a GitHub App, where the local Go server simply listens on http at port 10080. GitHub sends the events to https://p14s.blue-toad.ts.net/. So far so good, and it works perfectly.

However, this particular GitHub app is only one of the pieces of software that I want to expose via Funnel for end-to-end testing. I could swap which program listens on port 10080 each time, but that's not great. For example, if I stop my Go backend for that GitHub App and work on something else serving HTTP over funnel, it will still receive the GitHub App webhook events from the previous app, because they're still wired up to the same place.

I could disable the webhook events from the GitHub App every time I stop testing it, but that's cumbersome. The problem here is that I'm sharing the same host (https://p14s.blue-toad.ts.net/) to test multiple apps at different times.

One decent option is to use different ports:

  --serve-port uint
    	port to serve on (443, 8443 or 10000) (default 443)

However, that's only three ports, so I could only test three different pieces of software, and then we're back to the same problem of sharing the same host and port. It would also be confusing for future me: I'll be sure to forget what I used each port for, even if I had unlimited ports. Potentially, attaching some form of label or name to each route or port, but that wouldn't be visible when looking at the HTTPS URLs alone.

I tried to solve this by using different paths, like so:

# tailscale serve status
https://p14s.blue-toad.ts.net (Funnel on)
|-- /                     text  "p14s is online"
|-- /cue-unity-github-app proxy http://127.0.0.1:10080

This way, the GitHub App was configured with the URL https://p14s.blue-toad.ts.net/cue-unity-github-app, which is not going to be shared with any other funnel route. The path also helps me remember what I exposed this particular route for. However, when doing a GET on that URL, an HTTP server listening on 10080 sees:

$ nc -l 127.0.0.1 10080
GET /cue-unity-github-app HTTP/1.1
Host: p14s.blue-toad.ts.net
[...]
X-Forwarded-For: 100.93.97.75

That is, it receives a GET /cue-unity-github-app rather than GET /. Right now it expects webhooks to come in at POST /foo, not at POST /cue-unity-github-app/foo. I could teach each one of these pieces of software to strip a path prefix, like https://pkg.go.dev/net/http#StripPrefix, but it feels unfortunate. Some of the pieces of software I need to test aren't written in Go, so changing their behavior won't be as straightforward.

How should we solve this?

Funnel already acts as an HTTP proxy, so I think it could also be taught to strip paths so that my app would see GET / in the example above. It doesn't need to be always on, or even by default - it could be an opt-in flag, like --strip-prefix.

Note that this is a problem I experience with the proxy subcommand, but not with path. If /home/mvdan/foo/bar.txt exists as a plaintext file and I expose it via tailscale serve /foo path /home/mvdan/foo, you will notice that https://p14s.blue-toad.ts.net/foo/bar.txt works perfectly - it reads /home/mvdan/foo/bar.txt rather than /home/mvdan/foo/foo/bar.txt.

My experience with HTTP proxies is somewhat limited, so I'm not sure whether what I am asking is an anti-pattern of any sort. However, the stripping already appears to happen for path, and intuitively I would find the proxy mode more useful if the stripping happened.

Yet another alternative would be to allow exposing multiple subdomains. For example, instead of exposing this GitHub App backend for testing at https://p14s.blue-toad.ts.net/cue-unity-github-app, I could expose it at https://cue-unity-github-app.p14s.blue-toad.ts.net/. I don't have a strong preference; both are equally easy to identify and remember.

What is the impact of not solving this?

I am able to use Funnel, but it's awkward to use it for more than one HTTP server at the same time - even when those HTTP servers aren't all running at the same time.

Anything else?

No response

Metadata

Metadata

Assignees

Labels

L2 FewLikelihoodP1 NuisancePriority levelT0 New featureIssue typefrFeature requestfunnelRelating to Tailscale Funnel https://tailscale.com/blog/introducing-tailscale-funnel/

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions