PDA

View Full Version : Security patterns: Forcing disconnection


churrusco
07-11-2007, 12:25 PM
Hi guys,

I'm doing some additional evaluation to LightStreamer, this time regarding Security. I would like to know if there is any security patter/best practice to address the scenario in which a client is successfully receiving notifications but suddenly the client session gets invalidated and Lighstreamer must disconnect him.

Assuming we are using the Java API, i.e. a non HTML client, what is the correct pattern to force a client unsubscription from server-side? Is it sending a notification? is there any exception that can be thrown? ...

Thanks,
Martin

Alessandro
07-12-2007, 10:37 AM
Hi Martin,

There exist cases where you could decide to disconnect a user from Lightstreamer Server (for example if they log into a second session on a different Server and you don't want to support multiple push session for the same account; if you have one Server only, ConflictingSessionException (http://www.lightstreamer.com/docs/adapter_java_javadoc/com/lightstreamer/interfaces/metadata/ConflictingSessionException.html)will do the trick). There are three ways to terminate an existing Lightstreamer session (one of which has been deprecated):

Option 1) Implement the getUserSession() (http://www.lightstreamer.com/docs/adapter_java_javadoc/com/lightstreamer/interfaces/metadata/MetadataProvider.html#getUserSessions(java.lang.String)) method of the MetadataProvider together with the KILL command on the HTTP interface (see section 4.4 of “Network Protocol Tutorial.pdf”). But the getUserSession() method has been deprecated (i.e. we could decide in future releases of Lightstreamer Server to discontinue it), mainly for security reasons.

Option 2) Use JMX (which is only available in the Vivace Edition of Lightstreamer). JMX (Java Management eXtensions) is a standard specification that allows an application to be controlled both internally and externally and both manually and automatically (i.e. from code). Lightstreamer Server exposes a set of MBeans that give full control over the engine and the sessions. To terminate a session through JMX, you just need to call the destroySession() (http://www.lightstreamer.com/docs/server_jmx_javadoc/com/lightstreamer/jmx/SessionMBean.html#destroySession())method on the appropriate SessionMBean object. The powerful aspect is that you can access MBean objects both from inside your Adapter and from your J2EE application server, because JMX already implements several transport protocols for remote invocation, besides in-process invocation.

Option 3) Handle the termination on the client side. Your clients will subscribe to some "service item" through which your Data Adapter will deliver termination notifications to the clients. When a client receives such message, it will terminate its Lightstreamer session, disconnecting from Lightstreamer Server.

rsouissi
07-15-2007, 09:20 PM
Hi,

Do you mind explaining in java code how option 3 (in process jmx invoke) works inside the meta adapter for instance ?
I asked a while ago in the forums how within the meta adapter notifyUser() function I can get the remoteIP and UserAgent and it seems SessionMBean does it.

Thanks
A

DarioCrivelli
07-16-2007, 04:03 PM
Hi

assuming you asked for an example based on JMX (i.e. option 2), we show you a code snippet that, added to the Metadata Adapter code, listens to the notifications for session initiation and termination and causes each session to be closed after it has lived longer than five minutes.

There are several ways to access Lightstreamer MBeans server from the Metadata Adapter.
We use the simplest one, which takes advantage of the fact that Lightstreamer Server loads all the Adapters in its main ClassLoader;
so we access the MBeans server via static references, through the MBeanServerFactory.findMBeanServer method.


private final Set currSessions = new HashSet();
private final Timer timer = new Timer();
private final long expiryTime = 5 * 60 * 1000;

private final MBeanServer server;
{
ObjectName serverMBeanName = null;
try {
serverMBeanName = new ObjectName("com.lightstreamer", "type", "Server");
// this MBean is always defined by Lightstreamer MBean server;
// we use it as a mean to identify Lightstreamer MBean server
} catch (MalformedObjectNameException e1) {
// TODO
}

if (serverMBeanName != null) {
ArrayList servers = MBeanServerFactory.findMBeanServer(null);
MBeanServer found = null;
for (int i = 0; i < servers.size(); i++) {
found = (MBeanServer) servers.get(i);
if (found.isRegistered(serverMBeanName)) {
break;
} else {
found = null;
}
}
if (found == null) {
// TODO
}
server = found;
} else {
server = null;
}
}

