mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
LDAP: improve POSIX support (#18235)
* LDAP: improve POSIX support * Correctly abtain DN attributes result * Allow more flexibility with comparison mapping between POSIX group & user * Add devenv for POSIX LDAP server * Correct the docs Fixes #18140
This commit is contained in:
parent
a4b0ccc138
commit
1e5fc76601
@ -28,38 +28,6 @@ search_filter = "(cn=%s)"
|
||||
# An array of base dns to search through
|
||||
search_base_dns = ["dc=grafana,dc=org"]
|
||||
|
||||
# In POSIX LDAP schemas, without memberOf attribute 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.
|
||||
|
||||
# Users with nested/recursive group membership and an LDAP server that supports LDAP_MATCHING_RULE_IN_CHAIN
|
||||
# can set group_search_filter, group_search_filter_user_attribute, group_search_base_dns and member_of
|
||||
# below in such a way that the user's recursive group membership is considered.
|
||||
#
|
||||
# Nested Groups + Active Directory (AD) Example:
|
||||
#
|
||||
# AD groups store the Distinguished Names (DNs) of members, so your filter must
|
||||
# recursively search your groups for the authenticating user's DN. For example:
|
||||
#
|
||||
# group_search_filter = "(member:1.2.840.113556.1.4.1941:=%s)"
|
||||
# group_search_filter_user_attribute = "distinguishedName"
|
||||
# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
|
||||
#
|
||||
# [servers.attributes]
|
||||
# ...
|
||||
# member_of = "distinguishedName"
|
||||
|
||||
## Group search filter, to retrieve the groups of which the user is a member (only set if memberOf attribute is not available)
|
||||
# group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"
|
||||
## Group search filter user attribute defines what user attribute gets substituted for %s in group_search_filter.
|
||||
## Defaults to the value of username in [server.attributes]
|
||||
## Valid options are any of your values in [servers.attributes]
|
||||
## If you are using nested groups you probably want to set this and member_of in
|
||||
## [servers.attributes] to "distinguishedName"
|
||||
# group_search_filter_user_attribute = "distinguishedName"
|
||||
## 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"
|
||||
|
57
devenv/docker/blocks/openldap/ldap_posix_dev.toml
Normal file
57
devenv/docker/blocks/openldap/ldap_posix_dev.toml
Normal file
@ -0,0 +1,57 @@
|
||||
# To troubleshoot and get more log info enable ldap debug logging in grafana.ini
|
||||
# [log]
|
||||
# filters = ldap:debug
|
||||
|
||||
[[servers]]
|
||||
# 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
|
||||
# Set to true if ldap server supports TLS
|
||||
use_ssl = false
|
||||
# Set to true if connect ldap server with STARTTLS pattern (create connection in insecure, then upgrade to secure connection with TLS)
|
||||
start_tls = 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
|
||||
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
|
||||
bind_password = 'grafana'
|
||||
|
||||
# An array of base dns to search through
|
||||
search_base_dns = ["dc=grafana,dc=org"]
|
||||
|
||||
search_filter = "(uid=%s)"
|
||||
|
||||
group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"
|
||||
group_search_filter_user_attribute = "uid"
|
||||
group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
|
||||
|
||||
[servers.attributes]
|
||||
name = "givenName"
|
||||
surname = "sn"
|
||||
username = "cn"
|
||||
member_of = "memberOf"
|
||||
email = "email"
|
||||
|
||||
# Map ldap groups to grafana org roles
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=posix-admins,ou=groups,dc=grafana,dc=org"
|
||||
org_role = "Admin"
|
||||
grafana_admin = true
|
||||
|
||||
# The Grafana organization database id, optional, if left out the default org (id 1) will be used
|
||||
# org_id = 1
|
||||
|
||||
[[servers.group_mappings]]
|
||||
group_dn = "cn=editors,ou=groups,dc=grafana,dc=org"
|
||||
org_role = "Editor"
|
||||
|
||||
[[servers.group_mappings]]
|
||||
# If you want to match all (or no ldap groups) then you can use wildcard
|
||||
group_dn = "*"
|
||||
org_role = "Viewer"
|
@ -12,7 +12,7 @@ After adding ldif files to `prepopulate`:
|
||||
|
||||
## Enabling LDAP in Grafana
|
||||
|
||||
Copy the ldap_dev.toml file in this folder into your `conf` folder (it is gitignored already). To enable it in the .ini file to get Grafana to use this block:
|
||||
If you want to use users/groups with `memberOf` support Copy the ldap_dev.toml file in this folder into your `conf` folder (it is gitignored already). To enable it in the .ini file to get Grafana to use this block:
|
||||
|
||||
```ini
|
||||
[auth.ldap]
|
||||
@ -21,6 +21,8 @@ config_file = conf/ldap_dev.toml
|
||||
; allow_sign_up = true
|
||||
```
|
||||
|
||||
Otherwise perform same actions for `ldap_dev_posix.toml` config.
|
||||
|
||||
## Groups & Users
|
||||
|
||||
admins
|
||||
@ -38,3 +40,11 @@ editors
|
||||
ldap-editors
|
||||
no groups
|
||||
ldap-viewer
|
||||
|
||||
|
||||
## Groups & Users (POSIX)
|
||||
|
||||
admins
|
||||
ldap-posix-admin
|
||||
no groups
|
||||
ldap-posix
|
||||
|
@ -78,3 +78,31 @@ objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
sn: ldap-torkel
|
||||
cn: ldap-torkel
|
||||
|
||||
# admin for posix group (without support for memberOf attribute)
|
||||
dn: uid=ldap-posix-admin,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-posix-admin@grafana.com
|
||||
userPassword: grafana
|
||||
objectclass: top
|
||||
objectclass: posixAccount
|
||||
objectclass: inetOrgPerson
|
||||
homedirectory: /home/ldap-posix-admin
|
||||
sn: ldap-posix-admin
|
||||
cn: ldap-posix-admin
|
||||
uid: ldap-posix-admin
|
||||
uidnumber: 1
|
||||
gidnumber: 1
|
||||
|
||||
# user for posix group (without support for memberOf attribute)
|
||||
dn: uid=ldap-posix,ou=users,dc=grafana,dc=org
|
||||
mail: ldap-posix@grafana.com
|
||||
userPassword: grafana
|
||||
objectclass: top
|
||||
objectclass: posixAccount
|
||||
objectclass: inetOrgPerson
|
||||
homedirectory: /home/ldap-posix
|
||||
sn: ldap-posix
|
||||
cn: ldap-posix
|
||||
uid: ldap-posix
|
||||
uidnumber: 2
|
||||
gidnumber: 2
|
||||
|
@ -23,3 +23,21 @@ objectClass: groupOfNames
|
||||
member: cn=ldap-torkel,ou=users,dc=grafana,dc=org
|
||||
member: cn=ldap-daniel,ou=users,dc=grafana,dc=org
|
||||
member: cn=ldap-leo,ou=users,dc=grafana,dc=org
|
||||
|
||||
# -- POSIX --
|
||||
|
||||
# posix admin group (without support for memberOf attribute)
|
||||
dn: cn=posix-admins,ou=groups,dc=grafana,dc=org
|
||||
cn: admins
|
||||
objectClass: top
|
||||
objectClass: posixGroup
|
||||
gidNumber: 1
|
||||
memberUid: ldap-posix-admin
|
||||
|
||||
# posix group (without support for memberOf attribute)
|
||||
dn: cn=posix,ou=groups,dc=grafana,dc=org
|
||||
cn: viewers
|
||||
objectClass: top
|
||||
objectClass: posixGroup
|
||||
gidNumber: 2
|
||||
memberUid: ldap-posix
|
||||
|
@ -126,8 +126,6 @@ group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
|
||||
group_search_filter_user_attribute = "uid"
|
||||
```
|
||||
|
||||
Also set `member_of = "dn"` in the `[servers.attributes]` section.
|
||||
|
||||
### Group Mappings
|
||||
|
||||
In `[[servers.group_mappings]]` you can map an LDAP group to a Grafana organization and role. These will be synced every time the user logs in, with LDAP being
|
||||
|
@ -29,6 +29,10 @@ func appendIfNotEmpty(slice []string, values ...string) []string {
|
||||
}
|
||||
|
||||
func getAttribute(name string, entry *ldap.Entry) string {
|
||||
if strings.ToLower(name) == "dn" {
|
||||
return entry.DN
|
||||
}
|
||||
|
||||
for _, attr := range entry.Attributes {
|
||||
if attr.Name == name {
|
||||
if len(attr.Values) > 0 {
|
||||
@ -40,6 +44,10 @@ func getAttribute(name string, entry *ldap.Entry) string {
|
||||
}
|
||||
|
||||
func getArrayAttribute(name string, entry *ldap.Entry) []string {
|
||||
if strings.ToLower(name) == "dn" {
|
||||
return []string{entry.DN}
|
||||
}
|
||||
|
||||
for _, attr := range entry.Attributes {
|
||||
if attr.Name == name && len(attr.Values) > 0 {
|
||||
return attr.Values
|
||||
|
@ -266,7 +266,9 @@ func (server *Server) Users(logins []string) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
server.log.Debug("LDAP users found", "users", spew.Sdump(serializedUsers))
|
||||
server.log.Debug(
|
||||
"LDAP users found", "users", spew.Sdump(serializedUsers),
|
||||
)
|
||||
|
||||
return serializedUsers, nil
|
||||
}
|
||||
@ -327,6 +329,9 @@ func (server *Server) getSearchRequest(
|
||||
inputs.Email,
|
||||
inputs.Name,
|
||||
inputs.MemberOf,
|
||||
|
||||
// In case for the POSIX LDAP schema server
|
||||
server.Config.GroupSearchFilterUserAttribute,
|
||||
)
|
||||
|
||||
search := ""
|
||||
@ -489,6 +494,7 @@ func (server *Server) requestMemberOf(entry *ldap.Entry) ([]string, error) {
|
||||
|
||||
if len(groupSearchResult.Entries) > 0 {
|
||||
for _, group := range groupSearchResult.Entries {
|
||||
|
||||
memberOf = append(
|
||||
memberOf,
|
||||
getAttribute(groupIDAttribute, group),
|
||||
|
@ -105,6 +105,16 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("getAttribute()", t, func() {
|
||||
Convey("Should get DN", func() {
|
||||
entry := &ldap.Entry{
|
||||
DN: "test",
|
||||
}
|
||||
|
||||
result := getAttribute("dn", entry)
|
||||
|
||||
So(result, ShouldEqual, "test")
|
||||
})
|
||||
|
||||
Convey("Should get username", func() {
|
||||
value := []string{"roelgerrits"}
|
||||
entry := &ldap.Entry{
|
||||
@ -137,6 +147,16 @@ func TestLDAPHelpers(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("getArrayAttribute()", t, func() {
|
||||
Convey("Should get DN", func() {
|
||||
entry := &ldap.Entry{
|
||||
DN: "test",
|
||||
}
|
||||
|
||||
result := getArrayAttribute("dn", entry)
|
||||
|
||||
So(result, ShouldResemble, []string{"test"})
|
||||
})
|
||||
|
||||
Convey("Should get username", func() {
|
||||
value := []string{"roelgerrits"}
|
||||
entry := &ldap.Entry{
|
||||
|
@ -11,6 +11,44 @@ import (
|
||||
)
|
||||
|
||||
func TestLDAPPrivateMethods(t *testing.T) {
|
||||
Convey("getSearchRequest()", t, func() {
|
||||
Convey("with enabled GroupSearchFilterUserAttribute setting", func() {
|
||||
server := &Server{
|
||||
Config: &ServerConfig{
|
||||
Attr: AttributeMap{
|
||||
Username: "username",
|
||||
Name: "name",
|
||||
MemberOf: "memberof",
|
||||
Email: "email",
|
||||
},
|
||||
GroupSearchFilterUserAttribute: "gansta",
|
||||
SearchBaseDNs: []string{"BaseDNHere"},
|
||||
},
|
||||
log: log.New("test-logger"),
|
||||
}
|
||||
|
||||
result := server.getSearchRequest("killa", []string{"gorilla"})
|
||||
|
||||
So(result, ShouldResemble, &ldap.SearchRequest{
|
||||
BaseDN: "killa",
|
||||
Scope: 2,
|
||||
DerefAliases: 0,
|
||||
SizeLimit: 0,
|
||||
TimeLimit: 0,
|
||||
TypesOnly: false,
|
||||
Filter: "(|)",
|
||||
Attributes: []string{
|
||||
"username",
|
||||
"email",
|
||||
"name",
|
||||
"memberof",
|
||||
"gansta",
|
||||
},
|
||||
Controls: nil,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("serializeUsers()", t, func() {
|
||||
Convey("simple case", func() {
|
||||
server := &Server{
|
||||
|
Loading…
Reference in New Issue
Block a user