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

WebSockets Next: initial version of security integration #40534

Merged
merged 2 commits into from
May 15, 2024

Conversation

mkouba
Copy link
Contributor

@mkouba mkouba commented May 9, 2024

  • also create a new Vertx duplicated context for error handler invocation

This is the initial POC for security integration. When quarkus-security is present and quarkus.http.auth.proactive=false, then we force the authentication before the HTTP upgrade so that it's possible to capture the SecurityIdentity and set it afterwards for all endpoint callbacks.

CC @sberyozkin @michalvavrik

@sberyozkin
Copy link
Member

Thanks @mkouba It looks perfect, though Michal may have some more suggestions.

Let me check how the identity expiry time can be provided

@mkouba
Copy link
Contributor Author

mkouba commented May 9, 2024

Let me check how the identity expiry time can be provided

@sberyozkin Pls create a separate issue for this topic.

@sberyozkin
Copy link
Member

sberyozkin commented May 9, 2024

@mkouba Sorry, sure, let me move the discussions related to the expired identity to another issue, see #40536

@sberyozkin
Copy link
Member

@mkouba @michalvavrik I can support this PR with the Docs update once the code is ready to go

Copy link
Contributor

@michalvavrik michalvavrik left a comment

Choose a reason for hiding this comment

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

@mkouba , looking forward for some more tests and docs, but IMO code is alright. maybe you want to gather opinions from others like @stuartwdouglas (if he has time).

As far as I am concerned, good job!

@mkouba mkouba changed the title WebSockets Next: security integration WebSockets Next: initial version of security integration May 9, 2024
@sberyozkin
Copy link
Member

Quick question, why disabling the proactive authentication is required for the identity be stored at the HTTP to WS upgrade time ?

@mkouba
Copy link
Contributor Author

mkouba commented May 10, 2024

Quick question, why disabling the proactive authentication is required for the identity be stored at the HTTP to WS upgrade time ?

It's not required. However, if proactive=false then we need to force the authentication before the HTTP upgrade is performed.

@sberyozkin
Copy link
Member

Hi @mkouba Oh I see, I wrongly assumed from the PR description that disabling it was required, but you are only explaining there what must be done in case of the lazy security check. Thanks

@mkouba
Copy link
Contributor Author

mkouba commented May 10, 2024

I can support this PR with the Docs update once the code is ready to go

@sberyozkin the code is IMO ready to go. At least for the first version ;-).

@mkouba mkouba marked this pull request as ready for review May 10, 2024 10:15
@quarkus-bot

This comment has been minimized.

client.sendAndAwait("hello"); // forbidden
client.sendAndAwait("hi"); // user service
client.waitForMessages(2);
assertEquals(Set.of("42", "forbidden"), Set.copyOf(client.getMessages().stream().map(Object::toString).toList()));
Copy link
Contributor

Choose a reason for hiding this comment

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

@mkouba if connection would be opened for endUri and the bean would look like this

 @Authenticated
@WebSocket(path = "/end")
    public static class Endpoint {

(which is how it looks in other tests, but not here)

If you use quarkus.http.auth.permission.secured.paths=/end as in other tests, it fails immediately and @OnError is not called, it happens before the request even gets to the WS Next extension.

However when the @Authenticated annotation is used, anonymous user can keep sending messages that are bound to fail. Instead, user will receive responses from @OnError.

Situation is similar in case of authorization, if you know that user will be unauthorized because in the initial handshake for that URI he didn't have these roles, how many times should he be allowed to send a message like you do

client.sendAndAwait("hello");
client.sendAndAwait("hi");

in theory, can it be 100 000 even though we know what the response will be?

I am asking because there are things I don't know yet, for example if the client.connect(basicAuth("user", "user"), endUri); is opened for certain URI, can we be sure it is only open for that URI? If so, either now or in the future, we need to decide whether we just keep this connection open.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

However when the @authenticated annotation is used, anonymous user can keep sending messages that are bound to fail. Instead, user will receive responses from @onerror.

Yes, and it's up to the endpoint author to decide what to do. For example, you can do something like:

@OnError
void error(ForbiddenException e, WebSocketConnection conn) {
  conn.closeAndAwait(); // and possibly specify the close reason
}

Situation is similar in case of authorization, if you know that user will be unauthorized because in the initial handshake for that URI he didn't have these roles, how many times should he be allowed to send a message like you do

Until the connection is closed.

I am asking because there are things I don't know yet, for example if the client.connect(basicAuth("user", "user"), endUri); is opened for certain URI, can we be sure it is only open for that URI?

I'm not sure I'm following. What do you mean?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I'm following. What do you mean?

My thinking was that only sensible reason to not close the connection is that same connection can be used for different URIs, but I think it's clear now that it cannot. Thanks

Yes, and it's up to the endpoint author to decide what to do.

Cool, let's stick with that. But I think the documentation (when it is written later) will have to make clear that on error you should close the connection unless you have a good reason. Just so that people won't forget, it doesn't make too much of a sense to me to keep this open if you cannot change the identity.

Copy link
Contributor

Choose a reason for hiding this comment

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

@mkouba speaking of what, according to the https://quarkus.io/guides/websockets-next-reference#websocket-endpoint-methods the @OnError is not required, am I correct? If so, what will happen when there is no @OnError, can we just close the connection in the future (not in this PR)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just so that people won't forget, it doesn't make too much of a sense to me to keep this open if you cannot change the identity.

Well, it depends on how the endpoint is used. You cannot change the identity but the message that resulted in a ForbiddenException could have been a mistake. It's very similar to a webapp where a user intentionally or unintentionally attemps to access a resource that is forbidden for this particular user 🤷. Do we log off the user automatically?

@mkouba speaking of what, according to the https://quarkus.io/guides/websockets-next-reference#websocket-endpoint-methods the @OnError is not required, am I correct?

You're right.

If so, what will happen when there is no @OnError, can we just close the connection in the future (not in this PR)?

Currently, we only log an error message. But we can certainly close the connection by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, the problem is that JAX-RS endpoints work diferently - each method handles an HTTP request, so it makes sense to map the security checks to methods. In our case, an endpoint method does not represent an HTTP request, in fact the endpoint instance is created and callbacks are invoked after the initial HTTP request upgrade finished successfully.

Maybe we should merge this PR and improve the integration in follow-up PRs. The discussion is getting harder to follow.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, the problem is that JAX-RS endpoints work diferently - each method handles an HTTP request, so it makes sense to map the security checks to methods. In our case, an endpoint method does not represent an HTTP request, in fact the endpoint instance is created and callbacks are invoked after the initial HTTP request upgrade finished successfully.

It makes sense. There should be consent and more people need to comment on how this should work. Whatever there is settlement on, it will need to be highlighted in the docs in the future.

Personally I'm still worried about expectations. For example in the ideal world, I'd expect that if you want to close connection then you put the annotation like this:

@Authenticated
@OnOpen
String open(@PathParam String name) {
    return "Hello " + name + "!";
}

than it works and connection is closed immediately for anonymous user.

Maybe we should merge this PR and improve the integration in follow-up PRs. The discussion is getting harder to follow.

Absolutely, this could go on for a long time. Before you merge, please let's hear from @stuartwdouglas first. I won't comment anymore to limit discussions in this PR down. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks Michal. Re closed connection - yes, I think that we could make it configurable and enable it by default, i.e. a connection is closed automatically if a security failure occurs.

Copy link
Member

Choose a reason for hiding this comment

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

Are we sure logging is the best option when an unhandled failure of any kind occurs? Unlike HTTP there is no standard way to report back to the client that there was a problem, so closing the connection seems like a reasonable repose.

That said I think we should make sure that if the endpoint has security annotations on the class itself it should affect the upgrade process, and make sure it will send back a challenge if the user is not authenticated. Opening the connection and then immediately closing it or just essentially ignoring all messages are both the wrong things to do.

Copy link
Contributor Author

@mkouba mkouba May 15, 2024

Choose a reason for hiding this comment

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

Are we sure logging is the best option when an unhandled failure of any kind occurs? Unlike HTTP there is no standard way to report back to the client that there was a problem, so closing the connection seems like a reasonable repose.

@stuartwdouglas I don't know, for me closing a connection on any error seems a bit too "aggressive" strategy. But it might make sense to make this configurable and provide several strategies out of the box: (1) close connection (default), (2) log an error and (3) noop. I will open a new issue as it's not a security-only thing. #40648

That said I think we should make sure that if the endpoint has security annotations on the class itself it should affect the upgrade process, and make sure it will send back a challenge if the user is not authenticated. Opening the connection and then immediately closing it or just essentially ignoring all messages are both the wrong things to do.

#40622

@sberyozkin
Copy link
Member

Thanks @mkouba, and @michalvavrik It is a perfect start IMHO.
The discussion about closing the connection is interesting, may be it should indeed be to the user code to close it if it is an authorization exception, as I guess then the WS client can try another method. I'm also assuming @Authenticated will always work with the callback since the authenticated identity is present.
But for #40536, IMHO. we'll have to close it automatically should the identity has expired, but we can discuss it later

Copy link
Member

@sberyozkin sberyozkin left a comment

Choose a reason for hiding this comment

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

LGTM, can't help waiting for trying it with quarkus-langchain4j demos :-)

@sberyozkin
Copy link
Member

Lets see if more comments will come early next week from Stuart, Clement, before merging

@michalvavrik
Copy link
Contributor

may be it should indeed be to the user code to close it if it is an authorization exception, as I guess then the WS client can try another method

That's what I tried to find out, but so far my understanding is that WS client cannot use the same connection to try another method.

@sberyozkin
Copy link
Member

sberyozkin commented May 14, 2024

@stuartwdouglas Hi Stuart, it would be great for this initial security integration PR to make it to 3.11.0, if not to CR1. As far as I'm concerned, I'm ready to merge after adding a doc commit to this PR

@sberyozkin
Copy link
Member

sberyozkin commented May 14, 2024

@mkouba I added a doc commit, please let me know if you are OK with it, also if you'd like, please squash.

Note, I'm listing @Authenticated only at the callback method level, as having it on the class level gives an impression it may be also securing onOpen and added a comment clarifying that at the moment the secure upgrade requires a configured HTTP policy.
Also, in the docs, I updated the code to inject SecurityIdentity directly.

Planning to merge tomorrow once the docs are good, unless Stuart adds some more comments. IMHO, it is good to go with some improvements to follow up.

@quarkus-bot

This comment has been minimized.

@quarkus-bot

This comment has been minimized.

@quarkus-bot

This comment has been minimized.

@quarkus-bot

This comment has been minimized.

Copy link

github-actions bot commented May 14, 2024

🙈 The PR is closed and the preview is expired.

Copy link
Contributor Author

@mkouba mkouba left a comment

Choose a reason for hiding this comment

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

@sberyozkin I think that it's a good start! I've added a few minor comments...

docs/src/main/asciidoc/websockets-next-reference.adoc Outdated Show resolved Hide resolved
docs/src/main/asciidoc/websockets-next-reference.adoc Outdated Show resolved Hide resolved
docs/src/main/asciidoc/websockets-next-reference.adoc Outdated Show resolved Hide resolved
@quarkus-bot
Copy link

quarkus-bot bot commented May 15, 2024

Status for workflow Quarkus CI

This is the status report for running Quarkus CI on commit 5f7feb0.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

⚠️ There are other workflow runs running, you probably need to wait for their status before merging.

You can consult the Develocity build scans.

@quarkus-bot
Copy link

quarkus-bot bot commented May 15, 2024

Status for workflow Quarkus Documentation CI

This is the status report for running Quarkus Documentation CI on commit 5f7feb0.

✅ The latest workflow run for the pull request has completed successfully.

It should be safe to merge provided you have a look at the other checks in the summary.

@mkouba
Copy link
Contributor Author

mkouba commented May 15, 2024

I think that we're good to go now and improve later in follow-up PRs, especially for #40622 and #40648.

@sberyozkin
Copy link
Member

3.11 has just been branched so I'm adding a backport label, would be great to have it 3.11.0

@sberyozkin sberyozkin merged commit 9726d32 into quarkusio:main May 15, 2024
22 checks passed
Quarkus Documentation automation moved this from To do to Done May 15, 2024
@quarkus-bot quarkus-bot bot added this to the 3.12 - main milestone May 15, 2024
@quarkus-bot quarkus-bot bot added the kind/enhancement New feature or request label May 15, 2024
}
----
<1> The echo callback method can only be invoked if the current security identity has an `admin` role.
<2> The error handler is invoked in case of the authorization failure.
Copy link
Contributor