public void notifyNewSession(String user, final String session) throws CreditsException, NotificationException {
synchronized (currSessions) {
currSessions.add(session);
}
timer.schedule(new TimerTask() {
public void run() {
synchronized (currSessions) {
if (! currSessions.contains(session)) {
return;
}
}
killSession(session);
// this also causes notifySessionClose to be called
}
}, expiryTime);
}

public void notifySessionClose(String session) throws NotificationException {
synchronized (currSessions) {
currSessions.remove(session);
}
}

private void killSession(String session) {
if (server == null) {
// TODO
return;
}
ObjectName sessionMBeanName = null;
Hashtable props = new Hashtable();
props.put("type", "Session");
props.put("sessionId", session);
try {
sessionMBeanName = new ObjectName("com.lightstreamer", props);
} catch (MalformedObjectNameException e1) {
// TODO
return;
}

Object ret;
try {
ret = server.invoke(sessionMBeanName, "destroySession", null, null);
} catch (InstanceNotFoundException e) {
// it is still possible that the session has just ended
return;
} catch (MBeanException e) {
// TODO
return;
} catch (ReflectionException e) {
// TODO
return;
}

if (((Boolean) ret).booleanValue() == false) {
// it is still possible that the session has just ended
return;
}
}


Old note (until Web Client Library version 4.2.1)
Note that this closure strategy is entirely server-side and the client receives no notice of the closure reason.
The client only sees an unexpected closure.
This means that, if the client is a web page based on the Web Client Library, the client will enter stalled state and, eventually, an attempt to create a new session will be performed.
In this sense, option 3 may be preferrable to both 1 and 2.
New note (since Web Client Library version 4.2.2)
Note that, in case a session is forcibly closed by the Server, the Web Client Library enters in "DISCONNECTED" state and does not try to recover the session; it just notifies application code through the "onServerError" event handler, with proper error codes.

