Improve OOTB experience for ssh and jmx authentication in fuse
This commit is contained in:
parent
71b36ceb07
commit
ee4fbca868
12 changed files with 119 additions and 32 deletions
|
@ -9,7 +9,7 @@ import org.keycloak.connections.mongo.api.types.MapperRegistry;
|
|||
import org.keycloak.connections.mongo.impl.EntityInfo;
|
||||
import org.keycloak.connections.mongo.impl.MongoStoreImpl;
|
||||
import org.keycloak.models.utils.reflection.Property;
|
||||
import org.keycloak.models.utils.reflection.Types;
|
||||
import org.keycloak.util.reflections.Types;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package org.keycloak.models.utils.reflection;
|
||||
package org.keycloak.util.reflections;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.io.Serializable;
|
|
@ -1,4 +1,4 @@
|
|||
package org.keycloak.models.utils.reflection;
|
||||
package org.keycloak.util.reflections;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.security.PrivilegedAction;
|
|
@ -1,4 +1,4 @@
|
|||
package org.keycloak.models.utils.reflection;
|
||||
package org.keycloak.util.reflections;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
|
@ -10,7 +10,10 @@
|
|||
<cm:property-placeholder persistent-id="org.keycloak" update-strategy="reload">
|
||||
<cm:default-properties>
|
||||
<cm:property name="jaasBearerKeycloakConfigFile" value="$[karaf.base]/etc/keycloak-hawtio.json"/>
|
||||
<cm:property name="jaasBearerRolePrincipalClass" value=""/>
|
||||
|
||||
<cm:property name="jaasDirectAccessKeycloakConfigFile" value="$[karaf.base]/etc/keycloak-direct-access.json"/>
|
||||
<cm:property name="jaasDirectAccessRolePrincipalClass" value="org.apache.karaf.jaas.boot.principal.RolePrincipal"/>
|
||||
</cm:default-properties>
|
||||
</cm:property-placeholder>
|
||||
|
||||
|
@ -20,12 +23,14 @@
|
|||
<jaas:module className="org.keycloak.adapters.jaas.BearerTokenLoginModule"
|
||||
flags="sufficient">
|
||||
keycloak-config-file = ${jaasBearerKeycloakConfigFile}
|
||||
role-principal-class = ${jaasBearerRolePrincipalClass}
|
||||
</jaas:module>
|
||||
|
||||
<!-- Used for direct username/password authentication for non-web scenarios (like ssh) -->
|
||||
<jaas:module className="org.keycloak.adapters.jaas.DirectAccessGrantsLoginModule"
|
||||
flags="sufficient">
|
||||
keycloak-config-file = ${jaasDirectAccessKeycloakConfigFile}
|
||||
role-principal-class = ${jaasDirectAccessRolePrincipalClass}
|
||||
</jaas:module>
|
||||
|
||||
</jaas:config>
|
||||
|
|
|
@ -35,6 +35,16 @@
|
|||
Security for <ulink url="http://cxf.apache.org/">Apache CXF</ulink> endpoints running on default engine provided by CXF servlet.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Security for SSH and JMX admin access.
|
||||
</para>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
Security for <ulink url="http://hawt.io/">Hawt.io admin console</ulink> .
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
<para>The best place to start is look at Fuse demo bundled as part of Keycloak examples in directory <literal>examples/fuse</literal> .</para>
|
||||
|
|
|
@ -31,7 +31,12 @@
|
|||
</variablelist>
|
||||
</para>
|
||||
<para>
|
||||
Both login modules have single configuration property <literal>keycloak-config-file</literal> where you need to provide location of keycloak.json configuration file.
|
||||
Both login modules have configuration property <literal>keycloak-config-file</literal> where you need to provide location of keycloak.json configuration file.
|
||||
It could be either provided from filesystem or from classpath (in that case you may need value like <literal>classpath:/folder-on-classpath/keycloak.json</literal> ).
|
||||
</para>
|
||||
<para>
|
||||
Second property <literal>role-principal-class</literal> allows to specify alternative class for Role principals attached to JAAS Subject. Default
|
||||
value for Role principal is <literal>org.keycloak.adapters.jaas.RolePrincipal</literal> . Note that class should have constructor
|
||||
with single String argument.
|
||||
</para>
|
||||
</section>
|
|
@ -15,14 +15,13 @@ allows to remotely connect to Keycloak and verify credentials based on [Direct a
|
|||
|
||||
Example steps for enable SSH authentication:
|
||||
|
||||
1) Import 'demo' realm as mentioned in [Base steps](../README.md#base-steps) . It contains client `sh-jmx-admin-client`, which is used for SSH authentication.
|
||||
1) Import 'demo' realm as mentioned in [Base steps](../README.md#base-steps) . It contains client `ssh-jmx-admin-client`, which is used for SSH authentication.
|
||||
Skip this step if you installed demo already.
|
||||
|
||||
2) Then you need to update/specify these 2 properties in file `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`:
|
||||
2) Then you need to update/specify this property in file `$FUSE_HOME/etc/org.apache.karaf.shell.cfg`:
|
||||
|
||||
```
|
||||
sshRealm=keycloak
|
||||
sshRole=org.keycloak.adapters.jaas.RolePrincipal:admin
|
||||
```
|
||||
|
||||
3) Copy file from Keycloak fuse examples `examples/fuse/fuse-admin/keycloak-direct-access.json` to `$FUSE_HOME/etc/` directory.
|
||||
|
@ -30,7 +29,7 @@ This file contains configuration of the client application, which is used by JAA
|
|||
|
||||
4) Start Fuse and install `keycloak` JAAS realm into Fuse. This could be done easily by installing `keycloak-jaas` feature, which has JAAS realm predefined
|
||||
(you are able to override it by using your own `keycloak` JAAS realm with higher ranking). As long as you already installed `keycloak-fuse-example` feature as mentioned
|
||||
in [examples readme](../README.md), you can skip this step as `keycloak-jaas` is installed already. Otherwise use those commands:
|
||||
in [examples readme](../README.md), you can skip this step as `keycloak-jaas` is installed already. Otherwise use those commands (replace Keycloak version with current one):
|
||||
|
||||
```
|
||||
features:addurl mvn:org.keycloak/keycloak-osgi-features/1.1.0.Final/xml/features
|
||||
|
@ -49,7 +48,7 @@ And login with password `password` . Note that other users from "demo" realm lik
|
|||
JMX authentication with keycloak credentials on JBoss Fuse 6.1
|
||||
--------------------------------------------------------------
|
||||
|
||||
This may be needed just in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may
|
||||
This may be needed in case if you really want to use jconsole or other external tool to perform remote connection to JMX through RMI. Otherwise it may
|
||||
be better to use just hawt.io/jolokia as jolokia agent is installed in hawt.io by default.
|
||||
|
||||
1) In file `$FUSE_HOME/etc/org.apache.karaf.management.cfg` you can change these 2 properties:
|
||||
|
@ -72,30 +71,23 @@ may be still able to access MBeans remotely via HTTP (Hawtio). So make sure to p
|
|||
really protect JMX mbeans.
|
||||
|
||||
|
||||
JMX on JBoss Fuse 6.2 and Apache Karaf 3.0.2
|
||||
SSH and JMX on JBoss Fuse 6.2 and Apache Karaf 3.0.2
|
||||
--------------------------------------------
|
||||
For SSH steps are very similar to above for 6.1. In JBoss Fuse 6.2 you may need to install `ssh` feature as it doesn't seem to be installed here by default.
|
||||
|
||||
For JMX, the steps are similar like above, however there is more fine grained authorization for JMX access in Fuse 6.2. You still need to configure jmxRealm in
|
||||
`$FUSE_HOME/etc/org.apache.karaf.management.cfg`, however jmxRole is not needed. So in config should be just this:
|
||||
```
|
||||
features:install ssh
|
||||
```
|
||||
|
||||
For JMX, the steps are similar like for Fuse 6.1, however there is more fine grained authorization for JMX access in Fuse 6.2 and Karaf 3.
|
||||
You need to install just jmxRealm in `$FUSE_HOME/etc/org.apache.karaf.management.cfg` . Property `jmxRole` is no longer valid.
|
||||
|
||||
```
|
||||
jmxRealm=keycloak
|
||||
```
|
||||
And then you need to configure authorization for all specific MBeans, which you want to access with authenticated user. For generic access
|
||||
you can configure the most generic ACL in file `$FUSE_HOME/etc/jmx.acl.cfg` like this:
|
||||
```
|
||||
|
||||
```
|
||||
list* = org.keycloak.adapters.jaas.RolePrincipal:admin
|
||||
get* = org.keycloak.adapters.jaas.RolePrincipal:admin
|
||||
is* = org.keycloak.adapters.jaas.RolePrincipal:admin
|
||||
set* = org.keycloak.adapters.jaas.RolePrincipal:admin
|
||||
* = org.keycloak.adapters.jaas.RolePrincipal:admin
|
||||
```
|
||||
Actually if you login as user `admin`, you have very limited privileges without possibility to do much JMX operations as this user has just `admin` role, which is not allowed to do much in JMX.
|
||||
|
||||
However if you login as user `jmxadmin` with password `password`, you will have all JMX privileges! This user has composite role `jmxAdmin`, which is mapped to
|
||||
all possible roles used in JMX authorization files like `etc/jmx.acl.*.cfg` . See karaf documentation for more info about fine grained JMX authorization.
|
||||
|
||||
Then admin will have permissions to see all the attributes and trigger all JMX operations, which don't have their own ACL. You can configure
|
||||
those specific ACLs too. For example if you want to allow operation `lookupAgents` in MBean `jolokia:type=Discovery` you can configure
|
||||
in file `$FUSE_HOME/etc/jmx.acl.jolokia.Discovery.cfg` the property like this:
|
||||
|
||||
```
|
||||
lookupAgents = org.keycloak.adapters.jaas.RolePrincipal:admin
|
||||
```
|
||||
|
|
|
@ -72,6 +72,22 @@
|
|||
"applicationRoles": {
|
||||
"realm-management": [ "realm-admin" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "jmxadmin",
|
||||
"enabled": true,
|
||||
"email" : "jmxadmin@admin.com",
|
||||
"firstName": "JmxAdmin",
|
||||
"lastName": "Burke",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": [ "user", "jmxAdmin" ],
|
||||
"applicationRoles": {
|
||||
"account": [ "manage-account" ],
|
||||
"realm-management": [ "realm-admin" ]
|
||||
}
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
|
@ -83,6 +99,38 @@
|
|||
{
|
||||
"name": "admin",
|
||||
"description": "Administrator privileges"
|
||||
},
|
||||
{
|
||||
"name": "manager"
|
||||
},
|
||||
{
|
||||
"name": "viewer"
|
||||
},
|
||||
{
|
||||
"name": "Operator"
|
||||
},
|
||||
{
|
||||
"name": "Maintainer"
|
||||
},
|
||||
{
|
||||
"name": "Deployer"
|
||||
},
|
||||
{
|
||||
"name": "Auditor"
|
||||
},
|
||||
{
|
||||
"name": "Administrator"
|
||||
},
|
||||
{
|
||||
"name": "SuperUser"
|
||||
},
|
||||
{
|
||||
"name": "jmxAdmin",
|
||||
"description": "Admin role with all privileges to SSH and JMX access",
|
||||
"composite": true,
|
||||
"composites": {
|
||||
"realm": [ "admin", "manager", "viewer", "Operator", "Maintainer", "Deployer", "Auditor", "Administrator", "SuperUser" ]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -149,7 +197,7 @@
|
|||
"scopeMappings": [
|
||||
{
|
||||
"client": "ssh-jmx-admin-client",
|
||||
"roles": ["admin"]
|
||||
"roles": [ "admin", "jmxAdmin" ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.adapters.jaas;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.security.Principal;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
@ -28,6 +29,7 @@ import org.keycloak.adapters.KeycloakDeployment;
|
|||
import org.keycloak.adapters.KeycloakDeploymentBuilder;
|
||||
import org.keycloak.adapters.RefreshableKeycloakSecurityContext;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.util.reflections.Reflections;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -35,11 +37,13 @@ import org.keycloak.representations.AccessToken;
|
|||
public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
||||
|
||||
public static final String KEYCLOAK_CONFIG_FILE_OPTION = "keycloak-config-file";
|
||||
public static final String ROLE_PRINCIPAL_CLASS_OPTION = "role-principal-class";
|
||||
|
||||
protected Subject subject;
|
||||
protected CallbackHandler callbackHandler;
|
||||
protected Auth auth;
|
||||
protected KeycloakDeployment deployment;
|
||||
protected String rolePrincipalClass;
|
||||
|
||||
// This is to avoid parsing keycloak.json file in each request. Key is file location, Value is parsed keycloak deployment
|
||||
private static ConcurrentMap<String, KeycloakDeployment> deployments = new ConcurrentHashMap<String, KeycloakDeployment>();
|
||||
|
@ -50,6 +54,9 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
|||
this.callbackHandler = callbackHandler;
|
||||
|
||||
String configFile = (String)options.get(KEYCLOAK_CONFIG_FILE_OPTION);
|
||||
rolePrincipalClass = (String)options.get(ROLE_PRINCIPAL_CLASS_OPTION);
|
||||
getLogger().debug("Declared options: " + KEYCLOAK_CONFIG_FILE_OPTION + "=" + configFile + ", " + ROLE_PRINCIPAL_CLASS_OPTION + "=" + rolePrincipalClass);
|
||||
|
||||
if (configFile != null) {
|
||||
deployment = deployments.get(configFile);
|
||||
if (deployment == null) {
|
||||
|
@ -117,7 +124,7 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
|||
this.subject.getPrivateCredentials().add(auth.getTokenString());
|
||||
if (auth.getRoles() != null) {
|
||||
for (String roleName : auth.getRoles()) {
|
||||
RolePrincipal rolePrinc = new RolePrincipal(roleName);
|
||||
Principal rolePrinc = createRolePrincipal(roleName);
|
||||
this.subject.getPrincipals().add(rolePrinc);
|
||||
}
|
||||
}
|
||||
|
@ -125,6 +132,23 @@ public abstract class AbstractKeycloakLoginModule implements LoginModule {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected Principal createRolePrincipal(String roleName) {
|
||||
if (rolePrincipalClass != null && rolePrincipalClass.length() > 0) {
|
||||
try {
|
||||
Class<Principal> clazz = Reflections.classForName(rolePrincipalClass, getClass().getClassLoader());
|
||||
Constructor<Principal> constructor = clazz.getDeclaredConstructor(String.class);
|
||||
return constructor.newInstance(roleName);
|
||||
} catch (Exception e) {
|
||||
getLogger().warn("Unable to create declared roleClass " + rolePrincipalClass + " due to " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to default rolePrincipal class
|
||||
return new RolePrincipal(roleName);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean abort() throws LoginException {
|
||||
return true;
|
||||
|
|
|
@ -118,6 +118,7 @@ public class JaxrsBearerTokenFilterImpl implements JaxrsBearerTokenFilter {
|
|||
started = true;
|
||||
}
|
||||
|
||||
// TODO: Use 'Reflections.classForName'
|
||||
protected Class<? extends KeycloakConfigResolver> loadResolverClass() {
|
||||
try {
|
||||
return (Class<? extends KeycloakConfigResolver>)getClass().getClassLoader().loadClass(keycloakConfigResolverClass);
|
||||
|
|
|
@ -6,6 +6,8 @@ import java.lang.reflect.Member;
|
|||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.keycloak.util.reflections.Reflections;
|
||||
|
||||
/**
|
||||
* A bean property based on the value represented by a getter/setter method pair
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue