Skip to content

Commit

Permalink
feat: Support for X-Original-Method used by nginx ingress controller (
Browse files Browse the repository at this point in the history
  • Loading branch information
dadrus authored Jun 23, 2023
1 parent 82c869b commit d95b989
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 28 deletions.
15 changes: 13 additions & 2 deletions charts/heimdall/templates/demo/ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,19 @@ metadata:
{{- include "heimdall.demo.labels" . | nindent 4 }}
{{- if eq .Values.operationMode "decision" }}
annotations:
{{ .Values.demo.forwardAuthMiddlewareAnnotation }}: "http://{{ include "heimdall.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.decision.port }}{{ .Values.demo.forwardAuthMiddlewareRequestUri }}"
{{ .Values.demo.forwardAuthMiddlewareResponseAnnotation }}: Authorization
nginx.ingress.kubernetes.io/configuration-snippet: |
auth_request /_auth;
auth_request_set $authHeader0 $upstream_http_authorization;
proxy_set_header 'Authorization' $authHeader0;
nginx.ingress.kubernetes.io/server-snippet: |
location = /_auth {
internal;
proxy_method $request_method;
proxy_pass "http://{{ include "heimdall.fullname" . }}.{{ .Release.Namespace }}.svc.cluster.local:{{ .Values.service.decision.port }}{{ .Values.demo.forwardAuthMiddlewareRequestUri }}";
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header Host $http_host;
}
{{- end }}
spec:
rules:
Expand Down
2 changes: 1 addition & 1 deletion charts/heimdall/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ demo:
# change the values to what is required for your ingress controller
forwardAuthMiddlewareAnnotation: nginx.ingress.kubernetes.io/auth-url
forwardAuthMiddlewareResponseAnnotation: nginx.ingress.kubernetes.io/auth-response-headers
forwardAuthMiddlewareRequestUri: /$request_uri
forwardAuthMiddlewareRequestUri: $request_uri

# Default values for heimdall.
image:
Expand Down
58 changes: 34 additions & 24 deletions docs/content/docs/guides/nginx.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@ In most cases you must configure heimdall to trust your NGINX instances by setti

With this method you set the `X-Forwarded-Host` to let heimdall know the host, respectively domain the request was sent to. All other URL parts (schema, path and query parameter) as well as the HTTP method are then inferred from the URL and the request heimdall receives.

Instead of using `X-Forwarded-Host` you could also make use of the `Host` header. In that case, there is no need to configure the `trusted_proxies`.

.Possible Configuration
====
[source, nginx]
Expand All @@ -46,21 +44,25 @@ location / {
location = /_auth { <4>
internal;
proxy_pass http://heimdall:4456$request_uri; <5>
proxy_pass_request_body off; <6>
proxy_method $request_method; <5>
proxy_pass http://heimdall:4456$request_uri; <6>
proxy_pass_request_body off; <7>
proxy_set_header Content-Length "";
proxy_set_header X-Forwarded-Host $http_host; <7>
proxy_set_header X-Forwarded-For $remote_addr; <8>
proxy_set_header X-Forwarded-Host $http_host; <8>
proxy_set_header X-Forwarded-For $remote_addr; <9>
}
----
<1> Configures NGINX to forward every request to the internal `/_auth` endpoint (this is where the actual heimdall integration happens - see below).
<2> When the response from heimdall returns, this and the next line set the Cookies set by heimdall in the response (whether you need this depends on your link:{{< relref "/docs/configuration/rules/pipeline_mechanisms/contextualizers.adoc" >}}[Contextualizers] and link:{{< relref "/docs/configuration/rules/pipeline_mechanisms/unifiers.adoc" >}}[Unifiers] configuration)
<3> When the response from heimdall returns, this and the next line set the `Authorization` header set by heimdall in the response (which header to set depends again on your link:{{< relref "/docs/configuration/rules/pipeline_mechanisms/contextualizers.adoc" >}}[Contextualizers] and link:{{< relref "/docs/configuration/rules/pipeline_mechanisms/unifiers.adoc" >}}[Unifiers] configuration)
<4> This is where the "magic" happens
<5> Configures NGINX to pass the request to heimdall and sets the request path and queries from the original request
<6> Disables sending of the request body. If your heimdall rules make use of the body, you should set this to `on` and remove the next line.
<7> This is where you forward the host information to heimdall
<8> Not really required, but makes the remote address available to heimdall and thus to the mechanisms used in by the rules. Requires `trusted_proxies` to be configured.
<5> Configure NGINX to use the HTTP method used by its client. Without this setting the implementation of `proxy_path` will use the HTTP GET method.
<6> Configures NGINX to pass the request to heimdall and sets the request path and queries from the original request
<7> Disables sending of the request body. If your heimdall rules make use of the body, you should set this to `on` and remove the next line.
<8> This is where you forward the host information to heimdall
<9> Not really required, but makes the remote address available to heimdall and thus to the mechanisms used in by the rules. Requires `trusted_proxies` to be configured.
NOTE: Instead of using `X-Forwarded-Host` you could also make use of the `Host` header. In that case, there is no need to configure the `trusted_proxies`. The `X-Forwarded-For` will then however be ignored as considered not trusted.
====

[#_second_option]
Expand All @@ -70,6 +72,8 @@ With this method you set the `X-Forwarded-Method`, `X-Forwarded-Proto`, `X-Forwa

Compared to the link:{{< relref "#_first_option" >}}[previous integration] option, the configuration only differs in the definition of the internal `/_auth` endpoint. So, the example configuration is limited to that part only.

NOTE: Proper configuration of `trusted_proxies` is mandatory if using this option.

.Possible Configuration
====
[source, nginx]
Expand Down Expand Up @@ -101,6 +105,8 @@ location = /_auth {

This method is a simplified alternative to the link:{{< relref "#_second_option" >}}[previous] one in which heimdall receives everything required to know the host url and the HTTP method in HTTP headers.

NOTE: Proper configuration of `trusted_proxies` is mandatory if using this option.

The difference is again in the definition of the internal `/_auth` endpoint. So, the example configuration is limited to that part.

.Possible Configuration
Expand All @@ -127,7 +133,9 @@ location = /_auth {

== Integration with NGINX Ingress Controller.

The integration option, described in the link:{{< relref "#_second_option" >}}[Forward all information in `X-Forwarded-*` headers] section corresponds more or less to the way how the `ngnix.conf` file is generated by the https://github.com/kubernetes/ingress-nginx/blob/3c8817f700a4ab1713e3369fc6e5f500b008d989/rootfs/etc/nginx/template/nginx.tmpl#L977[default nginx-ingress template] used by the https://kubernetes.github.io/ingress-nginx/[NGINX Ingress Controller]. The only missing parts are the request path and the query parameter. So you can integrate heimdall by adding the following annotations to your ingress configuration.
The integration option, described in the link:{{< relref "#_second_option" >}}[Forward all information in `X-Forwarded-*` headers] section corresponds more or less to the way how the `ngnix.conf` file is generated by the https://github.com/kubernetes/ingress-nginx/blob/3c8817f700a4ab1713e3369fc6e5f500b008d989/rootfs/etc/nginx/template/nginx.tmpl#L977[default nginx-ingress template] used by the https://kubernetes.github.io/ingress-nginx/[NGINX Ingress Controller]. Instead of making use of `X-Forwarded-Method`, the nginx ingress controller sets the `X-Original-Method` header which is also supported by heimdall. The only missing parts are the request path and the query parameter. So you can integrate heimdall by adding the annotations as shown in the example below to your ingress configuration.

NOTE: The configuration used in the example below requires proper configuration of `trusted_proxies`. Otherwise heimdall will use the HTTP method, used by the nginx ingress controller while communicating with heimdall (which is HTTP GET) instead of the value sent in the `X-Original-Method`.

.Possible Configuration
====
Expand Down Expand Up @@ -162,29 +170,31 @@ This example makes use of the very link:{{< relref "#_first_option" >}}[first] i
[source, yaml]
----
nginx.ingress.kubernetes.io/auth-response-headers: Authorization <1>
nginx.ingress.kubernetes.io/configuration-snippet: "auth_request /_auth;" <2>
nginx.ingress.kubernetes.io/configuration-snippet: |
auth_request /_auth; <1>
auth_request_set $authHeader0 $upstream_http_authorization; <2>
proxy_set_header 'Authorization' $authHeader0;
nginx.ingress.kubernetes.io/server-snippet: |
location = /_auth { <3>
internal;
proxy_pass http://<heimdall service name>.<namespace>.svc.cluster.local:<decision port>$request_uri; <4>
proxy_pass_request_body off; <5>
proxy_method $request_method; <4>
proxy_pass http://<heimdall service name>.<namespace>.svc.cluster.local:<decision port>$request_uri; <5>
proxy_pass_request_body off; <6>
proxy_set_header Content-Length "";
proxy_set_header X-Forwarded-Host $http_host; <6>
proxy_set_header X-Forwarded-For $remote_addr; <7>
proxy_set_header Host $http_host; <7>
}
# other annotations required
----
<1> Let NGINX forward the `Authorization` header set by heimdall to the upstream service. This configuration depends on
<1> Configures NGINX ingress controller to make use of an external auth service and pass incoming request to `/_auth` route.
<2> Let NGINX forward the `Authorization` header set by heimdall to the upstream service. This configuration depends on
your link:{{< relref "/docs/configuration/rules/pipeline_mechanisms/contextualizers.adoc" >}}[Contextualizers] and link:{{< relref "/docs/configuration/rules/pipeline_mechanisms/unifiers.adoc" >}}[Unifiers] configuration
<2> Configures NGINX ingress controller to make use of an external auth service and pass incoming request to `/_auth` route.
<3> The implementation of the _auth route.
<4> Forwards the request to heimdall and sets the request path and queries from the original request (no slash this time)
<5> Disables sending of the request body. If your heimdall rules make use of the body, you should set this to `on` and remove the next line.
<6> This is where you forward the host information to heimdall
<7> Not really required, but makes the remote address available to heimdall and thus to the mechanisms used in by the rules.
<4> Configures NGINX to use the HTTP method used by its client. Without this setting the implementation of `proxy_path` will use the HTTP GET method.
<5> Forwards the request to heimdall and sets the request path and queries from the original request (no slash this time)
<6> Disables sending of the request body. If your heimdall rules make use of the body, you should set this to `on` and remove the next line.
<7> This is where you forward the host information to heimdall
NOTE: The above configuration requires `trusted_proxies` to be configured, otherwise heimdall will not make use of the `X-Forwarded-Host` and `X-Forwarded-For` headers. If you don't want to set the `trusted_proxies`, you can make use of the `Host` header instead of the `X-Forwarded-Host`, as also mentioned link:{{< relref "#_first_option" >}}[here].
NOTE: If you rely on the domain, the request came from in your rules, you should rather make use of the `X-Forwarded-Host` header in lieu of the `Host` header and configure the `trusted_proxies` property.
====

Checkout the Kubernetes quickstarts on https://github.com/dadrus/heimdall/tree/main/examples/kubernetes/quickstarts[GitHub] for a working demo.
5 changes: 4 additions & 1 deletion internal/fiber/middleware/xfmphu/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ const (
xForwardedMethod = "X-Forwarded-Method"
xForwardedURI = "X-Forwarded-Uri"
xForwardedPath = "X-Forwarded-Path"
xSentFrom = "X-Sent-From"

// the following header are nginx specific.

xSentFrom = "X-Sent-From"
xOriginalMethod = "X-Original-Method"
nginxIngressAgent = "nginx-ingress-controller"
)
38 changes: 38 additions & 0 deletions internal/fiber/middleware/xfmphu/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,25 @@ func TestMiddlewareApplicationWithoutConfiguredTrustedProxy(t *testing.T) {
assert.Equal(t, url.Values{"foo": []string{"bar"}}, extractedURL.Query())
},
},
{
uc: "X-Original-Method set",
configureRequest: func(t *testing.T, req *http.Request) {
t.Helper()

req.Header.Set(xOriginalMethod, "POST")
req.URL.RawQuery = url.Values{"foo": []string{"bar"}}.Encode()
},
assert: func(t *testing.T) {
t.Helper()

require.True(t, testAppCalled)
assert.Equal(t, "GET", extractedMethod)
assert.Equal(t, "http", extractedURL.Scheme)
assert.Equal(t, "heimdall.test.local", extractedURL.Host)
assert.Equal(t, "/test", extractedURL.Path)
assert.Equal(t, url.Values{"foo": []string{"bar"}}, extractedURL.Query())
},
},
{
uc: "X-Forwarded-Proto set",
configureRequest: func(t *testing.T, req *http.Request) {
Expand Down Expand Up @@ -285,6 +304,25 @@ func TestMiddlewareApplicationWithConfiguredTrustedProxy(t *testing.T) {
assert.Equal(t, url.Values{"foo": []string{"bar"}}, extractedURL.Query())
},
},
{
uc: "X-Original-Method set",
configureRequest: func(t *testing.T, req *http.Request) {
t.Helper()

req.Header.Set(xOriginalMethod, "POST")
req.URL.RawQuery = url.Values{"foo": []string{"bar"}}.Encode()
},
assert: func(t *testing.T) {
t.Helper()

require.True(t, testAppCalled)
assert.Equal(t, "POST", extractedMethod)
assert.Equal(t, "http", extractedURL.Scheme)
assert.Equal(t, "heimdall.test.local", extractedURL.Host)
assert.Equal(t, "/test", extractedURL.Path)
assert.Equal(t, url.Values{"foo": []string{"bar"}}, extractedURL.Query())
},
},
{
uc: "X-Forwarded-Proto set",
configureRequest: func(t *testing.T, req *http.Request) {
Expand Down
6 changes: 6 additions & 0 deletions internal/fiber/middleware/xfmphu/request_method.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ func requestMethod(c *fiber.Ctx) string {
if len(forwardedMethodVal) != 0 {
return forwardedMethodVal
}

// used by nginx ingress controller
forwardedMethodVal = c.Get(xOriginalMethod)
if len(forwardedMethodVal) != 0 {
return forwardedMethodVal
}
}

return c.Method()
Expand Down

0 comments on commit d95b989

Please sign in to comment.