I wrote up a load balancing script that queries an api to get a list of ip address of healthy vms, and everything worked great in the editor. However, when I saved it and tested it live, I would always get an error that says “Direct IP access not allowed”.
I got around that by using a reverse ip hostname (i.e. x.x.x.x.bc.googleusercontent.com). But then, I was also using arbitrary port numbers for my services, and it would just give me an error saying that it failed to connect to the server. This also reveals my hostname, which, ideally, would be kept private.
So, I changed the all the ports to 80. The requests are finally getting through, but it seemed like not all of my headers were being sent. (i.e. Host and X-Forwarded-Proto) I double checked this with httpbin.org/headers.
All of these things make it seem like workers aren’t as powerful as it’s been made out to be. Is it really necessary to make fetch so restrictive?
Additionally, it would be very helpful if the editor testing would succumb to the same restrictions as it does live. Documentation about them would be great too.
I’m sorry to hear that you’ve run into so many of our sharp edges – I know how frustrating it can be, and I really appreciate the time you spent writing up your experience, because it helps us learn and improve our service. We definitely have some limitations, and we’d like to lift them as we are able to.
At this time we do not allow direct IP access via fetch(). Although it is technically possible for us to lift this restriction, we must perform a thorough security review of the implications on our infrastructure before we can do so. This will take time, so I cannot guarantee that it will happen by any particular date.
The reverse IP hostname is a nice trick! Regarding the error revealing your hostname, it may be necessary to sanitize certain 5xx errors which Cloudflare returns before returning them to the eyeball. This would probably be necessary even if direct IP access worked, since an origin connection error could still occur in that case, potentially revealing the origin IP.
I think that not being able to use arbitrary ports on an external service is a bug, but I’m still trying to confirm – there may be some rationale for disallowing it that I’m unaware of.
Neither of the two headers you listed can be directly controlled – the Workers runtime will overwrite both of them, since it emits host-style HTTP requests, which it must construct from the absolute URL passed to fetch(). However, both headers should indeed be sent. httpbin.org/headers does not seem to expose X-Forwarded-Proto, but it does expose the Host header, at least when I tested it.
To verify that X-Forwarded-Proto is sent (and correctly reflects the eyeball <-> Cloudflare connection protocolEdit: @ssttevee points out in the next post that it is actually derived from the scheme of the URL passed to fetch()), I configured nginx on my origin with an access log like so:
When I curl http://my-domain.com, I see an access log of the form "my-domain.com" "http" logged at my origin. When I curl https://my-domain.com, I see "my-domain.com" "https" logged at my origin. Are you able to perform a similar test?
I presume the default value of X-Forwarded-Proto (reflecting the connection protocol between the eyeball and Cloudflare) should suffice for your use case, but I understand the need to override the Host header. The best I can offer here might be to point you to the Resolve Override feature:
However, Resolve Override can only resolve to orange-cloud records on your zone, so using this would require you to make an A record for each IP you want to load balance across, which might not be feasible.
I hear you, and we’re working on both making the preview service more accurate and documenting all of these restrictions.
No. I know that we return 500, 502, 504, and 520-526, inclusive, but I’m unaware of an exhaustive list in our documentation. If you need to sanitize the error pages, I’d recommend unconditionally replacing the bodies of 5xx responses with your own text.
Thanks for pointing this out; I was mistaken: X-Forwarded-Proto takes its value from the URL passed to fetch() (except same-zone subrequests with a Flexible SSL setting, which always take the value “http”, it seems).