jakartaee / websocket

Jakarta WebSocket
https://projects.eclipse.org/projects/ee4j.websocket
Other
62 stars 43 forks source link

Java EE Security alignment #238

Open glassfishrobot opened 9 years ago

glassfishrobot commented 9 years ago

In JavaEE 7 we have some security problems with WebSocket.

An authenticated session, with a valid Session.getUserPrincipal() doesn't authenticates in the container on websocket events, so EJB / CDI calls are unauthenticated.

I've tested with WildFly 8.2.0 and GlassFish 4.1, with a sample app which calls EJB methods from @onOpen, @onClose and @onMessage.

Although we can workaround these issues with interceptors and vendor specific security managers, it's a common use case for JavaEE applications and an important requirement for cloud/SaaS applications.

I've created an open-source library to get workaround these problems in JBoss/WildFly. It's called "JBoss Security Extended" and is available on maven central with GAV "com.github.panga:jboss-security-extended:1.0.0".

Library source and docs: https://github.com/panga/jboss-security-extended WebSocket sample app source using library: https://github.com/panga/websocket-auth

glassfishrobot commented 6 years ago
glassfishrobot commented 9 years ago

@glassfishrobot Commented Reported by panga

glassfishrobot commented 9 years ago

@glassfishrobot Commented @pavelbucek said: not sure whether this is a direct duplicate or just consequence of #197. Anyway, I don't see what WebSocket spec can do here - if you have to call proprietary APIs to achieve what you want, we cannot really make that a requirement for all implementations, since that would imply tight integration with single CDI implementation.

glassfishrobot commented 9 years ago

@glassfishrobot Commented panga said: I think it's consequence of #197 and it answers the question 4 of that issue "Is the caller Principal passed on to EJB from WebSocket (I think the answer is yes)?"

The answer is No.

I only need call proprietary API to workaround the problem because spec doesn't cover Websocket vs CDI/EJB integration.

Containers should care about websocket integration with Java EE, not the implementations (Tyrus / Undertow).

glassfishrobot commented 9 years ago

@glassfishrobot Commented tremes said: Hi, I am not 100% sure I understand this issue correctly but I think it's related to https://issues.jboss.org/browse/WELD-2028. This should be IMO clarified.

glassfishrobot commented 9 years ago

@glassfishrobot Commented panga said: You're right, but it's the container responsibility to integrate WebSocket with JAAS, there's nothing to do in WELD, because EJBs are also affected.

I have a test case attached with the issue.

glassfishrobot commented 8 years ago

@glassfishrobot Commented arjan_t said:

You're right, but it's the container responsibility to integrate WebSocket with JAAS,

Note that JAAS is not the universal Java EE security framework. In fact, almost nothing in Java EE refers to JAAS, which is itself a Java SE framework from which Java EE only uses a minimal number of types. What you're probably looking for here is the integration of WebSocket with the native security internals of Servlet, EJB and JCA, such that the authenticated identity (the native, vendor specific implementation of it) propagates to WebSocklet.

Servlet, EJB and JCA may use something JAAS based for the initial authentication, but there is no requirement for that and its just an implementation detail. JASPIC does define a bridge profile for JAAS LoginModules, but that is fully optional.

As we're planning to define a (injectable) SecurityContext in the Java EE security EG that is intended to be used "everywhere", it would be great if we could somehow include WebSockets here. One thing to take into account here is that establishing an authenticated identity is only really specified for Servlet and SOAP, and is primarily per request. The authentication mechanism (e.g FORM or BASIC) has to be aware of the session. In other words, the authenticated identity doesn't automatically stick to the HTTP session.

glassfishrobot commented 9 years ago

@glassfishrobot Commented Issue-Links: is related to WEBSOCKET_SPEC-197

glassfishrobot commented 7 years ago

@glassfishrobot Commented This issue was imported from java.net JIRA WEBSOCKET_SPEC-238

bekwam commented 5 years ago

A Websocket call from a properly-authenticated user should be able to call a secured EJB.

The spec does say that Websockets is built off of the "Servlet defined security mechanism" in 8.1 and 8.2. When I define a security-constraint for my Servlet with a role and a Basic login-config, my identity is propagated to the EJBs. However, when I define an identical security-constraint for my Websocket, the identity is not propagated to the EJB, although I do get a password challenge.

I think the identity should survive the initial upgrade and carry over to all subsequent post-open calls.

My Servlet and Websocket observations have been on WildFly.

joakime commented 5 years ago

