Fail & log requests where ASP.NET model binding failed #22
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
With this PR, model binding failures on any controller method will cause the resulting request handling to be immediately terminated with a
400 Bad Request
, and the failure details to be logged to stdout and sentry:epic rant time
It is rant time!
So I've spent a significant chunk of the last two days trying to figure out why the staging deployment of this service was failing in a completely incomprehensible way. Specifically, the way it was failing was as follows:
For a given beatmap upload, there are two requests. The first assigns the new beatmap IDs by creating a couple of database rows, and the second is supposed to accept a beatmap package, parse it, and populate everything that is required and also notify everything that a new beatmap (or an update thereof) has arrived.
On staging, the first request would succeed no problem, and create the rows (verifiably so, which was checked several times manually via mysql), but the second request would return a 404. The only way it could feasibly return a 404 is if the first request never happened.
The 404 was not caused by anything of the following:
Some ad-hoc logging nailed this down to an apparent ASP.NET model binding failure. "Model binding" refers to the magic glue that is supposed to let you controller actions like plain methods with a bunch of annotations and get all of the data from various sources (route, form, query string, what have you) into nice C# objects for perusal. It turned out that in this particular case the ID of the beatmap in the second request was ZERO, even though the request path (i.e. the place it was supposed to come from) was 100% correct.
Some more ad-hoc logging produced the following absolute stinker:
At this point I am restraining myself from saying some very unkind words. What is happening here is that file uploads will use
/tmp
for large enough files:(source: https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-9.0#file-upload-scenarios)
Secondly, on staging the docker container was deployed with the filesystem set to readonly, as per OWASP recommendations (https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html#rule-8-set-filesystem-and-volumes-to-read-only). Thus:
ASPNETCORE_TEMP
was not explicitly set, ASP.NET internals attempted to buffer the file to/tmp
./tmp
was not writable.As per
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-9.0#example, it is apparently the consumer's sole responsibility to check that model binding succeeded and inspect any resulting errors:
which is why this commit has to exist to avoid any such further stupidity happening ever again. Hopefully.