dadrus / heimdall

A cloud native Identity Aware Proxy and Access Control Decision service

Home Page:https://dadrus.github.io/heimdall/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Heimdall "silently" responses with an error if the to be processed request exceeds the max buffer size

dadrus opened this issue · comments

Preflight checklist

  • I agree to follow this project's Code of Conduct.
  • I have read and am following this repository's Contribution Guidelines."
  • I could not find a solution in the existing issues, docs, nor discussions.

Describe the bug

This is actually not really a bug, but an annoying observability shortcoming.

If the request to heimdall (e.g. if heimdall is used as a proxy or a decision service) exceeds 4kB, which is the default for both, the request and the response buffers, heimdall responses with 431 without emitting any log statements.
Depending on the usage scenario, it is difficult to understand why the call to an upstream failed. Typically, there is a need to increase the log level of the proxy in front of heimdall to understand what went wrong.

How can the bug be reproduced

  1. Start any of the demo setups with docker compose available under https://github.com/dadrus/heimdall/tree/main/examples/docker-compose/quickstarts
  2. Instead of following the curl example requests craft a request with a header, e.g. X-Too-Big set to a value 4kB of size.
  3. You'll see 431 as a response, but there won't be a log statement at any log level and there won't be any trace entry for that call well.

Relevant configuration

To overcome that issue, there is a need to increase the buffer limits, e.g. like shown below.

serve:
  decision: # or proxy
    buffer_limit:
      read: 100KB
      write: 100KB

Version

0.11.1-alpha

On which operating system are you observing this issue?

Linux

In which environment are you deploying?

Other

Additional Context

Affects any type of deployment

There is unfortunately no hook, which could be set to log a 431 error. And there are more like this. The responsible HTTP server implementation includes the following lines of code:

for {
	w, err := c.readRequest(ctx)
	if c.r.remain != c.server.initialReadLimitSize() {
		// If we read any bytes off the wire, we're active.
		c.setState(c.rwc, StateActive, runHooks)
	}
	if err != nil {
		const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"

		switch {
		case err == errTooLarge:
			// Their HTTP client may or may not be
			// able to read this if we're
			// responding to them and hanging up
			// while they're still writing their
			// request. Undefined behavior.
			const publicErr = "431 Request Header Fields Too Large"
			fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
			c.closeWriteAndWait()
			return

		case isUnsupportedTEError(err):
			// Respond as per RFC 7230 Section 3.3.1 which says,
			//      A server that receives a request message with a
			//      transfer coding it does not understand SHOULD
			//      respond with 501 (Unimplemented).
			code := StatusNotImplemented

			// We purposefully aren't echoing back the transfer-encoding's value,
			// so as to mitigate the risk of cross side scripting by an attacker.
			fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
			return

		case isCommonNetReadError(err):
			return // don't reply
			default:
			if v, ok := err.(statusError); ok {
				fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s: %s%s%d %s: %s", v.code, StatusText(v.code), v.text, errorHeaders, v.code, StatusText(v.code), v.text)
				return
			}
			publicErr := "400 Bad Request"
			fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
			return
		}
	}

// further code

For each of the errors handled in the switch statement, there is no way to have it logged by the implementation making use of that code

There is a corresponding FR to allow logging in such cases. As long as there is no solution for this, there is no way to resolve the corresponding issue with heimdall.