What headers should be used when combining URI-rewrites and HATEAOS? - reverse-proxy

Let's say I have a Foo service that accepts requests like:
http://foo-service/bar/baz
...and returns HATEOAS-style responses:
{
"self": "http://foo-service/bar/baz"
}
(Yes it should be links, href, etc - I'm simplifying for this question).
Now suppose I want to put that behind a reverse-proxy that also rewrites URI paths:
http://router/foo/bar/baz
(Here, I'm detecting the path starts with /foo/..., and so the reverse-proxy knows what service to route to. I would expect the self link to be "http://router/foo/bar/baz", even though the reverse-proxy actually made a request to http://foo-service/bar/baz).
I know about the Host: and X-Forwarded-Host: headers for specifying what the original request host was.
What is the correct header (or more generally, what is the correct way) for specifying the original path?

Finally found a possible answer: it seems to be X-Forwarded-Prefix.
eg: HATEOAS paths are invalid when using an API Gateway in a Spring Boot app

Related

How to pass original URI, with arguments, to Traefik ErrorPage handler specified in `query`?

I'm trying to use nginx to serve a custom error page using the Error Page middleware so that 404 requests to a lambda service (which I don't control) can be handled with a custom error page. I want to be able to get the context of this original request on that error page, either in Nginx for further forwarding, or else as a header for further handling e.g. in PHP or whatnot so I can provide contextual links on the 404 page.
However, right now after the redirection to Nginx in Traefik's ErrorPage middleware it seems the request has lost all the headers and data from the original service query.
The relevant part of my dockerfile:
traefik.port=8080
traefik.protocol=http
traefik.docker.network=proxy
traefik.frontend.rule=PathPrefix:/myservice;ReplacePathRegex:^/myservice/(.*) /newprefix/$$1
traefik.frontend.errors.myservice.status=404
traefik.frontend.errors.myservice.service=nginx
traefik.frontend.errors.myservice.query=/myservice-{status}
Nginx receives the forwarded 404 request, but the request URI comes through as nothing more than the path /myservice-404 specified in query (or /, if I omit traefik.frontend.errors.myservice.query). After the ReplacePathRegex I have the path of the original request available in the HTTP_X_REPLACED_PATH header, but any query arguments are no longer accessible in any header, and nginx can't see anything else about the original URI. For example, if I requested mysite.com/myservice/some/subpath?with=parameters, the HTTP_X_REPLACED_PATH header will show /myservice/some/subpath but not include the parameters.
Is it possible in Traefik to pass another service the complete context about the original request?
What I'm really looking for is something like try_files, where I could say "if this traefik request fails, try this other path instead", but I'd settle for being able to access the original, full request arguments within the handling backend server. If there was a way to send Nginx a request with the full path and query received by Traefik, that would be ideal.
tl;dr:
I am routing a request to a specific service in Traefik
If that request 404s, I want to be able to pass that request to Nginx for further processing / a contextual error page
I want Nginx and/or the page which receives the ErrorPage redirect to be able to know about the request that 404'd in the service
Unfortunately this is not possible with Traefik. I tried to achieve something similar but I realized that the only information that we are able to pass to the error page is the HTTP code, that's it.
The only options available are mentioned in their docs: https://doc.traefik.io/traefik/middlewares/errorpages/

Ambassador Mapping Rewrite Needs Original URL In Header

I'm using Ambassador Mappings created through a Helm chart in order to rewrite and forward a web request. The client has requested that we include the originally requested URL in a header when we forward the request.
Based on documentation, it looks like there are only two dynamic values I can set: %DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% and %PROTOCOL%, neither of which seems to solve my issue.
Are there any ways for me to set this header properly through an Ambassador mapping?
Thanks!
So I think we've found the solution. If you use Envoy's syntax and make sure that is forwarded through, you can use their REQ() function, to get the authority and path, to construct the URL. For example:
http://%REQ(:authority)%%REQ(:path)%
Here is the documentation for all of the possibilities you can use with Envoy through Ambassador's mapping: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#custom-request-response-headers

Routing requests using cloudflare to different web applications

I currently have two web apps that are set up in cloudflare with the following CNAMEs. Both are keystonejs applications.
app1.example.com ===pointing to ===> AWS ALB 1
app2.example.com ===pointing to ===> AWS ALB 2
I have Cloudflare Enterprise set up, so i'm able to use the "Render Override" feature in my page rules. I have 2 page rules set up using the following:
www.example.com ===render override ===> app1.example.com
www.example.com/app2/* ===render override ===> app2.example.com
Now in order to access the keystonejs application on app2.example.com. The application is called using app2.example.com/pa
The problem that i'm facing is that render override doesnt allow me to use sub paths, and i do not want to use the forwarding rule. Do i need to make my keystone application accessible through the root url, namely app2.example.com/ ? or is there another way to do this? Otherwise, would i need to use a reverse proxy? such as nginx ?
Thanks
Note: Since you are an enterprise customer, I highly recommend contacting your Customer Success Manager and/or Solutions Engineer at Cloudflare. They are there to help with exactly these kinds of questions. That said, I'll answer the question here for the benefit of self-serve customers.
I think when you say "Render Override" you actually mean "Resolve Override". This setting changes the DNS lookup for the request such that it is routed to a different origin IP address than it would be normally.
Note that Resolve Override does not rewrite the request in any way; it only routes it to a different server. So, a request to www.example.com/app2/foo will go to the server app2.example.com, but the path will still be /app2/foo (not /foo), and the Host header will still be Host: www.example.com.
It sounds like in your case you really want /app2/* to be rewritten to /pa/*, in addition to redirecting to a different origin. You can accomplish this using Cloudflare Workers, which lets you execute arbitrary JavaScript on Cloudflare's edge. Here's what the script might look like:
addEventListener("fetch", event => {
event.respondWith(handle(event.request));
});
async function handle(request) {
let url = new URL(request.url) // parse the URL
if (url.pathname.startsWith("/app2/")) {
// Override the target hostname.
url.host = "app2.example.com"
// Replace /app2/ with /pb/ in the path.
url.pathname = "/pb/" + url.pathname.slice("/app2/".length)
// Send the request on to origin.
return fetch(url, request)
} else {
// Just override the hostname.
url.host = "app1.example.com"
// Send the request on to origin.
return fetch(url, request)
}
}
With this deployed, you can remove your Resolve Override page rules, as they are now covered by the Worker script.
Note that the above script actually does rewrite the Host header in addition to the path. If you want the Host header to stay as www.example.com, then you will need to use the cf.resolveOverride option. This is only available to enterprise customers; ask your CSM or SE if you need help using it. But, for most cases, you actually want the Host header to be rewritten, so you probably don't need this.

Rewriting the response header in traefik

I have a kubernetes cluster and I am using traefik ingress controller to route traffic to deployments inside the kubenetes cluster.
I am able to use ingress.kubernetes.io/rewrite-target annotation to change the incoming request path to the one expected by the backend.
For example : /star is transformed to /trek by the rewrite target annotation and the request gets routed to /trek and backend processing is successful.
What I want to know is if there is a way to change response header so that /trek gets changed back to /star?
Did you get an answer to this? It looks like similar functionality is available in Apache Traffic Server: https://docs.trafficserver.apache.org/en/latest/admin-guide/plugins/header_rewrite.en.html, but would be good to have it in traefik
The functionality that does this is modifiers and specifically ReplacePath. Here is a similar answer with some examples.

Standard header for public address with an API Gateway

Let's say you are using an API gateway like Apigee or Amazon API Gateway and your public address for your API is http://my.public.dns.com/path/v1/articles. This gets routed through your gateway to an internal host http://some.internal.host.com/v1/articles. Now if your internal API returns relative links to itself then when they get served to the client they will be incorrect as they are based on its path not the actual public path. I know I can resolve this by transforming the response using the tools available in the respective gateway.
The question I have is; is there a standard way for a gateway to communicate the public path to the downstream component? I was thinking there might be a similar HTTP header to X-Forwarded-For.
There is no standard similar header.
Note that I think you mean if the back-end returns absolute links. If the links were relative, they'd already be correct. Relative links use no leading / thus are "relative" to the current directory -- whatever it may be - and so any external prefix is retained by the browser and the links transparently remain valid. Relative links can also backtrack a directory level with a ../ path prefix for each level.
Note also that API Gateway doesn't require any path prefix if you're using a custom domain name for your API This necessarily limits you to deploying a single "stage," but that's a reasonable tradeoff for a more flexible path... so the easiest solution might be to use a path in your API matching those internal paths.