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¶
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 paththat satisfy this filter. The result should be
unique: the user that logs in to Lenses. The
keyword
<user> is replaced at runtime withthe
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 theLDAP 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 aregular 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 theLDAP 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 thegroups of the current logged user. The default
behaviour (
false ) uses the current loggeduser 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¶
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. |