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
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]
......
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"]
openldap:
image: cnry/openldap
build: blocks/openldap
environment:
SLAPD_PASSWORD: grafana
SLAPD_DOMAIN: grafana.org
SLAPD_ADDITIONAL_MODULES: memberof
ports:
- "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 (
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
......
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
......
......@@ -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()
......
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
}
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