Once upgraded you are no longer HTTP or within the scope of a Servlet, so there's that to contend with. There's open issues surrounding this nuance for WELD/CDI (#197) and HttpSession too (#175). The HttpServletRequest and HttpServletResponse are both recycled once upgraded as well. The only time you can safely use Session.getUserPrincipal() (currently) is from @OnOpen or the ServerEndpointConfig.Configurator.

bekwam commented 5 years ago

Are these the only options for using EJBs with Websockets?

  1. Write unsecured EJBs (no RolesAllowed)
  2. Use a façade EJB marked with RunAs and PermitAll to call a secured EJB
  3. Use a proprietary extension that saves off the Principal and re-applies it to the secured EJB context
markt-asf commented 4 years ago

When this was originally discussed in the WebSocket EG the concern was that the life cycle of the HTTP session was separate from that of the WebSocket session. What if the app expires the HTTP session? What impact does that have on any authenticated identity in WebSocket? Does termination of the HTTP session imply termination of the WebSocket session? And so on. There didn't seem to be a solution that was appropriate for all use cases. HandshakeRequest.getUserPrincipal() and HandshakeRequest.getHttpSession() were added to allow applications to implement the solution that best met their requirements. What if we added Session..getUserPrincipal() and Session.getHttpSession() that return null on clients and on servers return the HTTP session and/or associated user principal if

In terms of implementation my expectation is that the WebSocket retains a copy of the HTTP session ID, looks up the HTTP session on a call to Session..getUserPrincipal() or Session.getHttpSession() and returns either the requested object or null if the HTTP session is no longer valid. If this sounds as if it could work, I could code up something in Tomcat that did essentially the same thing but via custom user properties that interested users could test. Any takers?

joakime commented 4 years ago

I don't like the idea of polluting the client Session with server / servlet specific concepts.

Perhaps a new ServerSession extends Session for these kinds of things?

Since these are very "Servlet" specific, would it make sense to have a Session.getHttpContext() with these 2 fields exposed?

Alternatively we could have @ServerEndpoint based endpoints support both HttpSession and UserPrincipal for the on @OnOpen event. Or even introduce a new server specific annotation akin to @OnUpgrade to support transferring any variety of HTTP specific details to the endpoint before @OnOpen Heck, make it support using the HttpServletRequest object as an optional parameter (before it actually upgrades the connection).

In Eclipse Jetty we have the following exposed (currently) in the websocket api: (sorted by most commonly used)

With a @OnUpgrade annotation, all of those become doable in a standard way.

markt-asf commented 4 years ago

Good point re the client Session. I'm still thinking about pros/cons of the approaches you suggest. What I am going to do is use this as the master issue and mark the various other issues that touch on this as duplicates. I'll note any additional specific requirements in this issue when I do that.

markt-asf commented 4 years ago

219 also requested ServletContext HttpSession and for @OnOpen, @OnMessage, @OnError and @OnClose.

markt-asf commented 4 years ago

218 was another request for the HttpSession along with some discussion on exactly what is exposed (original object, (read-only) copy.

217 was another request for the ServletContext

And finally, #175 (HttpSession last accessed time) needs to be kept in mind.

jhanders34 commented 4 years ago

Is anything being done in JakartaEE 9 to resolve this issue? It has recently come up again in a question in stackoverflow.

joakime commented 4 years ago

219 also requested ServletContext HttpSession and for @OnOpen, @OnMessage, @OnError and @OnClose.

HttpSession isn't valid for @OnMessage and @OnError and @OnClose (the HTTP side of things is long gone and recycled at the point in time we can call those methods)

jhanders34 commented 4 years ago

I was speaking specifically about the initial post in this issue referring to calling ejbs on web socket method calls.

joakime commented 4 years ago

I was speaking specifically about the initial post in this issue referring to calling ejbs on web socket method calls.

Issue #197 would need to be solved for that.

The current problem has to do with scope, as in websocket connection scope, it's length (in time) complicates a lot of things. Many things around websocket will need to change (CDI / EJB / Servlet) in order for this to have a chance to work. That kind of change cannot happen in Jakarta EE 8 or Jakarta EE 9 (too drastic). Jakarta EE 10 is the first time this has a hope of being addressed. (we are currently working on Jakarta EE 9)

VGerris commented 3 years ago

and now, when I am reading is we have this issue 5 years open and the other 7 years. Are there any ways forward ? For starters a good documentation on how to fetch the proper session and corresponding websocket session in a thread safe way? The problems with time could perhaps be mitigated by suggesting some scenarios for authentication? Then a user/developer knows the best practice for handling that. Those best practices can then perhaps be added as an extension.

joakime commented 3 years ago

@VGerris HTTP overlaps with WebSocket only during the handshake.

So that means all behaviors that come from HTTP (be it Authentication, Authorization, Cookies, HttpSession, etc) can only apply during the WebSocket Handshake. And once that Handshake is over, the information obtained during that WebSocket Handshake becomes immutable.

Once you are upgraded fully into WebSocket, there's no longer any "live" information from HTTP. Example:

To fix the behaviors on HttpSession would require a dramatic change on the Servlet spec for HttpSession, likely rendering the HttpSession API no longer backward compatible with older versions of the Servlet spec. The HttpSession would essentially have to be a "live" object where changes from other threads would need to be represented in the HttpSession object whereever it may be. This is very problematic for clustered environments, and it one of the reasons this isn't done currently.

I question if we would even still be able to use the standard java.security package classes (eg: java.security.Principal), as there's no notification / listener / event mechanism present in that API for long lived authn/authz like on WebSocket.

In short, this is not an easy thing to fix, to support what you are looking for would requires changes in a half dozen or more Jakarta EE specs outside of WebSocket, and those change would be dramatic.

VGerris commented 3 years ago

Thank you for the quick reply. I have found some posts on StackOverflow from you too I think, thanks for that. I am mostly looking for a concrete description of a threadsafe implementation for the handling and storing of the client id of the http session. I understand after that it is not in the spec what to do when it comes to checking if the websocket is authenticated (like the HTTP session could or could not be). I wonder if there are best practices for that that kind of add to requirements of an implementation in a secure 'enterprise' environment. Those best practices I would like to see available in additional libraries, that could be run in any J2EE server (like Tomcat). If I get tot the point of understanding enough to write it myself in a proper way, I will publish it. I was surprised that a protocol like this has still so many implementation challenges that don't seem to be 'a standard'. Thanks for all your efforts and contributions, they are greatly appreciated!

faceless2 commented 3 years ago

While I completely understand the argument for HttpSession, I don't see it for ServletContext. ServletContext and ServerContainer represent essentially the same thing when run in an engine that supports both. The ServletContext is responsible for loading the classes implementing my WebSocket Endpoints, and I can get a reference for the WebSocketContainer from the ServletContext by calling servletContext.getAttribute("javax.websocket.server.ServerContainer"), so I know a ServletContainer contains at most one of these.

What I can't do is get a reference from the ServerContainer back to the ServletContext, which makes it impossible to read initialization parameters from the web.xml in the WS environment. The approach sometimes recommended (take it from the HttpSession in ServletEndPointConfig.Configurator.modifyHandshake) is no use if there is no HttpSession in place.

There are obviously ways around this, but they shouldn't be necessary. The addition of a WebSocketContainer.getUserProperties() method would allow me to pass through data from the ServletContext; the addition of Object getServletContext() on ServerContainer would be better; or, failing that, adding the same method to HandshakeRequest would be another approach that is clearly not going to cause any conceptual issues, given we can already access HttpSesssion from the same object.

joakime commented 3 years ago

The ServletContext exist to handle the world of Servlets. (Filters, various Servlet Listeners, HTTP, Authentication, Cookies, HttpSession, request dispatching, etc..)

The HTTP/1.1 upgrade to websocket is the overlap with the Servlet world. Once you are upgraded to WebSocket, you are are no longer in the world of Servlets too.

From WebSocket point of view, the ServletContext exists to facilitate HTTP/1.1 negotiation (less so for the abbreviated negotiation for websocket in HTTP/2 or HTTP/3) to an eventual WebSocket connection. Once the websocket connection exists, you are no longer part of the Servlet world. And only tangentially connected to the ServletContext as a result of the context path the websocket was negotiated against. (meaning the websocket connections should shutdown with the ServletContext during shutdown/destroy)

To me, where we are now is a historical side effect of how the Servlet spec was written. It really didn't have (for a long time) the idea of long lived lifecycles for objects. Then HttpSession came along to fill that kind of role, but even that was designed for (relatively) short lived http exchanges (performing updates/merges at the end of exchange only).

Meanwhile, the JSP spec comes along and identifies a hierarchy of the behaviors in the servlet context, creating a few JSP scopes: application, session, request. page. These make sense and are happily adopted.

Next, CDI comes along and identifies a few scopes on the servlet spec (application, session, request, etc) for it's own purposes. These are nice and tidy, a good layering, where the next scope acts like a subset of the higher scope (this is just a viewpoint of how those scopes behave, and is not what the CDI spec actually says)

Now you have WebSocket, which is very long lived. You can easily have during a single WebSocket connection a few sessions, many requests, authentication that changes (denied access, logout, etc), and it could even outlive the application as well (in a cross context scenario).
Neither the Servlet spec, the WebSocket spec, or CDI have hammered out what to do in these kinds of scenarios. (new scopes? new lifecycles identified and detailed? new listeners? new annotations? new exceptions? new behaviors? backward incompatible changes to existing behavior? we'll see.) Most of the behaviors we have currently rely on the performing actions around the start (sometimes the end) of the scopes already defined. However, the long lived websocket connection break this model in interesting ways. In WebSocket we have the HTTP land upgrade, the WebSocket open handshake, the WebSocket connection read/write, and finally the WebSocket close handshake. (imagine if we had to update/query various states of scopes or objects in the ServletContext on each websocket connection event - read/write of frames or messages - in order to trigger some kind of behavior)

Note: I've seen folks have some success with the websocket authn/authz angle by controlling things on the non-websocket components (like the chat component, or data component, or pubsub component, etc), tossing exceptions when the authn/authz changes in specific ways (rejecting the action that the websocket message triggered) so that the websocket component can identify when to start the websocket close handshake. Don't ask me how they did that, I'm just aware that they solved their issue in this general way.

There is a lot of people interested, a few aborted starts, and a realization that its going to take a significant effort on the parts of many people, and jakarta specs, to pull off successfully.

Stay tuned, it will likely happen, and this specific issue (focused on just the security angle) is just one small part of the bigger picture to make it happen.