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: