Security framework

Karaf supports JAAS with some enhancements to allow JAAS to work nicely in an OSGi environment. This framework also features an OSGi keystore manager with the ability to deploy new keystores or truststores at runtime.

Overview

This feature allows runtime deployment of JAAS based configuration for use in various parts of the application. This includes the remote console login, which uses the karaf realm, but which is configured with a dummy login module by default. These realms can also be used by the NMR, JBI components or the JMX server to authenticate users logging in or sending messages into the bus.

In addition to JAAS realms, you can also deploy keystores and truststores to secure the remote shell console, setting up HTTPS connectors or using certificates for WS-Security.

A very simple XML schema for spring has been defined, allowing the deployment of a new realm or a new keystore very easily.

Schema

To override or deploy a new realm, you can use the following XSD which is supported by a Spring namespace handler and can thus be defined in a Spring xml configuration file.

Following is the XML Schema to use when defining Karaf realms:

<?xml version="1.0" encoding="UTF-8"?>
<!--

    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    this work for additional information regarding copyright ownership.
    The ASF licenses this file to You under the Apache License, Version 2.0
    (the "License"); you may not use this file except in compliance with
    the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.

-->
<xs:schema elementFormDefault='qualified'
           targetNamespace='http://karaf.apache.org/xmlns/jaas/v1.1.0'
           xmlns:xs='http://www.w3.org/2001/XMLSchema'
           xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:tns='http://karaf.apache.org/xmlns/jaas/v1.1.0'>

    <xs:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0"/>

    <xs:element name="config">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="module" minOccurs="0" maxOccurs="unbounded">
                    <xs:complexType mixed="true">
                        <xs:attribute name="name" use="optional" type="xs:string"/>
                        <xs:attribute name="className" use="required" type="xs:string"/>
                        <xs:attribute name="flags" default="required">
                            <xs:simpleType>
                                <xs:restriction base="xs:NMTOKEN">
                                    <xs:enumeration value="required"/>
                                    <xs:enumeration value="requisite"/>
                                    <xs:enumeration value="sufficient"/>
                                    <xs:enumeration value="optional"/>
                                </xs:restriction>
                            </xs:simpleType>
                        </xs:attribute>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
            <xs:attribute name="name" use="required" type="xs:string"/>
            <xs:attribute name="rank" use="optional" default="0" type="xs:int"/>
        </xs:complexType>
    </xs:element>

    <xs:element name="keystore">
        <xs:complexType>
            <xs:attribute name="name" use="required" type="xs:string"/>
            <xs:attribute name="rank" use="optional" default="0" type="xs:int"/>
            <xs:attribute name="path" use="required" type="xs:string"/>
            <xs:attribute name="keystorePassword" use="optional" type="xs:string"/>
            <xs:attribute name="keyPasswords" use="optional" type="xs:string"/>
        </xs:complexType>
    </xs:element>

</xs:schema>

You can find the schema at the following location.

Here are two examples using this schema:

<?xml version="1.0" encoding="UTF-8"?> 
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:jaas="http://karaf.apache.org/xmlns/jaas/v1.0.0"
           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">

    <type-converters>
        <bean class="org.apache.karaf.jaas.modules.properties.PropertiesConverter"/>
    </type-converters> 

    <!-- Bean to allow the $[karaf.base] property to be correctly resolved -->
    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]"/>

    <jaas:config name="myrealm">
        <jaas:module className="org.apache.karaf.jaas.modules.properties.PropertiesLoginModule" 
                     flags="required">
            users = $[karaf.base]/etc/users.properties
        </jaas:module>
    </jaas:config>

    <service interface="org.apache.karaf.jaas.modules.BackingEngineFactory">
        <bean class="org.apache.karaf.jaas.modules.properties.PropertiesBackingEngineFactory"/>
    </service>

</blueprint>

NB: Don't forge to expose the BackingEngine as an OSGi service.

