LDAP authentication with Sensu Go: troubleshooting & tips

LDAP

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.

Join the Conversation