diff --git a/conf/ldap.toml b/conf/ldap.toml index 9455c8df3a1..bfe28d4e835 100644 --- a/conf/ldap.toml +++ b/conf/ldap.toml @@ -2,7 +2,7 @@ verbose_logging = false [[servers]] -# Ldap server host +# Ldap server host (specify multiple hosts space separated) host = "127.0.0.1" # Default port is 389 or 636 if use_ssl = true port = 389 @@ -10,17 +10,40 @@ port = 389 use_ssl = 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 = "cn=admin,dc=grafana,dc=org" # Search user bind password bind_password = 'grafana' +# Schema's supporting memberOf + # Search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" search_filter = "(cn=%s)" # An array of base dns to search through search_base_dns = ["dc=grafana,dc=org"] +# Uncomment this section (and comment out the previous 2 entries) to use POSIX schema. +# In POSIX LDAP schemas, querying the people 'ou' gives you entries that do not have a +# memberOf attribute, so 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. +# +# Search filter, used to retrieve the user +# search_filter = "(uid=%s)" +# +# An array of the base DNs to search through for users. Typically uses ou=people. +# search_base_dns = ["ou=people,dc=grafana,dc=org"] +# +# Group search filter, to retrieve the groups of which the user is a member +# group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))" +# +# 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 [servers.attributes] name = "givenName" diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index af9982e77df..6337f932256 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -2,8 +2,10 @@ package login import ( "crypto/tls" + "crypto/x509" "errors" "fmt" + "io/ioutil" "strings" "github.com/davecgh/go-spew/spew" @@ -24,18 +26,37 @@ func NewLdapAuthenticator(server *LdapServerConf) *ldapAuther { } func (a *ldapAuther) Dial() error { - address := fmt.Sprintf("%s:%d", a.server.Host, a.server.Port) var err error - if a.server.UseSSL { - tlsCfg := &tls.Config{ - InsecureSkipVerify: a.server.SkipVerifySSL, - ServerName: a.server.Host, + var certPool *x509.CertPool + if a.server.RootCACert != "" { + certPool := x509.NewCertPool() + for _, caCertFile := range strings.Split(a.server.RootCACert, " ") { + if pem, err := ioutil.ReadFile(caCertFile); err != nil { + return err + } else { + if !certPool.AppendCertsFromPEM(pem) { + return errors.New("Failed to append CA certficate " + caCertFile) + } + } } - a.conn, err = ldap.DialTLS("tcp", address, tlsCfg) - } else { - a.conn, err = ldap.Dial("tcp", address) } + for _, host := range strings.Split(a.server.Host, " ") { + address := fmt.Sprintf("%s:%d", host, a.server.Port) + if a.server.UseSSL { + tlsCfg := &tls.Config{ + InsecureSkipVerify: a.server.SkipVerifySSL, + ServerName: host, + RootCAs: certPool, + } + a.conn, err = ldap.DialTLS("tcp", address, tlsCfg) + } else { + a.conn, err = ldap.Dial("tcp", address) + } + if err == nil { + return nil + } + } return err } @@ -290,18 +311,51 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) { return nil, errors.New("Ldap search matched more than one entry, please review your filter setting") } + var memberOf []string + if a.server.GroupSearchFilter == "" { + memberOf = getLdapAttrArray(a.server.Attr.MemberOf, searchResult) + } else { + // If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups + var groupSearchResult *ldap.SearchResult + for _, groupSearchBase := range a.server.GroupSearchBaseDNs { + filter := strings.Replace(a.server.GroupSearchFilter, "%s", username, -1) + groupSearchReq := ldap.SearchRequest{ + BaseDN: groupSearchBase, + Scope: ldap.ScopeWholeSubtree, + DerefAliases: ldap.NeverDerefAliases, + Attributes: []string{ + // Here MemberOf would be the thing that identifies the group, which is normally 'cn' + a.server.Attr.MemberOf, + }, + Filter: filter, + } + + groupSearchResult, err = a.conn.Search(&groupSearchReq) + if err != nil { + return nil, err + } + + if len(groupSearchResult.Entries) > 0 { + for i := range groupSearchResult.Entries { + memberOf = append(memberOf, getLdapAttrN(a.server.Attr.MemberOf, groupSearchResult, i)) + } + break + } + } + } + return &ldapUserInfo{ DN: searchResult.Entries[0].DN, LastName: getLdapAttr(a.server.Attr.Surname, searchResult), FirstName: getLdapAttr(a.server.Attr.Name, searchResult), Username: getLdapAttr(a.server.Attr.Username, searchResult), Email: getLdapAttr(a.server.Attr.Email, searchResult), - MemberOf: getLdapAttrArray(a.server.Attr.MemberOf, searchResult), + MemberOf: memberOf, }, nil } -func getLdapAttr(name string, result *ldap.SearchResult) string { - for _, attr := range result.Entries[0].Attributes { +func getLdapAttrN(name string, result *ldap.SearchResult, n int) string { + for _, attr := range result.Entries[n].Attributes { if attr.Name == name { if len(attr.Values) > 0 { return attr.Values[0] @@ -311,6 +365,10 @@ func getLdapAttr(name string, result *ldap.SearchResult) string { return "" } +func getLdapAttr(name string, result *ldap.SearchResult) string { + return getLdapAttrN(name, result, 0) +} + func getLdapAttrArray(name string, result *ldap.SearchResult) []string { for _, attr := range result.Entries[0].Attributes { if attr.Name == name { diff --git a/pkg/login/settings.go b/pkg/login/settings.go index 93ff04054c1..b181dac3281 100644 --- a/pkg/login/settings.go +++ b/pkg/login/settings.go @@ -19,6 +19,7 @@ type LdapServerConf struct { Port int `toml:"port"` UseSSL bool `toml:"use_ssl"` SkipVerifySSL bool `toml:"ssl_skip_verify"` + RootCACert string `toml:"root_ca_cert"` BindDN string `toml:"bind_dn"` BindPassword string `toml:"bind_password"` Attr LdapAttributeMap `toml:"attributes"` @@ -26,6 +27,9 @@ type LdapServerConf struct { SearchFilter string `toml:"search_filter"` SearchBaseDNs []string `toml:"search_base_dns"` + GroupSearchFilter string `toml:"group_search_filter"` + GroupSearchBaseDNs []string `toml:"group_search_base_dns"` + LdapGroups []*LdapGroupToOrgRole `toml:"group_mappings"` }