瀏覽代碼

More work on ldap auth, got memberOf working in the docker ldap test server, playing with config options and structures, #1450

Torkel Ödegaard 10 年之前
父節點
當前提交
bfe7b77313

+ 9 - 1
conf/defaults.ini

@@ -185,10 +185,18 @@ enabled = true
 hosts = ldap://127.0.0.1:389
 use_ssl = false
 bind_path = cn=%s,dc=grafana,dc=org
+bind_password =
+search_bases = dc=grafana,dc=org
+search_filter = (cn=%s)
 attr_username = cn
-attr_name = cn
+attr_name = givenName
 attr_surname = sn
 attr_email = email
+attr_member_of = memberOf
+
+[auth.ldap.member.to.role.map]
+-: cn=admins,dc=grafana,dc=org -> "Admin" in "Main Org."
+-: cn=users,dc=grafana,dc=org  -> "Viewer" in "Main Org."
 
 #################################### SMTP / Emailing ##########################
 [smtp]

+ 15 - 23
docker/blocks/openldap/Dockerfile

@@ -1,33 +1,25 @@
-FROM phusion/baseimage:0.9.8
-MAINTAINER Nick Stenning <nick@whiteink.com>
+FROM debian:jessie
 
-ENV HOME /root
+MAINTAINER Christian Luginbühl <dinke@pimprecords.com>
 
-# Disable SSH
-RUN rm -rf /etc/service/sshd /etc/my_init.d/00_regen_ssh_host_keys.sh
+ENV OPENLDAP_VERSION 2.4.40
 
-# Use baseimage-docker's init system.
-CMD ["/sbin/my_init"]
+RUN apt-get update && \
+    DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y \
+        slapd=${OPENLDAP_VERSION}* && \
+    apt-get clean && \
+    rm -rf /var/lib/apt/lists/*
 
-# Configure apt
-RUN echo 'deb http://us.archive.ubuntu.com/ubuntu/ precise universe' >> /etc/apt/sources.list
-RUN apt-get -y update
-
-# Install slapd
-RUN LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y slapd
-
-# Default configuration: can be overridden at the docker command line
-ENV LDAP_ROOTPASS toor
-ENV LDAP_ORG Acme Widgets Inc.
-ENV LDAP_DOMAIN example.com
+RUN mv /etc/ldap /etc/ldap.dist
 
 EXPOSE 389
 
-RUN mkdir /etc/service/slapd
-ADD slapd.sh /etc/service/slapd/run
+VOLUME ["/etc/ldap", "/var/lib/ldap"]
+
+COPY modules/ /etc/ldap.dist/modules
 
-# To store the data outside the container, mount /var/lib/ldap as a data volume
+COPY entrypoint.sh /entrypoint.sh
 
-RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+ENTRYPOINT ["/entrypoint.sh"]
 
-# vim:ts=8:noet:
+CMD ["slapd", "-d", "32768", "-u", "openldap", "-g", "openldap"]

+ 2 - 1
docker/blocks/openldap/fig

@@ -1,8 +1,9 @@
 openldap:
-  image: cnry/openldap
+  build: blocks/openldap
   environment:
     SLAPD_PASSWORD: grafana
     SLAPD_DOMAIN: grafana.org
+    SLAPD_ADDITIONAL_MODULES: memberof
   ports:
     - "389:389"
 

+ 0 - 42
docker/blocks/openldap/slapd.sh

@@ -1,42 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-status () {
-  echo "---> ${@}" >&2
-}
-
-set -x
-: LDAP_ROOTPASS=${LDAP_ROOTPASS}
-: LDAP_DOMAIN=${LDAP_DOMAIN}
-: LDAP_ORGANISATION=${LDAP_ORGANISATION}
-
-if [ ! -e /var/lib/ldap/docker_bootstrapped ]; then
-  status "configuring slapd for first run"
-
-  cat <<EOF | debconf-set-selections
-slapd slapd/internal/generated_adminpw password ${LDAP_ROOTPASS}
-slapd slapd/internal/adminpw password ${LDAP_ROOTPASS}
-slapd slapd/password2 password ${LDAP_ROOTPASS}
-slapd slapd/password1 password ${LDAP_ROOTPASS}
-slapd slapd/dump_database_destdir string /var/backups/slapd-VERSION
-slapd slapd/domain string ${LDAP_DOMAIN}
-slapd shared/organization string ${LDAP_ORGANISATION}
-slapd slapd/backend string HDB
-slapd slapd/purge_database boolean true
-slapd slapd/move_old_database boolean true
-slapd slapd/allow_ldap_v2 boolean false
-slapd slapd/no_configuration boolean false
-slapd slapd/dump_database select when needed
-EOF
-
-  dpkg-reconfigure -f noninteractive slapd
-
-  touch /var/lib/ldap/docker_bootstrapped
-else
-  status "found already-configured slapd"
-fi
-
-status "starting slapd"
-set -x
-exec /usr/sbin/slapd -h "ldap:///" -u openldap -g openldap -d 0

+ 0 - 56
pkg/api/ldapauth/ldapauth.go

@@ -1,56 +0,0 @@
-package ldapauth
-
-import (
-	"errors"
-	"fmt"
-	"net/url"
-
-	"github.com/go-ldap/ldap"
-	"github.com/grafana/grafana/pkg/log"
-	"github.com/grafana/grafana/pkg/setting"
-)
-
-var (
-	ErrInvalidCredentials = errors.New("Invalid Username or Password")
-)
-
-func Login(username, password string) error {
-	url, err := url.Parse(setting.LdapHosts[0])
-	if err != nil {
-		return err
-	}
-
-	log.Info("Host: %v", url.Host)
-	conn, err := ldap.Dial("tcp", url.Host)
-	if err != nil {
-		return err
-	}
-
-	defer conn.Close()
-
-	bindFormat := "cn=%s,dc=grafana,dc=org"
-
-	nx := fmt.Sprintf(bindFormat, username)
-	err = conn.Bind(nx, password)
-
-	if err != nil {
-		if ldapErr, ok := err.(*ldap.Error); ok {
-			if ldapErr.ResultCode == 49 {
-				return ErrInvalidCredentials
-			}
-		}
-		return err
-	}
-	return nil
-
-	// search := ldap.NewSearchRequest(url.Path,
-	// 	ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
-	// 	fmt.Sprintf(ls.Filter, name),
-	// 	[]string{ls.AttributeUsername, ls.AttributeName, ls.AttributeSurname, ls.AttributeMail},
-	// 	nil)
-	// sr, err := l.Search(search)
-	// if err != nil {
-	// 	log.Debug("LDAP Authen OK but not in filter %s", name)
-	// 	return "", "", "", "", false
-	// }
-}

+ 7 - 27
pkg/auth/auth.go

@@ -13,32 +13,6 @@ var (
 	ErrInvalidCredentials = errors.New("Invalid Username or Password")
 )
 
-type LoginSettings struct {
-	LdapEnabled bool
-}
-
-type LdapFilterToOrg struct {
-	Filter  string
-	OrgId   int
-	OrgRole string
-}
-
-type LdapSettings struct {
-	Enabled      bool
-	Hosts        []string
-	UseSSL       bool
-	BindDN       string
-	AttrUsername string
-	AttrName     string
-	AttrSurname  string
-	AttrMail     string
-	Filters      []LdapFilterToOrg
-}
-
-type AuthSource interface {
-	AuthenticateUser(username, password string) (*m.User, error)
-}
-
 type AuthenticateUserQuery struct {
 	Username string
 	Password string
@@ -56,7 +30,13 @@ func AuthenticateUser(query *AuthenticateUserQuery) error {
 	}
 
 	if setting.LdapEnabled {
-		err = loginUsingLdap(query)
+		for _, server := range setting.LdapServers {
+			auther := NewLdapAuthenticator(server)
+			err = auther.login(query)
+			if err == nil || err != ErrInvalidCredentials {
+				return err
+			}
+		}
 	}
 
 	return err

+ 75 - 18
pkg/auth/ldap.go

@@ -1,8 +1,8 @@
 package auth
 
 import (
+	"errors"
 	"fmt"
-	"net/url"
 
 	"github.com/go-ldap/ldap"
 	"github.com/grafana/grafana/pkg/bus"
@@ -11,23 +11,49 @@ import (
 	"github.com/grafana/grafana/pkg/setting"
 )
 
-func loginUsingLdap(query *AuthenticateUserQuery) error {
-	url, err := url.Parse(setting.LdapHosts[0])
-	if err != nil {
-		return err
+func init() {
+	setting.LdapServers = []*setting.LdapServerConf{
+		&setting.LdapServerConf{
+			UseSSL: false,
+			Host:   "127.0.0.1",
+			Port:   "389",
+			BindDN: "cn=%s,dc=grafana,dc=org",
+		},
 	}
+}
 
-	conn, err := ldap.Dial("tcp", url.Host)
-	if err != nil {
-		return err
+type ldapAuther struct {
+	server *setting.LdapServerConf
+	conn   *ldap.Conn
+}
+
+func NewLdapAuthenticator(server *setting.LdapServerConf) *ldapAuther {
+	return &ldapAuther{
+		server: server,
 	}
+}
 
-	defer conn.Close()
+func (a *ldapAuther) Dial() error {
+	address := fmt.Sprintf("%s:%s", a.server.Host, a.server.Port)
+	var err error
+	if a.server.UseSSL {
+		a.conn, err = ldap.DialTLS("tcp", address, nil)
+	} else {
+		a.conn, err = ldap.Dial("tcp", address)
+	}
 
-	bindPath := fmt.Sprintf(setting.LdapBindPath, query.Username)
-	err = conn.Bind(bindPath, query.Password)
+	return err
+}
 
-	if err != nil {
+func (a *ldapAuther) login(query *AuthenticateUserQuery) error {
+	if err := a.Dial(); err != nil {
+		return err
+	}
+	defer a.conn.Close()
+
+	bindPath := fmt.Sprintf(a.server.BindDN, query.Username)
+
+	if err := a.conn.Bind(bindPath, query.Password); err != nil {
 		if ldapErr, ok := err.(*ldap.Error); ok {
 			if ldapErr.ResultCode == 49 {
 				return ErrInvalidCredentials
@@ -40,22 +66,33 @@ func loginUsingLdap(query *AuthenticateUserQuery) error {
 		BaseDN:       "dc=grafana,dc=org",
 		Scope:        ldap.ScopeWholeSubtree,
 		DerefAliases: ldap.NeverDerefAliases,
-		Attributes:   []string{"cn", "sn", "email"},
+		Attributes:   []string{"sn", "email", "givenName", "memberOf"},
 		Filter:       fmt.Sprintf("(cn=%s)", query.Username),
 	}
 
-	result, err := conn.Search(&searchReq)
+	result, err := a.conn.Search(&searchReq)
 	if err != nil {
 		return err
 	}
 
-	log.Info("Search result: %v, error: %v", result, err)
+	if len(result.Entries) == 0 {
+		return errors.New("Ldap search matched no entry, please review your filter setting.")
+	}
 
-	for _, entry := range result.Entries {
-		log.Info("cn: %s", entry.Attributes[0].Values[0])
-		log.Info("email: %s", entry.Attributes[2].Values[0])
+	if len(result.Entries) > 1 {
+		return errors.New("Ldap search matched mopre than one entry, please review your filter setting")
 	}
 
+	surname := getLdapAttr("sn", result)
+	givenName := getLdapAttr("givenName", result)
+	email := getLdapAttr("email", result)
+	memberOf := getLdapAttrArray("memberOf", result)
+
+	log.Info("Surname: %s", surname)
+	log.Info("givenName: %s", givenName)
+	log.Info("email: %s", email)
+	log.Info("memberOf: %s", memberOf)
+
 	userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username}
 	err = bus.Dispatch(&userQuery)
 
@@ -70,6 +107,26 @@ func loginUsingLdap(query *AuthenticateUserQuery) error {
 	return nil
 }
 
+func getLdapAttr(name string, result *ldap.SearchResult) string {
+	for _, attr := range result.Entries[0].Attributes {
+		if attr.Name == name {
+			if len(attr.Values) > 0 {
+				return attr.Values[0]
+			}
+		}
+	}
+	return ""
+}
+
+func getLdapAttrArray(name string, result *ldap.SearchResult) []string {
+	for _, attr := range result.Entries[0].Attributes {
+		if attr.Name == name {
+			return attr.Values
+		}
+	}
+	return []string{}
+}
+
 func createUserFromLdapInfo() error {
 	return nil
 

+ 2 - 5
pkg/setting/setting.go

@@ -118,9 +118,8 @@ var (
 	GoogleAnalyticsId string
 
 	// LDAP
-	LdapEnabled  bool
-	LdapHosts    []string
-	LdapBindPath string
+	LdapEnabled bool
+	LdapServers []*LdapServerConf
 
 	// SMTP email settings
 	Smtp SmtpSettings
@@ -419,8 +418,6 @@ func NewConfigContext(args *CommandLineArgs) {
 
 	ldapSec := Cfg.Section("auth.ldap")
 	LdapEnabled = ldapSec.Key("enabled").MustBool(false)
-	LdapHosts = ldapSec.Key("hosts").Strings(" ")
-	LdapBindPath = ldapSec.Key("bind_path").String()
 
 	readSessionConfig()
 	readSmtpSettings()

+ 14 - 8
pkg/setting/setting_ldap.go

@@ -1,19 +1,25 @@
 package setting
 
-type LdapFilterToOrg struct {
-	Filter  string
-	OrgId   int
-	OrgRole string
+type LdapMemberToOrgRole struct {
+	LdapMemberPattern string
+	OrgId             int
+	OrgRole           string
 }
 
-type LdapSettings struct {
-	Enabled      bool
-	Hosts        []string
+type LdapServerConf struct {
+	Host         string
+	Port         string
 	UseSSL       bool
 	BindDN       string
+	BindPassword string
 	AttrUsername string
 	AttrName     string
 	AttrSurname  string
 	AttrMail     string
-	Filters      []LdapFilterToOrg
+	AttrMemberOf string
+
+	SearchFilter  []string
+	SearchBaseDNs []string
+
+	LdapMemberMap []LdapMemberToOrgRole
 }