While many of us understand the need for strong passwords, we also know that most people don’t want to use a unique and highly complex password for each of the multitude of services they need to authenticate to. One way to get around this problem — avoiding password reuse or the use of weak (easy-to-remember) passwords — is to use single-sign on (SSO). As the name implies, SSO allows for users to log on to various related — but independent — services using the same username and password.
SSO is often accomplished by using Lightweight Directory Access Protocol (LDAP). While LDAP has a variety of use cases, in this post, I’ll focus on authentication — specifically, how to use LDAP authentication for single-sign on (SSO) with Sensu Go.
First off, a quick overview of Sensu authentication.
Authenticating with Sensu Go
To access the Sensu web UI, and sensuctl (the Sensu command-line tool), you’ll need to authenticate via a username and password. You can also use username and password to access the API, but we suggest using API keys for that. You can do this either with Sensu’s built-in authentication or via an external authentication, which we’ll go into more detail later.
Using Sensu’s built-in authentication, you can create and manage credentials (usernames and passwords) with the users API, either directly or via sensuctl. Once you’ve created users, you can manage their access via role-based access control (RBAC).
Here’s an example RBAC profile (for ClusterRole and ClusterRoleBinding) for Sensu Go:
---
type: ClusterRole
api_version: core/v2
metadata:
name: sensu:sales
spec:
rules:
- resources:
- assets
verbs:
- get
- list
- resources:
- handlers
verbs:
- get
- list
- resources:
- checks
- entities
- events
- filters
- hooks
- mutators
- silenced
verbs:
- get
- list
- create
- update
- delete
---
type: ClusterRoleBinding
api_version: core/v2
metadata:
name: sensu:sales
spec:
role_ref:
type: ClusterRole
name: sensu:sales
subjects:
- type: Group
name: sensu.io:sales
Note that in this example, the group name sensu.io:sales
, instead of just sales
. You’ll need this prefix if you configure the groups_prefix
attribute in the LDAP provider (more on that below).
Authenticating with LDAP
In addition to Sensu’s built-in authentication, you can authenticate via external providers, including Microsoft Active Directory and LDAP. Note that support for external authentication providers is a commercial feature. Learn more about commercial features.
As noted earlier, we’ll be focusing on LDAP authentication for this post. For information on Active Directory authentication, check out our documentation.
Here’s an example LDAP configuration with Sensu:
type: ldap
api_version: authentication/v2
metadata:
name: openldap
spec:
groups_prefix: ldap
servers:
- binding:
password: BINDING_PASSWORD
user_dn: cn=binder,dc=acme,dc=org
client_cert_file: /path/to/ssl/cert.pem
client_key_file: /path/to/ssl/key.pem
group_search:
attribute: member
base_dn: dc=acme,dc=org
name_attribute: cn
object_class: groupOfNames
host: 127.0.0.1
insecure: false
port: 636
security: tls
trusted_ca_file: /path/to/trusted-certificate-authorities.pem
user_search:
attribute: uid
base_dn: dc=acme,dc=org
name_attribute: cn
object_class: person
username_prefix: ldap
I won’t delve into the details of this configuration, but if you’ve done any work with LDAP in the past most of these attributes should be straightforward. The two Sensu specific outliers that I would like to mention are the optional, but highly suggested, groups_prefix and username_prefix. These are useful for differentiating the sources of users and groups when defining role bindings and cluster role bindings.
For more examples and a deep-dive of LDAP attributes, check out our documentation.
LDAP troubleshooting tips
Our docs have a whole section on LDAP troubleshooting, with commonly seen authentication and authorization errors. While we won’t go into all of those here, we will share some LDAP troubleshooting best practices that Sensu CEO Caleb Hailey posted in Discourse, our Community Forum.
First off, the ldapsearch
utility is your friend, allowing you to query an LDAP server to troubleshoot connectivity issues. Here’s an example ldapsearch
query:
$ ldapsearch -x -H 'ldap://ldap.yourcompany.com:636' \
-b 'dc=sensu,dc=io' '(mail=username@sensu.io)' \
-D username -w password
Note: Earlier versions of Sensu (pre-5.6.0) do not support LDAPS, but it’s still possible with stunnel
. Check out Caleb’s Discourse post for an example of how this would work.
Sensu needs to be configured to look up users via a configurable attribute, like an email address or username. It also needs to know how to perform group searches, which is also configurable. Here’s an example LDAP Provider template for Sensu Go and Google G Suite’s Google Cloud Identity LDAP service:
---
type: ldap
api_version: authentication/v2
metadata:
name: gsuite-ldap
spec:
servers:
- host: ldap.google.com
port: 636
insecure: false
security: tls
client_cert_file: /etc/sensu/ldap/gsuite-ldap.crt
client_key_file: /etc/sensu/ldap/gsuite-ldap.key
binding:
user_dn: username
password: password
group_search:
base_dn: dc=sensu,dc=io
# attribute: member
# name_attribute: cn
# object_class: memberOf
user_search:
base_dn: dc=sensu,dc=io
attribute: mail
# name_attribute: displayName
# object_class: uid
groups_prefix: sensu.io
# username_prefix: gsuite-ldap
Because Sensu supports multiple SSO providers, we’ve made it so you can avoid group membership name collisions (i.e., if two SSO providers both have “engineering” groups, and those group members need different levels of access). As noted earlier in our RBAC profile example, you can configure an optional groups_prefix
to help avoid those collisions.
Last but not least, Sensu has detailed debug logging to help troubleshoot any SSO or LDAP configuration issues. Here’s an example excerpt (ran through jq
. for formatting):
{
"component": "authentication",
"error": "key xxxxxxxx@sensu.io not found",
"level": "debug",
"msg": "could not authenticate with provider \"basic\"",
"time": "2020-04-25T00:21:48Z"
}
{
"component": "authentication/v2",
"level": "debug",
"msg": "using ldap server ldap.google.com:636",
"time": "2020-04-25T00:21:48Z"
}
{
"component": "authentication/v2",
"level": "debug",
"msg": "running ldap search with basedn \"dc=sensu,dc=io\" and filter \"(&(objectClass=person)(mail=xxxxxxxx@sensu.io))\"",
"time": "2020-04-25T00:21:48Z"
}
{
"component": "authentication/v2",
"level": "debug",
"msg": "username \"xxxxxxxx@sensu.io\" mapped to entry \"uid=xxxxxxxx,ou=Employees,ou=Users,dc=sensu,dc=io\"",
"time": "2020-04-25T00:21:48Z"
}
{
"component": "authentication/v2",
"level": "debug",
"msg": "running ldap search with basedn \"dc=sensu,dc=io\" and filter \"(&(objectclass=groupOfNames)(member=uid=xxxxxxxx,ou=Employees,ou=Users,dc=sensu,dc=io))\"",
"time": "2020-04-25T00:21:48Z"
}
{
"component": "authentication/v2",
"level": "debug",
"msg": "found 13 group(s): [\"xxxxxxxx\" \"xxxxxxxx\" \"xxxxxxxx\" \"xxxxxxxx\" \"xxxxxxxx\" \"xxxxxxxx\" \"xxxxxxxx\" \"xxxxxxxx\" \"xxxxxxxx\" \"xxxxxxxx\" \"sales\" \"xxxxxxxx\" \"xxxxxxxx\"]",
"time": "2020-04-25T00:21:49Z"
}
{
"component": "apid",
"duration": "2534.143ms",
"level": "info",
"method": "GET",
"msg": "request completed",
"path": "/auth",
"size": 1091,
"status": 200,
"time": "2020-04-25T00:21:50Z"
}
{
"component": "backend.api",
"level": "debug",
"msg": "request authorized by the binding sensu:sales",
"time": "2020-04-25T00:21:51Z",
"zz_request": {
"apiGroup": "core",
"apiVersion": "v2",
"namespace": "default",
"resource": "namespaces",
"resourceName": "default",
"username": "xxxxxxxx",
"verb": "get"
}
}
{
"component": "backend.api",
"level": "debug",
"msg": "all namespaces implicitly authorized by the binding sensu:sales",
"time": "2020-04-25T00:21:51Z",
"zz_request": {
"apiGroup": "core",
"apiVersion": "v2",
"namespace": "",
"resource": "namespaces",
"resourceName": "",
"username": "xxxxxxxx",
"verb": "list"
}
}
{
"component": "backend.api",
"level": "debug",
"msg": "request authorized by the binding sensu:sales",
"time": "2020-04-25T00:21:51Z",
"zz_request": {
"apiGroup": "core",
"apiVersion": "v2",
"namespace": "default",
"resource": "namespaces",
"resourceName": "default",
"username": "xxxxxxxx",
"verb": "get"
}
}
{
"component": "rbac",
"level": "debug",
"msg": "request authorized by the binding sensu:sales",
"time": "2020-04-25T00:21:51Z",
"zz_request": {
"apiGroup": "core",
"apiVersion": "v2",
"namespace": "default",
"resource": "events",
"resourceName": "",
"username": "xxxxxxxx",
"verb": "list"
}
}
{
"component": "backend.api",
"level": "debug",
"msg": "request authorized by the binding sensu:sales",
"time": "2020-04-25T00:21:51Z",
"zz_request": {
"apiGroup": "core",
"apiVersion": "v2",
"namespace": "default",
"resource": "namespaces",
"resourceName": "default",
"username": "xxxxxxxx",
"verb": "get"
}
}
{
"component": "rbac",
"level": "debug",
"msg": "request authorized by the binding sensu:sales",
"time": "2020-04-25T00:21:51Z",
"zz_request": {
"apiGroup": "core",
"apiVersion": "v2",
"namespace": "default",
"resource": "events",
"resourceName": "",
"username": "xxxxxxxx",
"verb": "list"
}
}
{
"component": "rbac",
"level": "debug",
"msg": "request authorized by the binding sensu:sales",
"time": "2020-04-25T00:21:51Z",
"zz_request": {
"apiGroup": "core",
"apiVersion": "v2",
"namespace": "default",
"resource": "entities",
"resourceName": "",
"username": "xxxxxxxx",
"verb": "list"
}
}
If you’re not familiar with using jq
for formatting JSON, you can learn more about it in the jq manual.
Looking closely at this example, you can see that Sensu first attempts to authenticate the user against the built-in “basic” provider (Sensu’s built-in username+password auth provider) and returns the error: "could not authenticate with provider \"basic\""
, and then proceeds to attempt authentication against the other configured provider(s) — in this case, an LDAP provider.
That’s all for now, but we welcome you to drop any further questions in the Discourse thread.