<jaas:keystore xmlns:jaas="http://karaf.apache.org/xmlns/jaas/v1.1.0"
               name="ks"
               rank="1"
               path="classpath:privatestore.jks"
               keystorePassword="keyStorePassword"
               keyPasswords="myalias=myAliasPassword">
</jaas:keystore>

The id attribute is the blueprint id of the bean, but it will be used by default as the name of the realm if no name attribute is specified. Additional attributes on the config elements are a rank, which is an integer. When the LoginContext looks for a realm for authenticating a given user, the realms registered in the OSGi registry are matched against the required name. If more than one realm is found, the one with the highest rank will be used, thus allowing the override of some realms with new values. The last attribute is publish which can be set to false to not publish the realm in the OSGi registry, thereby disabling the use of this realm.

Each realm can contain one or more module definitions. Each module identifies a LoginModule and the className attribute must be set to the class name of the login module to use. Note that this login module must be available from the bundle classloader, so either it has to be defined in the bundle itself, or the needed package needs to be correctly imported. The flags attribute can take one of four values that are explained on the JAAS documentation.
The content of the module element is parsed as a properties file and will be used to further configure the login module.

Deploying such a code will lead to a JaasRealm object in the OSGi registry, which will then be used when using the JAAS login module.

Configuration override and use of the rank attribute

The rank attribute on the config element is tied to the ranking of the underlying OSGi service. When the JAAS framework performs an authentication, it will use the realm name to find a matching JAAS configuration. If multiple configurations are used, the one with the highest rank attribute will be used.
So if you want to override the default security configuration in Karaf (which is used by the ssh shell, web console and JMX layer), you need to deploy a JAAS configuration with the name name="karaf" and rank="1".

<?xml version="1.0" encoding="UTF-8"?>
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
           xmlns:jaas="http://karaf.apache.org/xmlns/jaas/v1.1.0"
           xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0">

    <!-- Bean to allow the $[karaf.base] property to be correctly resolved -->
    <ext:property-placeholder placeholder-prefix="$[" placeholder-suffix="]"/>

    <type-converters>
        <bean class="org.apache.karaf.jaas.modules.properties.PropertiesConverter"/>
    </type-converters> 

    <jaas:config name="karaf" rank="1">
        <jaas:module className="org.apache.karaf.jaas.modules.properties.PropertiesLoginModule"
                     flags="required">
            users = $[karaf.base]/etc/users.properties
            ...
        </jaas:module>
    </jaas:config>

</blueprint>

Architecture

Due to constraints in the JAAS specification, one class has to be available for all bundles. This class is called ProxyLoginModule and is a LoginModule that acts as a proxy for an OSGi defines LoginModule. If you plan to integrate this feature into another OSGi runtime, this class must be made available from the system classloader and the related package be part of the boot delegation classpath (or be deployed as a fragment attached to the system bundle).

The xml schema defined above allows the use of a simple xml (leveraging spring xml extensibility) to configure and register a JAAS configuration for a given realm. This configuration will be made available into the OSGi registry as a JaasRealm and the OSGi specific Configuration will look for such services. Then the proxy login module will be able to use the information provided by the realm to actually load the class from the bundle containing the real login module.

Available realms

Karaf comes with several login modules to handle authentication needs for your environment.

PropertiesLoginModule

LoginModule org.apache.karaf.jaas.modules.properties.PropertiesLoginModule
BackingEngineFactory org.apache.karaf.jaas.modules.properties.PropertiesBackingEngineFactory

This login module is the one configured by default. It uses a properties text file to load the users, passwords and roles.

Name Description
users location of the properties file

This file uses the properties file format.
The format of the properties is as follows, with each line defining a user, its password and associated roles:

user=password[,role][,role]...
<jaas:config name="karaf">
    <jaas:module className="org.apache.karaf.jaas.modules.properties.PropertiesLoginModule" 
                 flags="required">
        users = $[karaf.base]/etc/users.properties
    </jaas:module>
</jaas:config>

