Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix preflight failure on unknown router paths #11376

Open
wants to merge 8 commits into
base: 4.7.x
Choose a base branch
from

Conversation

ThomasIommi
Copy link

Hi!

I've tried to propose a fix for this issue #11375 and to update tests.
Let me know if there are any suggestions or feedback. I'm open to any improvements or discussions.

Thanks!

@CLAassistant
Copy link

CLAassistant commented Nov 23, 2024

CLA assistant check
All committers have signed the CLA.

@ThomasIommi ThomasIommi force-pushed the fix-preflight-failure-on-unknown-router-paths branch from a3f933d to 7bb04d7 Compare November 23, 2024 19:03
@glorrian
Copy link
Contributor

what the motivation for this pr? i think server shouldn't response 200 on non existing route

@ThomasIommi
Copy link
Author

ThomasIommi commented Nov 24, 2024

Hi!

In the current implementation, OPTIONS requests are being blocked with 403 Forbidden errors, even when the routes do exist but are not explicitly known by the router. This occurs, for example, in scenarios where the server acts as a proxy using a @Filter, as outlined in the official documentation ((https://docs.micronaut.io/latest/guide/#proxyClient)).

I believe (though I might be mistaken) that it’s not the responsibility of the CorsFilter to block such requests at this stage. The original CORS request should be blocked if the server fails to handle it, not the preflight OPTIONS request.

I encountered this issue after upgrading from Micronaut 4.6.X to 4.7.X and traced it back to this PR: #11097.

Let me know if you disagree. If there’s another approach to account for routes handled by @Filters, I’d be happy to adjust this PR accordingly.

@glorrian
Copy link
Contributor

I am confused by the tests that you deleted "A preflight request is rejected for a non-existing route" and "A preflight request is rejected for a route that does exist but doesn't handle the requested HTTP Method" because they must pass. if you fix some functionality, you should at least leave all previous tests valid, but it's better to add new ones

@ThomasIommi
Copy link
Author

Have you reviewed the PR I mentioned (#11097)?

I believe it introduced a regression. Additionally, there were changes to the tests in the PR itself, and I don't think I can make those tests pass since I'm essentially saying they don't represent the correct behavior.

I'm specifically referring to parts like this one:

image

Sorry for the screenshot I couldn't figure out how to link to this diff 😅.

@glorrian
Copy link
Contributor

yeah, thanks to the link on pr, i got you.

@@ -113,7 +112,7 @@ class CorsVersionSpec extends Specification {

when:
request = HttpRequest.OPTIONS("/new-not-allowed-from-private")
preflightHeaders(null, true).each { k, v -> request.header(k, v)}
preflightHeaders("x-api-version", true).each { k, v -> request.header(k, v)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what the reason of "x-api-version" here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While debugging, I discovered that without the "Access-Control-Request-Headers" header, the test failed to target the correct TestController endpoint (annotated with @CrossOrigin(allowPrivateNetwork = false) and @Version("2")).

Before the proposed changes, I believe the test passed for the wrong reason — the 403 FORBIDDEN response occurred because the route was not found, not due to the @CrossOrigin(allowPrivateNetwork = false) configuration.

I think the issue is related to the @Version("2") annotation since temporarily removing this annotation resolved the problem, but I then decided not to remove the annotation and add the header instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ive seen to the code. this behaviour happens by the reason that matchesOrigin method at CorsFilter returns false if we defined Version annotation but request will not support this header cause accessControlRequestHeaders is null and then it responce 200 cause all cors requests return 200 by this pr. so i guess it test right, thanks

@@ -494,20 +494,15 @@ private MutableHttpResponse<?> handlePreflightRequest(@NonNull HttpRequest<?> re

@Nullable
private boolean validatePreflightRequest(@NonNull HttpRequest<?> request,
@NonNull CorsOriginConfiguration config) {
@NonNull CorsOriginConfiguration config) {
Optional<HttpMethod> methodToMatchOptional = validateMethodToMatch(request, config);
if (methodToMatchOptional.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (validateMethodToMatch(request, config).isEmpty() ||
            !CorsUtil.isPreflightRequest(request) ||
            !hasAllowedHeaders(request, config)) {
            return false;
        }


@Property(name = "micronaut.server.cors.configurations.foo.allowed-origins", value = "http://www.foo.com")
@Property(name = "micronaut.server.cors.configurations.foo.exposed-headers", value = "Foo-Header,Bar-Header")
void "A preflight request is rejected for a route that does exist but doesn't handle the requested HTTP Method"() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need this feature.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could handle it as a special case: if the route exists, we perform an additional check on the HTTP methods it supports. However, if the route is not found, we make no assumptions. Honestly, I find this approach a bit trickier. For me, it’s simpler to treat it as if the route it is not known / found.

@yawkat
Copy link
Member

yawkat commented Dec 3, 2024

I'm not sure I like this change. It is not against spec from what I can tell, but it does weaken the filtering we have.

A simple workaround would be to make your filter pre-routing as well, since you clearly don't need route information.

Other options for this PR would be to either add a config option to disable CORS preflight handling for some paths, or a config option to disable the route requirement entirely like you do in this PR.

@dstepanov
Copy link
Contributor

I think we should extract the is route exists logic to some interface and allow the addition of an extra checker.

@ThomasIommi
Copy link
Author

ThomasIommi commented Dec 3, 2024

@yawkat
Doesn't the workaround you propose still invalidate the use of @Filter to implement a gateway proxy?

In a similar scenario to this use case (https://docs.micronaut.io/latest/guide/#proxyClient), if I move the proxy filter before the CORS filter and find a way to bypass it, I believe I would then need to handle CORS in every backend behind the gateway. That doesn’t seem ideal. I hope this makes sense, and that I'm not overlooking something.

I still think the responsibility of the CORS filter for preflight requests is to simply validate the CORS configuration, not the routes. Of course, I could be wrong, but I thought we were on the same page based on the comments in the PR I'm trying to address; did I misunderstand, or has something changed your perspective?

Just curious! Maybe I'm missing some background. 😛

@ThomasIommi
Copy link
Author

I like the idea of using a configuration to enforce or disable route requirements. I can try to update the code accordingly as soon as possible.

@dstepanov
Copy link
Contributor

I would not disable validation; I would add a new check to validate the route's validity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Ready for Review
Development

Successfully merging this pull request may close these issues.

5 participants