Merge pull request #17 from thouveng/add-ldap-support
Add support for LDAP authentication
diff --git a/README.rst b/README.rst
index f806ffe..369b262 100644
--- a/README.rst
+++ b/README.rst
@@ -44,6 +44,83 @@
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.
diff --git a/grafana/files/grafana.ini b/grafana/files/grafana.ini
index 9ae09b9..f2071bb 100644
--- a/grafana/files/grafana.ini
+++ b/grafana/files/grafana.ini
@@ -144,15 +144,15 @@
#################################### 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 @@
#################################### 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 @@
#################################### 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]
diff --git a/grafana/files/ldap.toml b/grafana/files/ldap.toml
new file mode 100644
index 0000000..ff5fb6e
--- /dev/null
+++ b/grafana/files/ldap.toml
@@ -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 %}
diff --git a/grafana/map.jinja b/grafana/map.jinja
index 88ac842..4c9fe60 100644
--- a/grafana/map.jinja
+++ b/grafana/map.jinja
@@ -11,6 +11,16 @@
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
diff --git a/grafana/server.sls b/grafana/server.sls
index 8ab3403..feb4f79 100644
--- a/grafana/server.sls
+++ b/grafana/server.sls
@@ -14,6 +14,20 @@
- 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: