Security Configuration

Once you have configured Lenses, it’s time to set up security.

Security has two important parts: authentication and authorization. Authentication is how users login to Lenses whereas authorization governs the rules of what a user can and can not do. Starting with Lenses 3.0, authorization is configured within the Lenses user interface instead of the security configuration file.

The platform supports a cascading set of authentication modules.

Every authentication module that is configured in the security configuration file, is enabled automatically. Since the Lenses Administrator and Basic modules do not require any configuration, they are always active.

Lenses Administrator

A default account with administrator privileges is always active. The default credentials are admin / admin and if left at default, a notification will show in the user interface, informing that the setup is insecure.

The default account username and password may be adjusted as below. For security purposes it is strongly advised to use your password’s SHA256 checksum instead of the plaintext.

# Lenses Administrator settings

lenses.security.user = "admin"

## For the password you can either use the plaintext
#lenses.security.password = "admin"
## Or you may use the SHA256 checksum (advised)
lenses.security.password = "sha256:8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918"

To create a SHA256 checksum for your password you can use the command line tools available in your Linux server or macOS.

unset HISTFILE # Disable history for the current terminal
echo -n "password" | sha256sum

Note

To disable the Lenses Administrator user, please set an adequately long random password. You can achieve this by using the snippet below:

dd if=/dev/urandom count=1 bs=1024 | sha256sum

Basic

In BASIC mode user accounts are managed by Lenses. Once a user successfully logs in, he is linked to one or more groups and therefore inherits all the permissions attached to the groups he belongs to.

To add, manage and delete users for the basic authentication module visit the Admin section in the Lenses web interface. You will need an account with sufficient privileges to manage users. Alternatively you can add users via the command-line, using the Lenses CLI tool.

The internal database is stored on-disk, under the lenses.storage.directory. Please keep this directory intact between updates and upgrades.

LDAP

For this mode, Lenses relies on the LDAP server to handle the user authentication and retrieve the user groups. Since the authentication is deferred, this means users are stored by LDAP and not Lenses. Once the authentication is successful, the next step involves querying LDAP for the user’s groups. All the user’s groups are then matched by name (case sensitive) with the groups stored in Lenses. All the matching groups’ permissions are combined.

Active Directory (AD) and OpenLDAP (with the memberOf overlay) servers are tested and supported in general. Due to the LDAP standard ambiguity, it is impossible to support all the configurations in the wild. The most usual pain point is group mapping. If the default class that extracts and maps LDAP groups to Lenses groups does not work, it is possible to implement your own.

Before setting up an LDAP connection, we advise to familiarize with LDAP and/or have access to your LDAP and/or Active Directory administrators.

An LDAP setup example is shown below:

# LDAP connection details

lenses.security.ldap.url="ldaps://example.com:636"
## For the LDAP user please use the distinguished name (DN).
## The LDAP user must be able to list users and their groups.
lenses.security.ldap.user="cn=lenses,ou=Services,dc=example,dc=com"
lenses.security.ldap.password="[PASSWORD]"
## When set to true, it uses the lenses.security.ldap.user to read the user's groups
## lenses.security.ldap.use.service.user.search=false

# LDAP user search settings

lenses.security.ldap.base="ou=Users,dc=example,dc=com"
lenses.security.ldap.filter="(&(objectClass=person)(sAMAccountName=<user>))"

# LDAP group search and mapping settings

lenses.security.ldap.plugin.class="io.lenses.security.ldap.LdapMemberOfUserGroupPlugin"
lenses.security.ldap.plugin.group.extract.regex="(?i)CN=(\\w+),ou=Groups.*"
lenses.security.ldap.plugin.memberof.key="memberOf"
lenses.security.ldap.plugin.person.name.key = "sn"

In the example above you can distinguish three key sections for LDAP:

  • the connection settings,
  • the user search settings,
  • and the group search settings.

Each key is explained in detail at the LDAP Configuration Options Table.

Lenses uses the connection settings to connect to your LDAP server. The provided account should be able to list users under the base path and their groups. The default group plugin only needs access to the memberOf attributes for each user, but your custom implementation may need different permissions.

When a user tries to login, a query is sent to the LDAP server for all accounts that are under the lenses.security.ldap.base and match the lenses.security.ldap.filter. The result needs to be unique; a distinguished name (DN) —the user that will login to Lenses.

In the example, the application would query the LDAP server for all entities under ou=Users,dc=example,dc=com that satisfy the LDAP filter (&(objectClass=person)(sAMAccountName=<user>)) where <user> would be replaced by the username that tries to login to Lenses. A more simple filter could be cn=<user>, which for user Mark would return the DN cn=Mark,ou=Users,dc=example,dc=com.

Once the user has been verified, Lenses queries the user groups and maps them to Lenses groups. For every LDAP group that matches a Lenses group, the user is granted the selected permissions. This process is done by the Group Extract Plugin. Depending on the LDAP setup, only one of the user or the Lenses service user may be able to retrieve the group memberships. This can be controlled by the option lenses.security.ldap.use.service.user.search. The default value (false) uses the user itself to query for groups. Groups be can created in the admin section of the web interface, or in the command line via the lenses-cli application.

There are those LDAP setups with stricter security levels which do not allowed for a logged user to list his own groups. In this case the user account set via lenses.security.ldap.user can be used to list the groups for any logged users. Of course that account needs elevated privileges to do so. To enable this behavior set lenses.security.ldap.use.service.user.search to true.

Warning

Set lenses.security.ldap.use.service.user.search to true to use lenses.security.ldap.user account to list a logged user groups when your LDAP setup restricts most of the users action to list their groups.

Group Extract Plugin

The group extract plugin is a class that implements an LDAP query that retrieves a user’s groups and makes any necessary transformation to match the LDAP group to a Lenses group name.

The default class implementation that comes with Lenses is io.lenses.security.ldap.LdapMemberOfUserGroupPlugin. If your LDAP server supports the memberOf functionality, where each user has his/her group memberships added as attributes to his/her entity, you can use it by setting the lenses.security.ldap.plugin.class option to this class:

lenses.security.ldap.plugin.class=io.lenses.security.ldap.LdapMemberOfUserGroupPlugin

The configuration settings for the default group extract plugin can be found at the LDAP Configuration Options Table. Below you will see a brief example of its setup.

# Set the full classpath that implements the group extraction
lenses.security.ldap.plugin.class="io.lenses.security.ldap.LdapMemberOfUserGroupPlugin"

# The plugin uses the 'memberOf' attribute. If this attribute has a different
# name in your LDAP set it here.
lenses.security.ldap.plugin.memberof.key="memberOf"

# This regular expression should return the group common name. If it matches
# a Lenses group name, the user is granted its permissions.
# As an example if there is a 'memberOf' attribute with value:
#   cn=LensesAdmins,ou=Groups,dn=example,dn=com
# The regular expression will return 'LensesAdmins'.
# Group names are case sensitive.
lenses.security.ldap.plugin.group.extract.regex="(?i)cn=(\\w+),ou=Groups.*"

# This is the LDAP attribute that holds the user's full name. It's optional.
lenses.security.ldap.plugin.person.name.key = "sn"

As an example, the memberOf search may return two attributes for user Mark:

attribute  value
---------  ------------------------------------------
memberOf   cn=LensesAdmin,ou=Groups,dc=example,dc=com
memberOf   cn=RandomGroup,ou=Groups,dc=example,dc=com

The regular expression (?i)cn=(\\w+),ou=Groups.* will return these two regex group matches:

LensesAdmin
RandomGroup

If any of these groups exist in Lenses, Mark will be granted the permissions of the matching groups.

To learn more about permissions in Lenses, see the _user_management section.

Custom LDAP Plugin

If your LDAP doesn’t offer the memberOf functionality, or it isn’t enough alone —for example in Active Directory there are groups of groups— you can provide your implementation.

The project template for a custom implementation can be found on GitHub (Lenses LDAP Plugin Template) . Once you build your implementation, drop the jar file into the plugins directory and set the lenses.security.ldap.plugin setting to point to your implementation’s full classpath.

Don’t forget to grant to the Lenses LDAP account any permissions it may need for your plugin to work.

Options Table

LDAP Configuration Options Table
Key Description Optional Type Default
lenses.security.ldap.url
The LDAP server URL. TLS, StartTLS and
unencrypted connections are supported.
Example: ldaps://example.com:636
no string “n/a”
lenses.security.ldap.user
The LDAP account for Lenses. Must be able to
list users and their groups. The distinguished
name (DN) must be used. Example:
cn=lenses,ou=Services,dc=example,dc=com
no string n/a
lenses.security.ldap.password The LDAP account password. so string n/a
lenses.security.ldap.base
The LDAP base path for querying user accounts.
All user accounts that will be able to access
Lenses should be under this path.
Example: ou=Users,dc=example,dc=com
no string n/a
lenses.security.ldap.filter
The LDAP query filter for matching users. Lenses
will request all entries under the base path
that satisfy this filter. The result should be
unique: the user that logs in to Lenses. The
keyword <user> is replaced at runtime with
the Username that requests access.
yes string
(&
 (objectClass=person)
 (sAMAccountName=<user>)
)
lenses.security.ldap.plugin.class
The full classpath for the class that implements
the LDAP query for the user’s groups and
maps them to Lenses groups. You can use
if your LDAP setup is supported.
yes string n/a
lenses.security.ldap.plugin.memberof.key
This key is used by the included LDAP plugin class
LdapMemberOfUserGroupPlugin. It expects the
LDAP user attribute that provides memberOf
information. In most implementations the attribute
has the same name, so you don’t have to set
anything.
yes string memberOf
lenses.security.ldap.plugin.group.extract.regex
This key is used by the included LDAP plugin class
LdapMemberOfUserGroupPlugin. It expects a
regular expression that will be used to extract
a part of the user’s groups. If this part matches a
Lenses group, the user will be granted all the
permissions of this group.
Lenses checks against the list of memberOf
attribute values and uses the first regex group
that is returned.
yes string (?i)CN=(\\w+),ou=Groups.*
lenses.security.ldap.plugin.person.name.key
This key is used by the included LDAP plugin class
LdapMemberOfUserGroupPlugin. It expects the
LDAP user attribute that provides the full name
of the user.
yes string sn
lenses.security.ldap.use.service.user.search
If set to true it uses the
lenses.security.ldap.user account to read the
groups of the current logged user. The default
behaviour (false) uses the current logged
user to read group memberships.
yes boolean false

Note

The configuration entries lenses.security.ldap.plugin.memberof.key, lenses.security.ldap.plugin.person.name.key, lenses.security.ldap.plugin.group.extract.regex, and lenses.security.ldap.plugin.person.name.key are specific to the default plugin class that comes with Lenses. A custom implementation may require different entries under lenses.security.ldap.plugin

Kerberos (SPNEGO)

In Kerberos mode, your browser will use the Simple and Protected GSSAPI Negotiation Mechanism (SPNEGO) to login to Lenses.

Before setting up Lenses with SPNEGO, it is important to make sure your browser does support this mechanism, and you are adequately familiar with the protocol and you have access to your Kerberos and/or Active Directory administrators.

Let’s look at an example of a Kerberos security setup:

# Kerberos settings

lenses.security.kerberos.service.principal="HTTP/lenses.url[@REALM]"
lenses.security.kerberos.keytab=/path/to/lenses.keytab

The SPNEGO-specific configuration is straightforward, you have to point Lenses to a password-less Kerberos keytab and specify the principal to use.

Your system should also provide a system-wide Kerberos configuration. Usually, for Linux distributions, this resides in the file /etc/krb5.conf which is populated with information about your KDC, Kerberos realm and other authentication-related settings. If there isn’t a global krb5.conf that Lenses can use, please ask your Kerberos administrator for one, then point Lenses to it via the LENSES_OPT environment variable:

export LENSES_OPTS="-Djava.security.krb5.conf=/path/to/krb5.conf

Once setup, your users should be automatically logged in whenever they visit the Lenses web interface or make a call to the /api/auth REST endpoint. If you are on a Microsoft Windows system, then logging into your Windows domain is usually sufficient to issue your Kerberos credentials. On a Linux environment, if you use Kerberos with PAM, your Kerberos credentials should be already available to Kerberos-enabled browsers. Otherwise, you will typically need to authenticate to the KDC manually using kinit at the command line and start your browser from the same terminal.

Group Mapping

A shortcoming of Kerberos is that by design it is just an authentication mechanism. It doesn’t provide any information about the user it authenticates except the user’s principal. As such it falls to the Lenses administrator to map users to groups.

This is done by adding the same user id to Lenses and link it to at least one group. To learn more about the permission model of Lenses, see the User Management section.

SPNEGO Principal

The Kerberos principal is not random in the SPNEGO protocol. When your browser tries to connect to a SPNEGO service it needs a ticket for it which is granted by the Key Distribution Center (KDC). In SPNEGO, the browser will always request a key from the KDC in the format HTTP/service.url@REALM. So, if your service is at service.example.com and your realm is EXAMPLE.COM, your browser will always request a ticket for the principal HTTP/service.example.com@EXAMPLE.COM. The realm usually can be omitted as it is part of the system-wide Kerberos settings.

To repeat one more time, if you setup Lenses at lenses.example.com, you need to ask your Kerberos or Active Directory administrator to create the principal HTTP/lenses.example.com and provide you with a password-less keytab for it.

Options Table

Kerberos Configuration Options Table
Key Description Optional Type Default
lenses.security.kerberos.service.principal
The Kerberos principal Lenses should use.
It must be present in the keytab and in
form below (SPNEGO requirement):
HTTP/lenses.address@REALM.COM
no string n/a
lenses.security.kerberos.keytab
Path to a Kerberos keytab that contains
the service principal. It should not
be password protected.
no string (path) n/a
lenses.security.kerberos.debug
Enable Java’s JAAS debugging information.
yes boolean false

Custom HTTP

In this mode, a custom, user-provided, class takes over authentication and authorization to Lenses using the users’ HTTP request headers. The authentication layer is separated from Lenses into your authentication solution. As an example, it would be possible to use JSON Web Tokens (JWT) injected via an authentication-proxy in-front of Lenses or any other single sign-on tokens your organization may use. The user-implemented class should process the request HTTP headers to extract the tokens and any other information it needs, ideally verifies them, then provides Lenses with the username and groups of the user.

An example of a security configuration with Custom HTTP:

# Just one setting for CUSTOM_HTTP, the full classpath
# of the security plugin implementation

lenses.security.plugin=my.custom.plugin.class.path

The only available option for this mode is lenses.security.plugin, which should point to the full Java classpath of the class implementing the required interface (HttpAuthenticationPlugin). Once you build your code, you can drop the required jar(s) inside the plugins directory or a subdirectory under it.

The interface of the Custom HTTP class:

public interface HttpAuthenticationPlugin {
    UserAndGroups authenticate(HttpRequest request);
}

The return object UserAndGroups should contain the username accepted to log in and the groups the person belongs to or raise an exception if the user is not allowed. To learn more about the permissions model of Lenses, check _user_management. To implement the interface, you need to create a project where this maven dependency needs to be added (here is the example for a Maven project):

<dependency>
    <groupId>com.landoop</groupId>
    <artifactId>lenses-security-http-plugin</artifactId>
    <version>1.0.0</version>
</dependency>

Example Code

A sample implementation can be found at GitHub. This code expects the header API_KEY based on which it performs a lookup to a mocked user-store. Let’s explore this example a bit further.

If you check the HeaderTokenAuthPlugin.java file inside the repository you will notice that the package has a classpath of io.lenses.security.auth.http.custom, therefore the full java classpath would be io.lenses.security.auth.http.custom.HeaderTokenAuthPlugin.

/*
First line of HeaderTokenAuthPlugin.java
*/

package io.lenses.security.auth.http.custom;

The expected header key is API_KEY [1]:

/*
First entry under public class in HeaderTokenAuthPlugin.java
*/

public class HeaderTokenAuthPlugin implements HttpAuthenticationPlugin {

  private static final String API_HEADER_NAME = "API_KEY";

The expected key values are one of [KEY-ADMIN, KEY-WRITE, KEY-READ]. Each value links to a different pair of user & group as shown under UserAndGroups.java.

/*
Entries under public class in UserAndGroups.java
*/

public class UserStore {

    private final Map<String, UserAndGroups> users = new HashMap<>();

    {
        users.put("KEY-ADMIN", new UserAndGroups("AdminUser", Collections.singleton("adminGroup")));
        users.put("KEY-WRITE", new UserAndGroups("WriteUser", Collections.singleton("writeGroup")));
        users.put("KEY-READ", new UserAndGroups("ReadUser", Collections.singleton("readGroup")));
        users.put("KEY-NODATA", new UserAndGroups("NoData", Collections.singleton("nodataGroup")));
    }

The security.conf syntax for the example above could be:

lenses.security.plugin=io.lenses.security.auth.http.custom.HeaderTokenAuthPlugin

The jar produced should be dropped inside the plugins directory. If you are using Lenses Box for development and testing, you may drop it under /plugins or /opt/lenses/plugins.

To test that everything works as expected, execute the following curl command.

curl --header "API_KEY: KEY-ADMIN" --compressed http://lenses.url/api/auth
[1]HTTP headers are case-insensitive. RFC 2616, section 4.2.