HttpRequest: inconsistent Scheme & Host properties

1/30/2020

My ASP.NET Core 3.1 application runs in Kubernetes. The ingress (load balancer) terminates SSL and talks to the pod over plain HTTP.

Let's say the ingress is reachable at https://my-app.com:443, and talks to the pod (where my app is running) at http://10.0.0.1:80.

When handling a request, the middleware pipeline sees an HttpRequest object with the following:

  • Scheme == "http"
  • Host.Value == "my-app.com"
  • IsHttps == False

This is weird:

  • Judging by the Scheme (and IsHttps), it seems the HttpRequest object describes the forwarded request from the ingress to the pod, the one that goes over plain HTTP. However, why isn't Host.Value equal to 10.0.0.1 in that case?
  • Or vice versa: if HttpRequest is trying to be clever and represent the original request, the one to the ingress, why doesn't it show "https" along with "my-app.com"?

At no point in handling the request is there a request coming to http://my-app.com. It's either https://my-app.com or http://10.0.0.1. The combination is inconsistent.

Other details

Digging deeper, the HttpRequest object has the following headers (among others) that show the reverse proxying in action:

Host: my-app.com
Referer: https://my-app.com/swagger/index.html
X-Real-IP: 10.0.0.1
X-Forwarded-For: 10.0.0.1
X-Forwarded-Host: my-app.com
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Scheme: https

I'm guessing HttpRequest is using these to get hold of the original host (my-app.com rather than 10.0.0.1) but it doesn't do the same for the original scheme (https rather than http).

Q1: Is this expected, and if so, what is the rationale?

Q2: What's the best way to get at the original URL (https://my-app.com)? The best I've found so far was to check if the X-Scheme and X-Forwarded-Host headers were present (by inspecting HttpRequest.Headers) and if so, using those. However, it's a little weird having to go to the raw HTTP headers in the middleware pipeline.

-- sferencik
asp.net-core
http
kubernetes-ingress

1 Answer

2/19/2020

Q1: Is this expected, and if so, what is the rationale?

I would say yes, it's the expected behavior.

The 'Host.Value=my-app.com' of HttpRequest object is reflecting the request header field originated by client (web browser, curl, ...), e.g.:

curl --insecure -H 'Host: my-app.com' https://<FRONTEND_FOR_INGRESS-MOST_OFTEN_LB-IP>

which is set*[1] in location block for that server 'my-app.com' inside generated nginx.conf file:

                    ...
                    set $best_http_host                     $http_host;
                    # [1] - Set Host value 
                    proxy_set_header Host                   $best_http_host;
                    ...
                    # Pass the extracted client certificate to the backend
                    ...
                    proxy_set_header                        Connection        $connection_upgrade;             
                    proxy_set_header X-Request-ID           $req_id;
                    proxy_set_header X-Real-IP              $remote_addr;
                    proxy_set_header X-Forwarded-For        $remote_addr;
                    proxy_set_header X-Forwarded-Host       $best_http_host;
                    ...  

whereas 'http_host' variable is created based on following "ngx_http_core_module" core functionality:

$http_name - arbitrary request header field; the last part of a variable name is the field name converted to lower case with dashes replaced by underscores

So described behavior is not anyhow unique to ASP.NET Core 3.0, you see unknown Dictionary containing key/value pairs of custom headers set explicitly by nginx controller, accordingly to nginx ingress current configuration, that's it.

You can inspect current nginx controller's configuration by your self with following command:

kubectl exec -it po/<nginx-ingress-controller-pod-name> -n <ingress-controller-namespace> -- cat /etc/nginx/nginx.conf

Q2: What's the best way to get at the original URL (https://my-app.com)?

I would try with constructing it using Configuration snippet, this is by introducing another custom header, where you concatenate values.

-- Nepomucen
Source: StackOverflow