About getting extended information on the sessions by taking advantage of the JMX interface:
Old note (for LS Server up to and including 3.6)
It has to be considered that many of the SessionMBean methods (and getUserAgent in particular) have not been implemented yet.
This is not reported in the MBeanInfo interface, but is shown in the javadoc-style interface description (http://www.lightstreamer.com/docs/server_jmx_javadoc/com/lightstreamer/jmx/SessionMBean.html).

New note (for LS Server 4.0 and later, still to be released at time of writing)
All the information available on the SessionMBean is reported in the javadoc-style interface description (http://www.lightstreamer.com/docs/server_jmx_javadoc/com/lightstreamer/jmx/SessionMBean.html).
Note that the same information is available dynamically through the MBeanInfo interface.

Dario

churrusco
07-16-2007, 04:21 PM
Thanks Dario.

The MBean strategy works perfectly for us.

The reason why we want to kill the user session is not really related with having multiple sessions (I guess to solve this overwrite the newSession callback method in the adapter is more than enough) but with security. Our system has a built-in hard session expiry mechanism to prevent malicious session handling attacks.

Having the JMX possibility is enough for us. Btw, is the JMX server secured? Because if it stores the session as it seems I guess that you can obtain plenty of useful information from it.

Regards,
Martin

DarioCrivelli
07-17-2007, 09:20 AM
Hi Martin

may you please clarify your security concerns?
We aim to guarantee security against external hosts. The JMX interface is exposed through some configurable ports, which can be hidden by the firewall and this should be enough.
However, at the moment, we don't protect the Server (and, in particular, its JMX interface) against its own Adapters. We consider the Server and all configured Adapters as "friends". In fact, they all lie in the same ClassLoader.

Dario

churrusco
07-17-2007, 10:21 AM
Hi Dario.

The problem is not the adapter but an attacker from outside that box, even within the green zone. You can enable some firewall rules to protect you from external attackers but within your intranet probably your sysadmins will want to be able to use the JMX console wherever they are.

As far as I know, there is no authentication for the JMX RMI interface so anyone that is in the intranet can open a JMX console and kill other user's sessions. Correct me if I'm wrong as I haven't tried it so it is only speculation.

Anyways, not a big issue for as and I suppose it could be an improvement.

DarioCrivelli
07-18-2007, 10:54 AM
Hi Martin,

Indeed, such a protection in the JMX access is lacking.
The extension is in our roadmap, but no time references have been set yet.

Dario

churrusco
07-23-2007, 01:14 PM
Thanks Dario,

that's good to know.

Is there any reason why Lighstreamer is using its own JMX server implementation instead of using the JDK Platform MBean Server?

Martin

Alessandro
07-24-2007, 12:45 PM
Hi Martin,

Lightstreamer Server uses Sun's MBean Server (in particular, Sun's JMX Reference Implementation and Sun's JMX Remote API). You can find the related JAR files under "\Lightstreamer\lib".

phan_lam
12-02-2009, 06:12 PM
There exist cases where you could decide to disconnect a user from Lightstreamer Server (for example if they log into a second session on a different Server and you don't want to support multiple push session for the same account;
Option 3) Handle the termination on the client side. Your clients will subscribe to some "service item" through which your Data Adapter will deliver termination notifications to the clients. When a client receives such message, it will terminate its Lightstreamer session, disconnecting from Lightstreamer Server.

This is our case: we want the server to disconnect all previous sessions of an user when he/she connects again (the same way as Yahoo Messenger does). And Option 3 sounds like a solution. Could you please explain a bit more details on how could I achieve this effect. Thank you.

DarioCrivelli
12-03-2009, 10:55 AM
Note that the "termination on the client side" technique was suggested for a java-based client.
If your client were a web page, it might be easier for a malicious user to disable the mechanism.

The idea is that the Data Adapter should manage special items that allow each single client session to subscribe to its own item, so as to receive its own notifications from the Data Adapter. For instance, the item names might be of the general form "<sessionID>_status".
The client should only subscribe to a generic name like "session_status"; then, the Metadata Adapter, in getItems could generate the final name by leveraging session information.
The Data Adapter might manage a single field for all those items (let's call it "allowed") and value it as "Y" or "N", according to your constraints.
A client which found that the "allowed" field has a value of "N" should disconnect.

Consider that the forthcoming 3.6 release of the Server will support a new "destroy session" request, as a milder replacement for the deprecated "kill all user sessions" feature.
So, it will be possible to issue the request from any backend process that knows the active session IDs (by communicating with the various Metadata Adapter instances).
However, the availability of the generic client SDK would be needed, which requires the Presto or Vivace version of the Server.

DarioCrivelli
02-19-2010, 04:31 PM
Lightstreaemer Server 3.6 release is now available.
What follows is a sample code for forcing session termination from the Metadata Adapter, made possible by the new "destroy session" request.
The sample can substitute the JMX based one shown earlier in this thread and represents a new implementation of Option 1 introduced above.

So, we show a code snippet that, added to the Metadata Adapter code, listens to the notifications for session initiation and termination and causes each session to be closed after it has lived longer than five minutes.
Upon forced session closure, the Web Client Library will enter "DISCONNECTED" state and won't try to recover the session; it will just notify application code through the "onServerError" event handler, with proper error codes.


import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import java.net.HttpURLConnection;
import java.net.URL;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import com.lightstreamer.interfaces.metadata.CreditsException;
import com.lightstreamer.interfaces.metadata.NotificationException;

.................

private static final String host = "localhost";
private static final int port = 80;
// we cast the local port number; the ports used by the current instance
// of Lightstreamer Server cannot be inspected in a simple way;
// the name of the configured <http_server> or <https_server> block
// related with the port on which the requests for the current session
// have been received is available as the "LOCAL_SERVER" element in the
// clientContext Map in notifyNewSession

private final Set currSessions = new HashSet();
private final Timer timer = new Timer();
private final long expiryTime = 5 * 60 * 1000;

public void notifyNewSession(String user, final String session, Map clientContext)
throws CreditsException, NotificationException {

synchronized (currSessions) {
currSessions.add(session);
}
timer.schedule(new TimerTask() {
public void run() {
synchronized (currSessions) {
if (! currSessions.contains(session)) {
return;
}
}
try {
killSession(session);
// this also causes notifySessionClose to be called
} catch (IOException e) {
// TODO
}
}
}, expiryTime);
}

public void notifySessionClose(String session) throws NotificationException {
synchronized (currSessions) {
currSessions.remove(session);
}
}

private void killSession(String session) throws IOException {
URL url = new URL("http", host, port, "/lightstreamer/control.txt");

HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setUseCaches(false);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setAllowUserInteraction(false);

DataOutputStream requestStream = new DataOutputStream(connection.getOutputStream());
requestStream.writeBytes("LS_op=destroy&LS_session=" + session);
requestStream.flush();
requestStream.close();

InputStream answerStream = connection.getInputStream();
BufferedReader answer = new BufferedReader(new InputStreamReader(answerStream));
String outcome = answer.readLine();
if (outcome != null && outcome.equals("OK")) {
// done
} else if (outcome != null && outcome.equals("SYNC ERROR")) {
// the session was not found
// TODO
} else {
// an unexpected condition occurred
// TODO
}
connection.disconnect();
}

.........



Note that the above code can only be used in Lightstreamer "Presto" or "Vivace" edition, in which the text interface is available.
In "Allegro" or "Moderato" edition, we need to resort to the html or javascript interface; the following code snippet exploits the javascript case; the request syntax is the same, but for control.js in place of control.txt.


import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;

import java.net.HttpURLConnection;
import java.net.URL;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import com.lightstreamer.interfaces.metadata.CreditsException;
import com.lightstreamer.interfaces.metadata.NotificationException;

.................

private static final String host = "localhost";
private static final int port = 80;
// we cast the local port number; the ports used by the current instance
// of Lightstreamer Server cannot be inspected in a simple way;
// the name of the configured <http_server> or <https_server> block
// related with the port on which the requests for the current session
// have been received is available as the "LOCAL_SERVER" element in the
// clientContext Map in notifyNewSession

private final Set currSessions = new HashSet();
private final Timer timer = new Timer();
private final long expiryTime = 5 * 60 * 1000;

public void notifyNewSession(String user, final String session, Map clientContext)
throws CreditsException, NotificationException {

synchronized (currSessions) {
currSessions.add(session);
}
timer.schedule(new TimerTask() {
public void run() {
synchronized (currSessions) {
if (! currSessions.contains(session)) {
return;
}
}
try {
killSession(session);
// this also causes notifySessionClose to be called
} catch (IOException e) {
// TODO
}
}
}, expiryTime);
}

public void notifySessionClose(String session) throws NotificationException {
synchronized (currSessions) {
currSessions.remove(session);
}
}

private void killSession(String session) throws IOException {
URL url = new URL("http", host, port, "/lightstreamer/control.js");

HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setUseCaches(false);
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setAllowUserInteraction(false);

DataOutputStream requestStream = new DataOutputStream(connection.getOutputStream());
requestStream.writeBytes("LS_op=destroy&LS_session=" + session);
requestStream.flush();
requestStream.close();

InputStream answerStream = connection.getInputStream();
/*
* TODO interpretation of the response
*/
connection.disconnect();
}

.........




Note that the response is in javascript and it is more complicated to analyze it.
In case the operation is successful we should expect:

/*
window.LS_lastError = {};
window.LS_lastError[1] = null;

*/

On the other hand, if the session was not found we should expect:

/*
window.LS_lastError = {};
window.LS_lastError[1] = "sync error";

*/

Any other response means that an unexpected condition has occurred.

mohamida
04-19-2010, 12:38 PM
Hi Dario,

How to do such thing, for example with the StockList Demo, knowing that this demo is using the default MetaAdapter ?
Do i have to impelement one ?

DarioCrivelli
04-19-2010, 01:19 PM
Yes, the above code is not part of any available sample, so you should add it and recompile in order to test it.
You can create a subclass of the default LiteralBasedProvider class, for instance.

mohamida
04-20-2010, 09:46 AM
Thank you Dario.
What i did is creating a new class, extending the default LiteralBasedProvider class and adding the lines (for destroying the session) in it.