Add support for LDAP authentication

This patch adds support for LDAP authentication. It also adds support
to manage authorization. It is now possible to enable several kind of
authentication like LDAP and basic auth. So we introduce a new schema
for allowing it:

  auth:
    basic:
      enabled: true
    ldap:
      enabled: true
      [...]

instead of

  auth:
    engine: basic

The former declaration is still valid for basic, anonymous and proxy
authentication.
pull/17/head
Guillaume Thouvenin 7 years ago
parent 884f1a179e
commit 377c14cca0
  1. 77
      README.rst
  2. 26
      grafana/files/grafana.ini
  3. 108
      grafana/files/ldap.toml
  4. 10
      grafana/map.jinja
  5. 14
      grafana/server.sls

@ -44,6 +44,83 @@ Server installed with PostgreSQL database
user: grafana
password: passwd
Server installed with LDAP authentication and all authenticated users are
administrators
.. code-block:: yaml
grafana:
server:
enabled: true
admin:
user: admin
password: passwd
auth:
ldap:
enabled: true
host: '127.0.0.1'
port: 389
use_ssl: false
bind_dn: "cn=admin,dc=grafana,dc=org"
bind_password: "grafana"
user_search_filter: "(cn=%s)"
user_search_base_dns:
- "dc=grafana,dc=org"
Server installed with LDAP and basic authentication
.. code-block:: yaml
grafana:
server:
enabled: true
admin:
user: admin
password: passwd
auth:
basic:
enabled: true
ldap:
enabled: true
host: '127.0.0.1'
port: 389
use_ssl: false
bind_dn: "cn=admin,dc=grafana,dc=org"
bind_password: "grafana"
user_search_filter: "(cn=%s)"
user_search_base_dns:
- "dc=grafana,dc=org"
Server installed with LDAP for authentication and authorization
.. code-block:: yaml
grafana:
server:
enabled: true
admin:
user: admin
password: passwd
auth:
ldap:
enabled: true
host: '127.0.0.1'
port: 389
use_ssl: false
bind_dn: "cn=admin,dc=grafana,dc=org"
bind_password: "grafana"
user_search_filter: "(cn=%s)"
user_search_base_dns:
- "dc=grafana,dc=org"
group_search_filter: "(&(objectClass=posixGroup)(memberUid=%s))"
group_search_base_dns:
- "ou=groups,dc=grafana,dc=org"
authorization:
enabled: true
admin_group: "admins"
editor_group: "editors"
viewer_group: "viewers"
Server installed with default StackLight JSON dashboards. This will
be replaced by the possibility for a service to provide its own dashboard
using salt-mine.

@ -144,15 +144,15 @@ auto_assign_org_role = {{ server.auto_assign_role }}
#################################### Anonymous Auth ##########################
[auth.anonymous]
{%- if server.auth.engine == 'anonymous' %}
{%- if server.auth.engine == 'anonymous' or server.auth.get('anonymous', {}).get('enabled', False) %}
enabled = true
{%- if server.auth.organization is defined %}
org_name = {{ server.auth.organization }}
{%- if server.auth.organization is defined or server.auth.anonymous.organization is defined %}
org_name = {{ server.auth.get('organization', server.auth.anonymous.organization) }}
{%- endif %}
{%- if server.auth.role is defined %}
org_name = {{ server.auth.role }}
{%- if server.auth.role is defined or server.auth.anonymous.role is defined %}
org_name = {{ server.auth.get('role', server.auth.anonymous.role) }}
{%- endif %}
{%- else %}
@ -193,16 +193,16 @@ org_name = {{ server.auth.role }}
#################################### Auth Proxy ##########################
[auth.proxy]
{%- if server.auth.engine == 'proxy' %}
{%- if server.auth.engine == 'proxy' or server.auth.get('proxy', {}).get('enabled', False) %}
enabled = true
header_name = {{ server.auth.get('header', 'X-Forwarded-User') }}
header_property = {{ server.auth.get('header_property', 'username') }}
header_name = {{ server.auth.get('proxy', {}).get('header', server.auth.get('header', 'X-Forwarded-User')) }}
header_property = {{ server.auth.get('proxy', {}).get('header_property', server.auth.get('header_property', 'username')) }}
auto_sign_up = true
{%- endif %}
#################################### Basic Auth ##########################
[auth.basic]
{%- if server.auth.engine == 'basic' %}
{%- if server.auth.engine == 'basic' or server.auth.get('basic', {}).get('enabled', False) %}
enabled = true
{%- else %}
enabled = false
@ -210,8 +210,12 @@ enabled = false
#################################### Auth LDAP ##########################
[auth.ldap]
;enabled = false
;config_file = /etc/grafana/ldap.toml
{%- if server.auth.get('ldap', {}).get('enabled', False) %}
enabled = true
config_file = /etc/grafana/ldap.toml
{%- else %}
enabled = false
{%- endif %}
#################################### SMTP / Emailing ##########################
[smtp]

@ -0,0 +1,108 @@
{%- from "grafana/map.jinja" import server with context %}
{%- set ldap_params = server.auth.ldap %}
# Set to true to log user information returned from LDAP
verbose_logging = false
[[servers]]
# Ldap server host (specify multiple hosts space separated)
host = "{{ ldap_params.host }}"
# Default port is 389 or 636 if use_ssl = true
port = {{ ldap_params.port }}
# Set to true if ldap server supports TLS
use_ssl = {{ ldap_params.use_ssl|lower }}
# Set to true if connect ldap server with STARTTLS pattern (create connection in insecure, then upgrade to secure connection with TLS)
start_tls = false
# set to true if you want to skip ssl cert validation
ssl_skip_verify = false
# set to the path to your root CA certificate or leave unset to use system defaults
# root_ca_cert = /path/to/certificate.crt
# Search user bind dn
bind_dn = "{{ ldap_params.bind_dn }}"
# Search user bind password
bind_password = "{{ ldap_params.bind_password }}"
# User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"
search_filter = "{{ ldap_params.user_search_filter }}"
# An array of base dns to search through
search_base_dns = {{ ldap_params.user_search_base_dns }}
# In POSIX LDAP schemas, without memberOf attribute a secondary query must be made for groups.
# This is done by enabling group_search_filter below. You must also set member_of= "cn"
# in [servers.attributes] below.
# Users with nested/recursive group membership and an LDAP server that supports LDAP_MATCHING_RULE_IN_CHAIN
# can set group_search_filter, group_search_filter_user_attribute, group_search_base_dns and member_of
# below in such a way that the user's recursive group membership is considered.
#
# Nested Groups + Active Directory (AD) Example:
#
# AD groups store the Distinguished Names (DNs) of members, so your filter must
# recursively search your groups for the authenticating user's DN. For example:
#
# group_search_filter = "(member:1.2.840.113556.1.4.1941:=%s)"
# group_search_filter_user_attribute = "distinguishedName"
# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
#
# [servers.attributes]
# ...
# member_of = "distinguishedName"
## Group search filter, to retrieve the groups of which the user is a member (only set if memberOf attribute is not available)
# group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"
## Group search filter user attribute defines what user attribute gets substituted for %s in group_search_filter.
## Defaults to the value of username in [server.attributes]
## Valid options are any of your values in [servers.attributes]
## If you are using nested groups you probably want to set this and member_of in
## [servers.attributes] to "distinguishedName"
# group_search_filter_user_attribute = "distinguishedName"
## An array of the base DNs to search through for groups. Typically uses ou=groups
# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
# Specify names of the ldap attributes your ldap uses
{%- if ldap_params.group_search_filter is defined %}
group_search_filter = "{{ ldap_params.group_search_filter }}"
{%- endif %}
{%- if ldap_params.group_search_base_dns is defined %}
group_search_base_dns = {{ ldap_params.group_search_base_dns }}
{%- endif %}
[servers.attributes]
name = "givenName"
surname = "sn"
username = "cn"
member_of = "memberOf"
email = "email"
{%- if ldap_params.get('authorization', {}).get('enabled', False) %}
# Map ldap groups to grafana org roles
{%- if ldap_params.authorization.admin_group is defined %}
[[servers.group_mappings]]
group_dn = "{{ ldap_params.authorization.admin_group }}"
org_role = "Admin"
# The Grafana organization database id, optional, if left out the default org (id 1) will be used
# org_id = 1
{%- endif %}
{%- if ldap_params.authorization.editor_group is defined %}
[[servers.group_mappings]]
group_dn = "{{ ldap_params.authorization.editor_group }}"
org_role = "Editor"
{%- endif %}
{%- if ldap_params.authorization.viewer_group is defined %}
[[servers.group_mappings]]
# If you want to match all (or no ldap groups) then you can use wildcard
group_dn = "{{ ldap_params.authorization.viewer_group }}"
org_role = "Viewer"
{%- endif %}
{%- else %}
{# Every user that can be authenticated is an admin #}
[[servers.group_mappings]]
group_dn = "*"
org_role = "Admin"
{%- endif %}

@ -11,6 +11,16 @@ Debian:
engine: file
auth:
engine: application
ldap:
enabled: false
host: '127.0.0.1'
port: 389
use_ssl: false
bind_dn: "cn=admin,dc=grafana,dc=org"
bind_password: "grafana"
user_search_filter: "(cn=%s)"
user_search_base_dns:
- "dc=grafana,dc=org"
admin:
user: admin
password: admin

@ -14,6 +14,20 @@ grafana_packages:
- require:
- pkg: grafana_packages
{%- if server.auth.get('ldap', {}).get('enabled', False) %}
/etc/grafana/ldap.toml:
file.managed:
- source: salt://grafana/files/ldap.toml
- template: jinja
- user: grafana
- group: grafana
- require:
- pkg: grafana_packages
- watch_in:
- service: grafana_service
{%- endif %}
{%- if server.dashboards.enabled %}
grafana_copy_default_dashboards:

Loading…
Cancel
Save