Choose a reason for hiding this comment

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

It can also be UnauthorizedException for @RolesAllowed.

Copy link
Member

Choose a reason for hiding this comment

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

@michalvavrik I think in this case it probably won't happen since the upgrade is secured with the HTTP authentication policy, while @RolesAllowed is run on the callbacks with SecurityIdentity being already around.

I guess this doc can be updated to list another possible exception when @mkouba has a chance to look at the next security enhancement, like supporting the secure upgrade with the annotations, to test the @RolesAllowed @OnOpen combination.

Copy link
Contributor

Choose a reason for hiding this comment

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

@michalvavrik I think in this case it probably won't happen since the upgrade is secured with the HTTP authentication policy, while @RolesAllowed is run on the callbacks with SecurityIdentity being already around.

with anonymous identity around, it's going to happen; WS Next authenticates eagerly, but does not enforce authorization eagerly

I guess this doc can be updated to list another possible exception when @mkouba has a chance to look at the next security enhancement, like supporting the secure upgrade with the annotations, to test the @RolesAllowed @onopen combination.

yeah, it was just a thought, no worry

@cescoffier
Copy link
Member

Awesome!

@gsmet gsmet modified the milestones: 3.12 - main, 3.11.0 May 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Development

Successfully merging this pull request may close these issues.

WebSockets Next: security integration
7 participants