Hi David,
again it sounds good, and it's what I had in mind.
Before provide a more complete feedback, let me take a look on your github.
Thanks anyway !
Regards
JB
On 08/22/2013 05:07 PM, David Bosschaert wrote:
Hi all,
As suggested by Christian, I started looking at adding role-based access to
OSGi services in general (in Karaf) and applying this to the Karaf commands.
At this point I have something that kinda works. It proxies services for
service consumers using service registry hooks and on invocation checks
that the Subject associated with the current Access Control Context has the
right roles. If not it blocks the service invocation by throwing a
SecurityException.
Not all services are secured this way, there is a new property in
etc/system.properties that selects what services are processed this way
using a simple OSGi service filter. To apply it to the shell commands it is
set to the following:
karaf.secured.services=(&(osgi.command.scope=*)(osgi.command.function=*))
The actual ACLs for the secured services are defined using ConfigAdmin in a
way that's pretty much identical to what I did for the JMX acls in
KARAF-2435, except that the PID doesn't matter. The configuration is
matched with a service on a 'service.guard' property. The other entries
match method names of the service. They can also match values passed in (as
you can do with JMX), so you can define different roles for doit("foo") and
doit("bar"). An example configuration could look like this:
service.guard = (objectClass=org.acme.TestServiceAPI)
doit = admin, viewer
doit["foo"] = admin
So the next thing I did was look at whether this could be applied to the
shell commands. The problem was that every little command is a separate
service so this would potentially be a lot of configuration files for the
administrator to maintain. You really want to define the ACLs for a single
scope in a single configuration file, something like this (for the bundle
scope):
install = manager
start = manager
list = manager, viewer
stop = manager
stop[/.*0.*/] = admin # only admin can stop the framework
uninstall = admin
To fit with the general service ACL model this would have to be 5 different
configuration files (one for each command). I thought that that was not
very user friendly. Therefore I came up with a mechanism that accepts
ConfigAdmin configuration for commands in the same scope like the above and
then generates additional ConfigAdmin configuration on the fly that
conforms to the general service ACL form.
With that, enabled... let's say I'm logged in as a 'manager', with the
above example configuration for the bundle scope, the it has the following
effect:
karaf@root()> stop 50
# works
karaf@root()> stop 0
Error executing command: Insufficient credentials.
Which is pretty much what I wanted to achieve :)
So basically what we have here is a combination of two things:
1. Modification of service registrations to add a roles property which is
then used by the CommandProcessor to only show the commands that the user
associated with the active console can potentially execute (it could still
reject commands based on arguments passed in).
2. Proxying of services (including shell command services) that check that
the Subject associated with the current AccessControlContext has the right
roles to make this invocation.
The role-checking is still done outside of the service implementations. The
actual services being secured don't need to change their code.
You can see my experimental implementation at my branch here:
https://github.com/bosschaert/karaf/commit/2668b88a7ddfb1ba93e7e732884734ff7dc0d1a3
That branch is not finished (cleanup, tests terribly lacking, no
optimization) and some things could be made a little more user friendly,
but it contains the general idea... If people are happy with the general
idea I can focus a little on tiding it up...
Thoughts anyone?
David
On 19 August 2013 10:56, David Bosschaert <[email protected]>wrote:
Hi Christian,
On 19 August 2013 10:29, Christian Schneider <[email protected]>wrote:
The idea was to use Shiro to establish a kind of security context in a
thread local. Your approach of using Subject.doas might be the better
alternative though.
In any case we should recommend one standard approach to establish the
security context. Perhaps we could even allow both and have adapters to
establish one context from another.
Yep, this needs to be standard.
On 08/15/2013 10:16 PM, Christian Schneider wrote:
I like the idea of adding permissions to the commands. I wonder though
if this is perhaps a more general problem that not only affects
commands.
So how about adding a generic permission check for services? For
example
I would like to use the @RolesAllowed annotation to couple roles or
permissions with service methods.
A service registry hook could then check that the caller has the
permission before allowing the call. Of course there could be other
additional way of adding this information like the service properties
you mentioned.
I'm not sure I like the annotation approach. One of the things that I
would
like to enable is for customers to change the roles associated with
operations/service invocations afterwards, simply because the roles
chosen
by the developer may not match up with the roles mappings of all
organizations. With an annotation approach you'd have to modify the code
and recompile it when you want to change them. I prefer to use OSGi
ConfigAdmin for that since it completely decouples this information from
the code and can easily be modified later...
We should also provide a generic way to attach authentication
I think there could be three levels of external configurability:
1. You could use annotations with roles like
@RolesAllowed("admin")
public void deleteUser(...);
2. You could use annotations to store permissions
@RolesAllowed("Userservice.**deleteUser")
public void deleteUser(...);
Then the mapping to roles could be done by using groups in the simplest
form. group UserService.deleteUser: admin, ...
3. You could completely externalize the decision. In this case a Policy
Decision Point approach could make sense.
You extract the meta information of a service call, give it to a pdp and
get back an authorization decision.
I would favor having just option 3. I can see that option 2 provides some
form of indirection, but you still need to modify the service code to add
the annotation in the first place. That might be ok for services that have
their code in the Karaf codebase, but for outside services it would be
pretty much impossible.
I would rather have a clear single way of configuring this so that it's
very clear what the definition is - if a security guy wants to figure out
what roles are needed for options 1 & 2 (s)he needs to have access to the
source to read how the annotation was declared, or otherwise rely on
documentation, which you are never sure is actually in sync with the code.
If we'd opt for a combined annotation+external approach it's still quite
hard to get a full overview of what roles are required to invoke what if
you want to understand that... Hence I would simply go for having just
option 3 and keep everything in one place.
information to a thread that calls a service. I tought about using
apache shiro for this but I am not sure if it is universal enough.
I don't understand why you need Shiro for this.
Isn't javax.security.auth.Subject.**doAs() the standard way to do this?
Probably it is. How does this work internally? Does it also use a thread
local? How does it work if you spawn a new thread using an executor or
similar?
I think we should do some examples to see how it works in practice.
Re Subject.doAs(). The Subject would be configured via JAAS to contain the
appropriate roles for the current user, as we do today in Karaf (although
you should be able to configure Shiro to provide this info)...
Once you're inside a piece of code that is executed via Subject.doAs()
(which could be in a different thread) you can do:
AccessControlContext acc = AccessController.getContext();
Subject subject = Subject.getSubject(acc);
At this point you can get all the Principals from the subject, e.g. all
the roles:
Set<RolePrincipal> roles = subject.getPrincipals(RolePrincipal.class);
This is all plain standard J2SE code, no library dependencies...
Cheers,
David
--
Jean-Baptiste Onofré
[email protected]
http://blog.nanthrax.net
Talend - http://www.talend.com