need ACLs - architecture to handle authentication and authorization for subscribe and call actions

#1

All,

Dynamic Authentication?

···

==========

For the new system we are building, I intend to authenticate users to the WAMP Router (Crossbar.io) by using the dynamic authentication feature. I want to have all clients connect and use CRA to authenticate, but I’m configuring Crossbar to use a custom authenticator:

“ws” : {

“type” : “websocket”,

"auth" : {

"wampcra" : {

	"type" : "dynamic",

	"authenticator" : "com.domain.session.authenticate"

}

}

}

But, the call “com.domain.session.authenticate” needs to be registered! How does the client that will register that function connect to they system if it can’t authenticate first!? Seems like a chicken/egg problem? What am I missing?

Session Client

==========

When a callee gets called, the client receives data like “funcname(args, kwargs, details)”, but unless one of the 3 parameters contains the auth_id of the user, we can not do granular authorization inside the function itself. To avoid passing authid as an argument (which I wouldn’t be able to trust coming from another client), I figured I’d jack into the “session id” that is attached to all clients in order to build a system that works similar to the pattern we find in HTTP/PHP sessions. Therefore, I’m planning to write a session management client which registers some functions as follows:

  • com.domain.session.authenticate(username, secret) : The ‘authenticate’ function will verify username and password and store the auth credentials in memory associated with the connected client’s session id. The stored data will include everything Crossbar needs to authenticate, like: authid, secret, realm, and authrole as well as other data like session id and possibly ip address, etc. Using this mapping, we can later look up any of this data from the session id alone.

  • com.domain.session.deauthenticate(auth_id) : Used to remove the auth_id records from the authorized session list. Doing this will purge the user from all sessions and effectively “log them out” and if possible, perhaps I can terminate a client connection that may match the particular session id.

  • com.domain.session.getSession(session_id) : Used by any other part in the system to look up the auth data about a given session id. It’ll return the in memory mapping of authid, realm, authrole, etc when passed in the session id of the client.

  • com.domain.session.authorize : Used by crossbar to handle authorization for routing. This needs more input, however…

By using the option disclose_me = true in all calls (or as I recently found out about, disclose_caller = true in the registration), I can have Crossbar attach a session id to all RPC calls.

Does it make sense to create this session object just to handle mapping of session_id onto authid and authrole, etc, or is there already something like this being stored inside Crossbar already? What I really want is for Crossbar to pass the authorization data about the client session to all my RPC calls so I can do more fine-grained ACL authorization inside each RPC call directly.

Talk About ACLs

==========

Let’s imagine a simple chat system built with Crossbar. If chat messages could be sent to a particular user by way of a subscribed topic, we might use something like this:

com.domain.chat..message

And a user would subscribe to this topic:

SUBSCRIBE com.domain.chat..message

But we need security, so we need to implement authorization to prevent a user from subscribing to this topic if they are not the recipient user (users should only receive messages sent to them). So, the authorization method would need to ensure that only user_id = 5 can subscribe to com.domain.chat.5.message … and likewise only user_id = 7 can subscribe to com.domain.chat.7.message. This is simple to do by matching a pattern to the uri and knowing nothing about chat, we can write a function to verify that the user_id you are authenticated with (aka authid) is found in the proper position of the matched uri pattern.

This can be done with a dynamic function like com.domain.session.authorize and have that defined by Crossbar as a dynamic function.

The problem I have is that this system will get very large quickly and the whole point of crossbar is to keep parts small, simple, and modular. In fact #1 in the benefits suggests it should be loosely coupled. Well, if I suddenly have many backend modules/clients connecting to this system, it would suck to have ALL of those registered functions and publishers of topics send their authorization through the SAME authorization function because eventually I will need very granular control over user permissions.

Many of todays larger and complex applications use ACLs to control what rights a user has and these ACLs can key off user_id, group membership, role, and more. Specifically for this example “chat” application, let’s extend our example and say we would like to have chat rooms. A chat room topic might look like this:

com.domain.chat.room.<room_id>.message

Messages in the chat room should only be sent to members of the chat room. No non-members should be able to subscribe to a room topic that they are not members of. Now, in our authorization object, we do not know the mapping of chat room ids to chat room members (that should be handled by the chat client), so we can not do a simple URI pattern match with any of the known session id meta data.

If all dynamic authorization is done inside the single ‘com.domain.session.authorization’ function, then suddenly the “com.domain.SESSION” backend client needs to know about chat … when that should only be knowledge that the “com.domain.CHAT” client knows about. See the problem here?

I am thinking that perhaps we can separate URI patterns into namespaces and have each of those patterns do authorization in their respective endpoint clients. Something like all “com.domain.chat." is authorized by “com.domain.chat.authorize” and "com.domain.othermodule.” is authorized by “com.domain.othermodule.authorize”. Does that customizability exist today in Crossbar?

– Dante

0 Likes

#2

So, this is the part where I’ve been stuck most of the day waiting for these email answers so I kept fighting through the problem … and now I have most of the answers already. Here are some:

All,

Dynamic Authentication?

==========

For the new system we are building, I intend to authenticate users to the WAMP Router (Crossbar.io) by using the dynamic authentication feature. I want to have all clients connect and use CRA to authenticate, but I’m configuring Crossbar to use a custom authenticator:

“ws” : {

“type” : “websocket”,

“auth” : {

“wampcra” : {

  "type" : "dynamic",
  "authenticator" : "com.domain.session.authenticate"

}

}

}

But, the call “com.domain.session.authenticate” needs to be registered! How does the client that will register that function connect to they system if it can’t authenticate first!? Seems like a chicken/egg problem? What am I missing?

I have created 2 different transports. The first transport listens on port 8080 on all IPs and will require that all connected clients authenticate using WampCRA. This transport also requires that dynamic authentication is turned on and that a custom URI is defined for authentication:

{

	"type" : "web",

	"endpoint" : {

		"type" : "tcp",

		"port" : 8080

	},

	"paths" : {

		"/" : {

			"type" : "static",

			"directory" : "../web/"

		},

		"ws" : {

			"type" : "websocket",

			"auth" : {

				"wampcra" : {

					"type" : "dynamic",

					"authenticator" : "com.domain.session.authenticate"

				}

			}

		}

	}

}

Meanwhile, a second “backend” transport is set up on another port, 9090 and this transport uses static authentication. Another added advantage is that the backend transport only listens on the local loopback, so it’s a bit more secured than if it were open to the public IP space:

{

	"type" : "websocket",

	"endpoint" : {

		"type" : "tcp",

		"port" : 9000,

		"interface" : "127.0.0.1"

	},

	"auth" : {

		"wampcra" : {

			"type" : "static",

			"users" : {

				"session" : {

					"secret" : "secret123",

					"role" : "session"

				},

				"backend" : {

					"secret" : "secret456",

					"role" : "backend"

				}

			}

		}

	}

},

So, with the 2 different transports, our “session” authenticator module/client can connect and register the authenticate method by first authenticating with the static password.

I found the hints in the demo here:

https://github.com/crossbario/crossbarexamples/blob/master/authenticate/wampcradynamic/php/.crossbar/config.json#L24

And with more effort, I also defined router roles for the session and backend clients to grant them register and publish on their respective URIs (as in the above example also).

Session Client

==========

When a callee gets called, the client receives data like “funcname(args, kwargs, details)”, but unless one of the 3 parameters contains the auth_id of the user, we can not do granular authorization inside the function itself. To avoid passing authid as an argument (which I wouldn’t be able to trust coming from another client), I figured I’d jack into the “session id” that is attached to all clients in order to build a system that works similar to the pattern we find in HTTP/PHP sessions. Therefore, I’m planning to write a session management client which registers some functions as follows:

  • com.domain.session.authenticate(username, secret) : The ‘authenticate’ function will verify username and password and store the auth credentials in memory associated with the connected client’s session id. The stored data will include everything Crossbar needs to authenticate, like: authid, secret, realm, and authrole as well as other data like session id and possibly ip address, etc. Using this mapping, we can later look up any of this data from the session id alone.
  • com.domain.session.deauthenticate(auth_id) : Used to remove the auth_id records from the authorized session list. Doing this will purge the user from all sessions and effectively “log them out” and if possible, perhaps I can terminate a client connection that may match the particular session id.
  • com.domain.session.getSession(session_id) : Used by any other part in the system to look up the auth data about a given session id. It’ll return the in memory mapping of authid, realm, authrole, etc when passed in the session id of the client.
  • com.domain.session.authorize : Used by crossbar to handle authorization for routing. This needs more input, however…

