From 14f439f8ba2d79277f11c6fc6f761c63aa0575cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Mon, 13 Jul 2015 15:37:11 +0200 Subject: [PATCH] refactor(ldap): refactoring ldap code, #1450 --- docker/blocks/openldap/entrypoint.sh | 93 ++++++++++++++ docker/blocks/openldap/modules/memberof.ldif | 33 +++++ pkg/auth/ldap.go | 126 ++++++++++++------- pkg/setting/setting_ldap.go | 4 +- 4 files changed, 211 insertions(+), 45 deletions(-) create mode 100755 docker/blocks/openldap/entrypoint.sh create mode 100644 docker/blocks/openldap/modules/memberof.ldif diff --git a/docker/blocks/openldap/entrypoint.sh b/docker/blocks/openldap/entrypoint.sh new file mode 100755 index 00000000000..39a8b892de8 --- /dev/null +++ b/docker/blocks/openldap/entrypoint.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# When not limiting the open file descritors limit, the memory consumption of +# slapd is absurdly high. See https://github.com/docker/docker/issues/8231 +ulimit -n 8192 + + +set -e + +chown -R openldap:openldap /var/lib/ldap/ + +if [[ ! -d /etc/ldap/slapd.d ]]; then + + if [[ -z "$SLAPD_PASSWORD" ]]; then + echo -n >&2 "Error: Container not configured and SLAPD_PASSWORD not set. " + echo >&2 "Did you forget to add -e SLAPD_PASSWORD=... ?" + exit 1 + fi + + if [[ -z "$SLAPD_DOMAIN" ]]; then + echo -n >&2 "Error: Container not configured and SLAPD_DOMAIN not set. " + echo >&2 "Did you forget to add -e SLAPD_DOMAIN=... ?" + exit 1 + fi + + SLAPD_ORGANIZATION="${SLAPD_ORGANIZATION:-${SLAPD_DOMAIN}}" + + cp -a /etc/ldap.dist/* /etc/ldap + + cat <<-EOF | debconf-set-selections + slapd slapd/no_configuration boolean false + slapd slapd/password1 password $SLAPD_PASSWORD + slapd slapd/password2 password $SLAPD_PASSWORD + slapd shared/organization string $SLAPD_ORGANIZATION + slapd slapd/domain string $SLAPD_DOMAIN + slapd slapd/backend select HDB + slapd slapd/allow_ldap_v2 boolean false + slapd slapd/purge_database boolean false + slapd slapd/move_old_database boolean true +EOF + + dpkg-reconfigure -f noninteractive slapd >/dev/null 2>&1 + + dc_string="" + + IFS="."; declare -a dc_parts=($SLAPD_DOMAIN) + + for dc_part in "${dc_parts[@]}"; do + dc_string="$dc_string,dc=$dc_part" + done + + base_string="BASE ${dc_string:1}" + + sed -i "s/^#BASE.*/${base_string}/g" /etc/ldap/ldap.conf + + if [[ -n "$SLAPD_CONFIG_PASSWORD" ]]; then + password_hash=`slappasswd -s "${SLAPD_CONFIG_PASSWORD}"` + + sed_safe_password_hash=${password_hash//\//\\\/} + + slapcat -n0 -F /etc/ldap/slapd.d -l /tmp/config.ldif + sed -i "s/\(olcRootDN: cn=admin,cn=config\)/\1\nolcRootPW: ${sed_safe_password_hash}/g" /tmp/config.ldif + rm -rf /etc/ldap/slapd.d/* + slapadd -n0 -F /etc/ldap/slapd.d -l /tmp/config.ldif >/dev/null 2>&1 + fi + + if [[ -n "$SLAPD_ADDITIONAL_SCHEMAS" ]]; then + IFS=","; declare -a schemas=($SLAPD_ADDITIONAL_SCHEMAS) + + for schema in "${schemas[@]}"; do + slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/schema/${schema}.ldif" >/dev/null 2>&1 + done + fi + + if [[ -n "$SLAPD_ADDITIONAL_MODULES" ]]; then + IFS=","; declare -a modules=($SLAPD_ADDITIONAL_MODULES) + + for module in "${modules[@]}"; do + slapadd -n0 -F /etc/ldap/slapd.d -l "/etc/ldap/modules/${module}.ldif" >/dev/null 2>&1 + done + fi + + chown -R openldap:openldap /etc/ldap/slapd.d/ +else + slapd_configs_in_env=`env | grep 'SLAPD_'` + + if [ -n "${slapd_configs_in_env:+x}" ]; then + echo "Info: Container already configured, therefore ignoring SLAPD_xxx environment variables" + fi +fi + +exec "$@" + diff --git a/docker/blocks/openldap/modules/memberof.ldif b/docker/blocks/openldap/modules/memberof.ldif new file mode 100644 index 00000000000..fd9cce957c3 --- /dev/null +++ b/docker/blocks/openldap/modules/memberof.ldif @@ -0,0 +1,33 @@ +dn: cn=module,cn=config +cn: module +objectClass: olcModuleList +objectClass: top +olcModulePath: /usr/lib/ldap +olcModuleLoad: memberof.la + +dn: olcOverlay={0}memberof,olcDatabase={1}hdb,cn=config +objectClass: olcConfig +objectClass: olcMemberOf +objectClass: olcOverlayConfig +objectClass: top +olcOverlay: memberof +olcMemberOfDangling: ignore +olcMemberOfRefInt: TRUE +olcMemberOfGroupOC: groupOfNames +olcMemberOfMemberAD: member +olcMemberOfMemberOfAD: memberOf + +dn: cn=module,cn=config +cn: module +objectClass: olcModuleList +objectClass: top +olcModulePath: /usr/lib/ldap +olcModuleLoad: refint.la + +dn: olcOverlay={1}refint,olcDatabase={1}hdb,cn=config +objectClass: olcConfig +objectClass: olcOverlayConfig +objectClass: olcRefintConfig +objectClass: top +olcOverlay: {1}refint +olcRefintAttribute: memberof member manager owner diff --git a/pkg/auth/ldap.go b/pkg/auth/ldap.go index ac1db20bf50..8b84d379c29 100644 --- a/pkg/auth/ldap.go +++ b/pkg/auth/ldap.go @@ -5,19 +5,24 @@ import ( "fmt" "github.com/go-ldap/ldap" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" - m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" ) func init() { setting.LdapServers = []*setting.LdapServerConf{ &setting.LdapServerConf{ - UseSSL: false, - Host: "127.0.0.1", - Port: "389", - BindDN: "cn=%s,dc=grafana,dc=org", + UseSSL: false, + Host: "127.0.0.1", + Port: "389", + BindDN: "cn=%s,dc=grafana,dc=org", + AttrName: "givenName", + AttrSurname: "sn", + AttrUsername: "cn", + AttrMemberOf: "memberOf", + AttrEmail: "email", + SearchFilter: "(cn=%s)", + SearchBaseDNs: []string{"dc=grafana,dc=org"}, }, } } @@ -27,6 +32,14 @@ type ldapAuther struct { conn *ldap.Conn } +type ldapUserInfo struct { + FirstName string + LastName string + Username string + Email string + MemberOf []string +} + func NewLdapAuthenticator(server *setting.LdapServerConf) *ldapAuther { return &ldapAuther{ server: server, @@ -51,60 +64,87 @@ func (a *ldapAuther) login(query *AuthenticateUserQuery) error { } 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 - } - } + // perform initial authentication + if err := a.initialBind(query.Username, query.Password); err != nil { return err } - searchReq := ldap.SearchRequest{ - BaseDN: "dc=grafana,dc=org", - Scope: ldap.ScopeWholeSubtree, - DerefAliases: ldap.NeverDerefAliases, - Attributes: []string{"sn", "email", "givenName", "memberOf"}, - Filter: fmt.Sprintf("(cn=%s)", query.Username), - } - - result, err := a.conn.Search(&searchReq) - if err != nil { + // find user entry & attributes + if user, err := a.searchForUser(query.Username); err != nil { return err + } else { + log.Info("Surname: %s", user.LastName) + log.Info("givenName: %s", user.FirstName) + log.Info("email: %s", user.Email) + log.Info("memberOf: %s", user.MemberOf) } - if len(result.Entries) == 0 { - return errors.New("Ldap search matched no entry, please review your filter setting.") + return errors.New("Aasd") +} + +func (a *ldapAuther) initialBind(username, userPassword string) error { + if a.server.BindPassword != "" { + userPassword = a.server.BindPassword } - if len(result.Entries) > 1 { - return errors.New("Ldap search matched mopre than one entry, please review your filter setting") + bindPath := fmt.Sprintf(a.server.BindDN, username) + + if err := a.conn.Bind(bindPath, userPassword); err != nil { + if ldapErr, ok := err.(*ldap.Error); ok { + if ldapErr.ResultCode == 49 { + return ErrInvalidCredentials + } + } + return err } - surname := getLdapAttr("sn", result) - givenName := getLdapAttr("givenName", result) - email := getLdapAttr("email", result) - memberOf := getLdapAttrArray("memberOf", result) + return nil +} - log.Info("Surname: %s", surname) - log.Info("givenName: %s", givenName) - log.Info("email: %s", email) - log.Info("memberOf: %s", memberOf) +func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) { + var searchResult *ldap.SearchResult + var err error - userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username} - err = bus.Dispatch(&userQuery) + for _, searchBase := range a.server.SearchBaseDNs { + searchReq := ldap.SearchRequest{ + BaseDN: searchBase, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldap.NeverDerefAliases, + Attributes: []string{ + a.server.AttrUsername, + a.server.AttrSurname, + a.server.AttrEmail, + a.server.AttrName, + a.server.AttrMemberOf, + }, + Filter: fmt.Sprintf(a.server.SearchFilter, username), + } - if err != nil { - if err == m.ErrUserNotFound { + searchResult, err = a.conn.Search(&searchReq) + if err != nil { + return nil, err + } + + if len(searchResult.Entries) > 0 { + break } - return err } - query.User = userQuery.Result + if len(searchResult.Entries) == 0 { + return nil, errors.New("Ldap search matched no entry, please review your filter setting.") + } - return nil + if len(searchResult.Entries) > 1 { + return nil, errors.New("Ldap search matched mopre than one entry, please review your filter setting") + } + + return &ldapUserInfo{ + LastName: getLdapAttr(a.server.AttrSurname, searchResult), + FirstName: getLdapAttr(a.server.AttrName, searchResult), + Username: getLdapAttr(a.server.AttrUsername, searchResult), + Email: getLdapAttr(a.server.AttrEmail, searchResult), + MemberOf: getLdapAttrArray(a.server.AttrMemberOf, searchResult), + }, nil } func getLdapAttr(name string, result *ldap.SearchResult) string { diff --git a/pkg/setting/setting_ldap.go b/pkg/setting/setting_ldap.go index c545c19d936..bda7c09b143 100644 --- a/pkg/setting/setting_ldap.go +++ b/pkg/setting/setting_ldap.go @@ -15,10 +15,10 @@ type LdapServerConf struct { AttrUsername string AttrName string AttrSurname string - AttrMail string + AttrEmail string AttrMemberOf string - SearchFilter []string + SearchFilter string SearchBaseDNs []string LdapMemberMap []LdapMemberToOrgRole