Note for windows users: Do not forget the typeconverter from the first config snippet. It makes sure that windows paths are handled correctly.

OsgiConfigLoginModule

LoginModule org.apache.karaf.jaas.modules.osgi.OsgiConfigLoginModule
BackingEngineFactory

The OsgiConfigLoginModule uses the OSGi ConfigurationAdmin service to provide the users, passwords and roles.

Name Description
pid the PID of the configuration containing user definitions

The format of the configuration is the same than for the PropertiesLoginModule.

JDBCLoginModule

LoginModule org.apache.karaf.jaas.modules.jdbc.JDBCLoginModule
BackingEngineFactory org.apache.karaf.jaas.modules.jdbc.JDBCBackingEngineFactory

The JDBCLoginModule uses a database to load the users, passwords and roles from a provided data source (normal or XA). The data source and the queries for password and role retrieval are configurable using the following parameters.

Name Description
datasource The datasource as on OSGi ldap filter or as JDNI name
query.password The SQL query that retries the password of the user
query.role The SQL query that retries the roles of the user

Passing a data source as an OSGi ldap filter

To use an OSGi ldap filter, the prefix osgi: needs to be provided, as shown below:

<jaas:config name="karaf">
    <jaas:module className="org.apache.karaf.jaas.modules.jdbc.JDBCLoginModule" 
                 flags="required">
        datasource = osgi:javax.sql.DataSource/(osgi.jndi.service.name=jdbc/karafdb)
        query.password = SELECT PASSWORD FROM USERS WHERE USERNAME=?
        query.role = SELECT ROLE FROM ROLES WHERE USERNAME=?
    </jaas:module>
</jaas:config>

Passing a data source as a JNDI name

To use an JNDI name, the prefix jndi: needs to be provided. The example below assumes the use of Aries JNDI to expose services via JNDI.

<jaas:config name="karaf">
    <jaas:module className="org.apache.karaf.jaas.modules.jdbc.JDBCLoginModule" 
                 flags="required">
        datasource = jndi:aries:services/javax.sql.DataSource/(osgi.jndi.service.name=jdbc/karafdb)
        query.password = SELECT PASSWORD FROM USERS WHERE USERNAME=?
        query.role = SELECT ROLE FROM ROLES WHERE USERNAME=?
    </jaas:module>
</jaas:config>

LDAPLoginModule

LoginModule org.apache.karaf.jaas.modules.ldap.LDAPLoginModule
BackingEngineFactory

The LDAPLoginModule uses LDAP to load the users and roles and bind the users on the LDAP to check passwords.

The LDAPLoginModule supports the following parameters:

Name Description
connection.url The LDAP connection URL, e.g. ldap://hostname
connection.username Admin username to connect to the LDAP. This parameter is optional, if it's not provided, the LDAP connection will be anonymous.
connection.password Admin password to connect to the LDAP. Only used if the connection.username is specified.
user.base.dn The LDAP base DN used to looking for user, e.g. ou=user,dc=apache,dc=org
user.filter The LDAP filter used to looking for user, e.g. (uid=%u) where %u will be replaced by the username.
user.search.subtree If "true", the user lookup will be recursive (SUBTREE). If "false", the user lookup will be performed only at the first level (ONELEVEL).
role.base.dn The LDAP base DN used to looking for roles, e.g. ou=role,dc=apache,dc=org
role.filter The LDAP filter used to looking for user's role, e.g. (member:=uid=%u)
role.name.attribute The LDAP role attribute containing the role string used by Karaf, e.g. cn
role.search.subtree If "true", the role lookup will be recursive (SUBTREE). If "false", the role lookup will be performed only at the first level (ONELEVEL).
authentication Define the authentication backend used on the LDAP server. The default is simple.
initial.context.factory Define the initial context factory used to connect to the LDAP server. The default is com.sun.jndi.ldap.LdapCtxFactory
ssl If "true" or if the protocol on the connection.url is ldaps, an SSL connection will be used
ssl.provider The provider name to use for SSL
ssl.protocol The protocol name to use for SSL (SSL for example)
ssl.algorithm The algorithm to use for the KeyManagerFactory and TrustManagerFactory (PKIX for example)
ssl.keystore The key store name to use for SSL. The key store must be deployed using a jaas:keystore configuration.
ssl.keyalias The key alias to use for SSL
ssl.truststore The trust store name to use for SSL. The trust store must be deployed using a jaas:keystore configuration.

A example of LDAPLoginModule usage follows:

<jaas:config name="karaf">
  <jaas:module className="org.apache.karaf.jaas.modules.ldap.LDAPLoginModule" flags="required">
        connection.url = ldap://localhost:389
        user.base.dn = ou=user,dc=apache,dc=org
        user.filter = (cn=%u)
        user.search.subtree = true
        role.base.dn = ou=group,dc=apache,dc=org
        role.filter = (member:=uid=%u)
        role.name.attribute = cn
        role.search.subtree = true
        authentication = simple
  </jaas:module>
</jaas:config>

If you wish to use an SSL connection, the following configuration can be used as an example:

<ext:property-placeholder />

<jaas:config name="karaf" rank="1">
    <jaas:module className="org.apache.karaf.jaas.modules.ldap.LDAPLoginModule" flags="required">
        connection.url = ldaps://localhost:10636
        user.base.dn = ou=users,ou=system
        user.filter = (uid=%u)
        user.search.subtree = true
        role.base.dn = ou=groups,ou=system
        role.filter = (uniqueMember=uid=%u)
        role.name.attribute = cn
        role.search.subtree = true
        authentication = simple
        ssl.protocol=SSL
        ssl.truststore=ks
        ssl.algorithm=PKIX
    </jaas:module>
</jaas:config>

<jaas:keystore name="ks"
               path="file:///${karaf.home}/etc/trusted.ks"
               keystorePassword="secret" />

The LDAPLoginModule supports the following patterns that you can use in the filter (user and role filters):

  • %u is replaced by the user
  • %dn is replaced by the user DN
  • %fqdn is replaced by the user full qualified DN (userDN,userBaseDN).
  • %nsdn is replaced by the userDNNamespace (interesting especially for ActiveDirectory).

Encryption service

The EncryptionService is a service registered in the OSGi registry providing means to encrypt and check encrypted passwords. This service acts as a factory for Encryption objects actually performing the encryption.

This service is used in all Karaf login modules to support encrypted passwords.

Configuring properties

Each login module supports the following additional set of properties:

Name Description
encryption.name Name of the encryption service registered in OSGi (cf. paragraph below)
encryption.enabled Boolean used to turn on encryption
encryption.prefix Prefix for encrypted passwords
encryption.suffix Suffix for encrypted passwords
encryption.algorithm Name of an algorithm to be used for hashing, like "MD5" or "SHA-1"
encryption.encoding Encrypted passwords encoding (can be hexadecimal or base64)
role.policy A policy for identifying roles (can be prefix or group) below)
role.discriminator A discriminator value to be used by the role policy

A simple example follows:

<jaas:config name="karaf">
    <jaas:module className="org.apache.karaf.jaas.modules.properties.PropertiesLoginModule" 
                 flags="required">
        users = $[karaf.base]/etc/users.properties
        encryption.enabled = true
        encryption.algorithm = MD5
        encryption.encoding = hexadecimal
    </jaas:module>
</jaas:config>

Prefix and suffix

The login modules have the ability to support both encrypted and plain passwords at the same time. In some cases, some login modules may be able to encrypt the passwords on the fly and save them back in an encrypted form.

Jasypt

Karaf default installation comes with a simple encryption service which usually fulfills simple needs. However, in some cases, you may need to install the Jasypt library which provides stronger encryption algorithms and more control over them.

To install the Jasypt library, the easiest way is to install the available feature:

karaf@root> features:install jasypt-encryption

It will download and install the required bundles and also register an EncryptionService for Jasypt in the OSGi registry.

When configuring a login module to use Jasypt, you need to specify the encryption.name property and set it to a value of jasypt to make sure the Jasypt encryption service will be used.

In addition to the standard properties above, the Jasypt service provides the following parameters:

Name Description
providerName Name of the java.security.Provider name to use for obtaining the digest algorithm
providerClassName Class name for the security provider to be used for obtaining the digest algorithm
iterations Number of times the hash function will be applied recursively
saltSizeBytes Size of the salt to be used to compute the digest
saltGeneratorClassName Class name of the salt generator

A typical realm definition using Jasypt encryption service would look like:

<jaas:config name="karaf">
    <jaas:module className="org.apache.karaf.jaas.modules.properties.PropertiesLoginModule" 
                 flags="required">
        users = $[karaf.base]/etc/users.properties
        encryption.enabled = true
        encryption.name = jasypt
        encryption.algorithm = SHA-256
        encryption.encoding = base64
        encryption.iterations = 100000
        encryption.saltSizeBytes = 16
    </jaas:module>
</jaas:config>

Role discovery policies

The JAAS specification does not provide means to distinguish between User and Role Principals without referring to the specification classes. In order to provide means to the application developer to decouple the application from Karaf JAAS implementation role policies have been created.

A role policy is a convention that can be adopted by the application in order to identify Roles, without depending from the implementation. Each role policy can be cofigured by setting a "role.policy" and "role.discriminator" property to the login module configuration. Currently, Karaf provides two policies that can be applied to all Karaf Login Modules.

  1. Prefixed Roles
  2. Grouped Roles

Prefixed Roles
When the prefixed role policy is used the login module applies a configurable prefix (property role.discriminator) to the role, so that the application can identify the role's principals by its prefix. Example:

<jaas:config name="karaf">
    <jaas:module className="org.apache.karaf.jaas.modules.properties.PropertiesLoginModule" 
                 flags="required">
        users = $[karaf.base]/etc/users.properties
        role.policy = prefix
        role.discriminator = ROLE_
    </jaas:module>
</jaas:config>

The application can identify the role principals using a snippet like this:

LoginContext ctx = new LoginContext("karaf", handler);
ctx.login();
authenticated = true;
subject = ctx.getSubject();
for (Principal p : subject.getPrincipals()) {
   	if (p.getName().startsWith("ROLE_")) {
   	   	roles.add((p.getName().substring("ROLE_".length())));
   	}
}

Grouped Roles
When the group role policy is used the login module provides all roles as members of a group with a configurable name (property role.discriminator). Example:

<jaas:config name="karaf">
    <jaas:module className="org.apache.karaf.jaas.modules.properties.PropertiesLoginModule" 
                 flags="required">
        users = $[karaf.base]/etc/users.properties
        role.policy = group
        role.discriminator = ROLES
    </jaas:module>
</jaas:config>
LoginContext ctx = new LoginContext("karaf", handler);
ctx.login();
authenticated = true;
subject = ctx.getSubject();
for (Principal p : subject.getPrincipals()) {
    if ((p instanceof Group) && ("ROLES".equalsIgnoreCase(p.getName()))) {
        Group g = (Group) p;
        Enumeration<? extends Principal> members = g.members();
        while (members.hasMoreElements()) {
            Principal member = members.nextElement();
            roles.add(member.getName());
        }
    }
}

Default role policies

The previous section describes how to leverage role policies. However, Karaf provides a default role policy, based on the following class names:

  • org.apache.karaf.jaas.modules.UserPrincipal
  • org.apache.karaf.jaas.modules.RolePrincipal
  • org.apache.karaf.jaas.modules.GroupPrincipal

It allows you to directly handling the role class:

String rolePrincipalClass = "org.apache.karaf.jaas.modules.RolePrincipal";

for (Principal p : subject.getPrincipals()) {
	if (p.getClass().getName().equals(rolePrincipalClass)) {
		roles.add(p.getName());
	}
}