By using the option disclose_me = true in all calls (or as I recently found out about, disclose_caller = true in the registration), I can have Crossbar attach a session id to all RPC calls.

Does it make sense to create this session object just to handle mapping of session_id onto authid and authrole, etc, or is there already something like this being stored inside Crossbar already? What I really want is for Crossbar to pass the authorization data about the client session to all my RPC calls so I can do more fine-grained ACL authorization inside each RPC call directly.

I was able to modify my front-end RPC calls to add { disclose_me: true } as an option and by doing this, the back-end RPC callee received additional data such as:

[authrole] => frontend

[caller] => 111409544

[authmethod] => wampcra

[authid] => dante

That is terrific! … and it keeps me moving. Because if you see in my original question, what I really wanted was for Crossbar to pass the ‘authid’ to the callee for the client and this is exactly what is happening now!

But, alas, it is still not enough to make me happy. I don’t want the caller to have to do this work. Since the entire back-end ACL framework is based upon receiving the ‘authid’ as a detail of the RPC call, if we are ever missing this data, the system will not work properly. I can imagine many wasted man-hours chasing after bugs where I or another developer will forget to add the disclose_me: true flag and wonder why permission is denied for the RPC call.

After reading the WAMP spec, I see the following:

https://github.com/tavendo/WAMP/blob/cd3673156bd312fabb431c8b25398783f9519922/spec/advanced.md#caller-identification

“Note that a Dealer MAY disclose the identity of a Caller even without the Caller having explicitly requested to do so when the Dealer configuration (for the called procedure) is setup to do so.”

Yeah, that’s what I want. I want a configuration directive in Crossbar that I can turn on that will basically override the disclose_me client settings and for the URI I define, allow a ‘disclose_me’ or ‘disclose_caller’ to be applied always. I’d like to have Crossbar do what the spec says that some Dealers might do and turn on this disclosure. Does it have that feature today? I didn’t see it documented and yet have not located anything in the sources.

– Dante

···

On Wednesday, January 14, 2015 at 4:42:59 PM UTC+4, Dante Lorenso wrote:

0 Likes

#3

Regarding the “chicken egg problem”:

The authenticator needs to authenticate itself, and this is done via static authentiation

https://github.com/crossbario/crossbarexamples/blob/master/authenticate/wampcradynamic/nodejs/.crossbar/config.json

or by running the authenticator side-by-side in a router, in which case the component has implicit trust:

https://github.com/crossbario/crossbarexamples/blob/master/authenticate/wampcradynamic/python/.crossbar/config.json

···

Am Mittwoch, 14. Januar 2015 21:47:09 UTC+1 schrieb Dante Lorenso:

So, this is the part where I’ve been stuck most of the day waiting for these email answers so I kept fighting through the problem … and now I have most of the answers already. Here are some:

On Wednesday, January 14, 2015 at 4:42:59 PM UTC+4, Dante Lorenso wrote:

All,

Dynamic Authentication?

==========

For the new system we are building, I intend to authenticate users to the WAMP Router (Crossbar.io) by using the dynamic authentication feature. I want to have all clients connect and use CRA to authenticate, but I’m configuring Crossbar to use a custom authenticator:

“ws” : {

“type” : “websocket”,

"auth" : {
"wampcra" : {
  "type" : "dynamic",
  "authenticator" : "com.domain.session.authenticate"
}

}

}

But, the call “com.domain.session.authenticate” needs to be registered! How does the client that will register that function connect to they system if it can’t authenticate first!? Seems like a chicken/egg problem? What am I missing?

I have created 2 different transports. The first transport listens on port 8080 on all IPs and will require that all connected clients authenticate using WampCRA. This transport also requires that dynamic authentication is turned on and that a custom URI is defined for authentication:

{

  "type" : "web",
  "endpoint" : {
  	"type" : "tcp",
  	"port" : 8080
  },
  "paths" : {
  	"/" : {
  		"type" : "static",
  		"directory" : "../web/"
  	},
  	"ws" : {
  		"type" : "websocket",
  		"auth" : {
  			"wampcra" : {
  				"type" : "dynamic",
  				"authenticator" : "com.domain.session.authenticate"
  			}
  		}
  	}
  }

}

Meanwhile, a second “backend” transport is set up on another port, 9090 and this transport uses static authentication. Another added advantage is that the backend transport only listens on the local loopback, so it’s a bit more secured than if it were open to the public IP space:

{

  "type" : "websocket",
  "endpoint" : {
  	"type" : "tcp",
  	"port" : 9000,
  	"interface" : "127.0.0.1"
  },
  "auth" : {
  	"wampcra" : {
  		"type" : "static",
  		"users" : {
  			"session" : {
  				"secret" : "secret123",
  				"role" : "session"
  			},
  			"backend" : {
  				"secret" : "secret456",
  				"role" : "backend"
  			}
  		}
  	}
  }

},

So, with the 2 different transports, our “session” authenticator module/client can connect and register the authenticate method by first authenticating with the static password.

I found the hints in the demo here:

https://github.com/crossbario/crossbarexamples/blob/master/authenticate/wampcradynamic/php/.crossbar/config.json#L24

And with more effort, I also defined router roles for the session and backend clients to grant them register and publish on their respective URIs (as in the above example also).

Session Client

==========

When a callee gets called, the client receives data like “funcname(args, kwargs, details)”, but unless one of the 3 parameters contains the auth_id of the user, we can not do granular authorization inside the function itself. To avoid passing authid as an argument (which I wouldn’t be able to trust coming from another client), I figured I’d jack into the “session id” that is attached to all clients in order to build a system that works similar to the pattern we find in HTTP/PHP sessions. Therefore, I’m planning to write a session management client which registers some functions as follows:

  • com.domain.session.authenticate(username, secret) : The ‘authenticate’ function will verify username and password and store the auth credentials in memory associated with the connected client’s session id. The stored data will include everything Crossbar needs to authenticate, like: authid, secret, realm, and authrole as well as other data like session id and possibly ip address, etc. Using this mapping, we can later look up any of this data from the session id alone.
  • com.domain.session.deauthenticate(auth_id) : Used to remove the auth_id records from the authorized session list. Doing this will purge the user from all sessions and effectively “log them out” and if possible, perhaps I can terminate a client connection that may match the particular session id.
  • com.domain.session.getSession(session_id) : Used by any other part in the system to look up the auth data about a given session id. It’ll return the in memory mapping of authid, realm, authrole, etc when passed in the session id of the client.
  • com.domain.session.authorize : Used by crossbar to handle authorization for routing. This needs more input, however…

By using the option disclose_me = true in all calls (or as I recently found out about, disclose_caller = true in the registration), I can have Crossbar attach a session id to all RPC calls.

Does it make sense to create this session object just to handle mapping of session_id onto authid and authrole, etc, or is there already something like this being stored inside Crossbar already? What I really want is for Crossbar to pass the authorization data about the client session to all my RPC calls so I can do more fine-grained ACL authorization inside each RPC call directly.

I was able to modify my front-end RPC calls to add { disclose_me: true } as an option and by doing this, the back-end RPC callee received additional data such as:

[authrole] => frontend

[caller] => 111409544

[authmethod] => wampcra

[authid] => dante

That is terrific! … and it keeps me moving. Because if you see in my original question, what I really wanted was for Crossbar to pass the ‘authid’ to the callee for the client and this is exactly what is happening now!

But, alas, it is still not enough to make me happy. I don’t want the caller to have to do this work. Since the entire back-end ACL framework is based upon receiving the ‘authid’ as a detail of the RPC call, if we are ever missing this data, the system will not work properly. I can imagine many wasted man-hours chasing after bugs where I or another developer will forget to add the disclose_me: true flag and wonder why permission is denied for the RPC call.

After reading the WAMP spec, I see the following:

https://github.com/tavendo/WAMP/blob/cd3673156bd312fabb431c8b25398783f9519922/spec/advanced.md#caller-identification

“Note that a Dealer MAY disclose the identity of a Caller even without the Caller having explicitly requested to do so when the Dealer configuration (for the called procedure) is setup to do so.”

Yeah, that’s what I want. I want a configuration directive in Crossbar that I can turn on that will basically override the disclose_me client settings and for the URI I define, allow a ‘disclose_me’ or ‘disclose_caller’ to be applied always. I’d like to have Crossbar do what the spec says that some Dealers might do and turn on this disclosure. Does it have that feature today? I didn’t see it documented and yet have not located anything in the sources.

– Dante

0 Likes