From 377c14cca0ff1cd5c0b2a292f28b96ee5408273e Mon Sep 17 00:00:00 2001 From: Guillaume Thouvenin Date: Mon, 5 Dec 2016 13:11:53 +0100 Subject: [PATCH] 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. --- README.rst | 77 +++++++++++++++++++++++++++ grafana/files/grafana.ini | 26 +++++---- grafana/files/ldap.toml | 108 ++++++++++++++++++++++++++++++++++++++ grafana/map.jinja | 10 ++++ grafana/server.sls | 14 +++++ 5 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 grafana/files/ldap.toml diff --git a/README.rst b/README.rst index f806ffe..369b262 100644 --- a/README.rst +++ b/README.rst @@ -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. 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 @@ 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] 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 @@ 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 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 @@ 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: