Commit bfe7b773 by Torkel Ödegaard

More work on ldap auth, got memberOf working in the docker ldap test server,…

More work on ldap auth, got memberOf working in the docker ldap test server, playing with config options and structures, #1450
parent a69086a7
...@@ -185,10 +185,18 @@ enabled = true ...@@ -185,10 +185,18 @@ enabled = true
hosts = ldap://127.0.0.1:389 hosts = ldap://127.0.0.1:389
use_ssl = false use_ssl = false
bind_path = cn=%s,dc=grafana,dc=org bind_path = cn=%s,dc=grafana,dc=org
bind_password =
search_bases = dc=grafana,dc=org
search_filter = (cn=%s)
attr_username = cn attr_username = cn
attr_name = cn attr_name = givenName
attr_surname = sn attr_surname = sn
attr_email = email 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 / Emailing ##########################
[smtp] [smtp]
......
FROM phusion/baseimage:0.9.8 FROM debian:jessie
MAINTAINER Nick Stenning <nick@whiteink.com>
ENV HOME /root MAINTAINER Christian Luginbühl <dinke@pimprecords.com>
# Disable SSH ENV OPENLDAP_VERSION 2.4.40
RUN rm -rf /etc/service/sshd /etc/my_init.d/00_regen_ssh_host_keys.sh
# Use baseimage-docker's init system. RUN apt-get update && \
CMD ["/sbin/my_init"] 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 mv /etc/ldap /etc/ldap.dist
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
EXPOSE 389 EXPOSE 389
RUN mkdir /etc/service/slapd VOLUME ["/etc/ldap", "/var/lib/ldap"]
ADD slapd.sh /etc/service/slapd/run
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"]
openldap: openldap:
image: cnry/openldap build: blocks/openldap
environment: environment:
SLAPD_PASSWORD: grafana SLAPD_PASSWORD: grafana
SLAPD_DOMAIN: grafana.org SLAPD_DOMAIN: grafana.org
SLAPD_ADDITIONAL_MODULES: memberof
ports: ports:
- "389:389" - "389:389"
......
#!/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
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
// }
}
...@@ -13,32 +13,6 @@ var ( ...@@ -13,32 +13,6 @@ var (
ErrInvalidCredentials = errors.New("Invalid Username or Password") 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 { type AuthenticateUserQuery struct {
Username string Username string
Password string Password string
...@@ -56,7 +30,13 @@ func AuthenticateUser(query *AuthenticateUserQuery) error { ...@@ -56,7 +30,13 @@ func AuthenticateUser(query *AuthenticateUserQuery) error {
} }
if setting.LdapEnabled { 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 return err
......
package auth package auth
import ( import (
"errors"
"fmt" "fmt"
"net/url"
"github.com/go-ldap/ldap" "github.com/go-ldap/ldap"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
...@@ -11,23 +11,49 @@ import ( ...@@ -11,23 +11,49 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
func loginUsingLdap(query *AuthenticateUserQuery) error { func init() {
url, err := url.Parse(setting.LdapHosts[0]) setting.LdapServers = []*setting.LdapServerConf{
if err != nil { &setting.LdapServerConf{
return err UseSSL: false,
Host: "127.0.0.1",
Port: "389",
BindDN: "cn=%s,dc=grafana,dc=org",
},
} }
}
conn, err := ldap.Dial("tcp", url.Host) type ldapAuther struct {
if err != nil { server *setting.LdapServerConf
return err 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) return err
err = conn.Bind(bindPath, query.Password) }
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, ok := err.(*ldap.Error); ok {
if ldapErr.ResultCode == 49 { if ldapErr.ResultCode == 49 {
return ErrInvalidCredentials return ErrInvalidCredentials
...@@ -40,22 +66,33 @@ func loginUsingLdap(query *AuthenticateUserQuery) error { ...@@ -40,22 +66,33 @@ func loginUsingLdap(query *AuthenticateUserQuery) error {
BaseDN: "dc=grafana,dc=org", BaseDN: "dc=grafana,dc=org",
Scope: ldap.ScopeWholeSubtree, Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases, DerefAliases: ldap.NeverDerefAliases,
Attributes: []string{"cn", "sn", "email"}, Attributes: []string{"sn", "email", "givenName", "memberOf"},
Filter: fmt.Sprintf("(cn=%s)", query.Username), Filter: fmt.Sprintf("(cn=%s)", query.Username),
} }
result, err := conn.Search(&searchReq) result, err := a.conn.Search(&searchReq)
if err != nil { if err != nil {
return err 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 { if len(result.Entries) > 1 {
log.Info("cn: %s", entry.Attributes[0].Values[0]) return errors.New("Ldap search matched mopre than one entry, please review your filter setting")
log.Info("email: %s", entry.Attributes[2].Values[0])
} }
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} userQuery := m.GetUserByLoginQuery{LoginOrEmail: query.Username}
err = bus.Dispatch(&userQuery) err = bus.Dispatch(&userQuery)
...@@ -70,6 +107,26 @@ func loginUsingLdap(query *AuthenticateUserQuery) error { ...@@ -70,6 +107,26 @@ func loginUsingLdap(query *AuthenticateUserQuery) error {
return nil 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 { func createUserFromLdapInfo() error {
return nil return nil
......
...@@ -118,9 +118,8 @@ var ( ...@@ -118,9 +118,8 @@ var (
GoogleAnalyticsId string GoogleAnalyticsId string
// LDAP // LDAP
LdapEnabled bool LdapEnabled bool
LdapHosts []string LdapServers []*LdapServerConf
LdapBindPath string
// SMTP email settings // SMTP email settings
Smtp SmtpSettings Smtp SmtpSettings
...@@ -419,8 +418,6 @@ func NewConfigContext(args *CommandLineArgs) { ...@@ -419,8 +418,6 @@ func NewConfigContext(args *CommandLineArgs) {
ldapSec := Cfg.Section("auth.ldap") ldapSec := Cfg.Section("auth.ldap")
LdapEnabled = ldapSec.Key("enabled").MustBool(false) LdapEnabled = ldapSec.Key("enabled").MustBool(false)
LdapHosts = ldapSec.Key("hosts").Strings(" ")
LdapBindPath = ldapSec.Key("bind_path").String()
readSessionConfig() readSessionConfig()
readSmtpSettings() readSmtpSettings()
......
package setting package setting
type LdapFilterToOrg struct { type LdapMemberToOrgRole struct {
Filter string LdapMemberPattern string
OrgId int OrgId int
OrgRole string OrgRole string
} }
type LdapSettings struct { type LdapServerConf struct {
Enabled bool Host string
Hosts []string Port string
UseSSL bool UseSSL bool
BindDN string BindDN string
BindPassword string
AttrUsername string AttrUsername string
AttrName string AttrName string
AttrSurname string AttrSurname string
AttrMail string AttrMail string
Filters []LdapFilterToOrg AttrMemberOf string
SearchFilter []string
SearchBaseDNs []string
LdapMemberMap []LdapMemberToOrgRole
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment