Read username from referenced LDAP record
* The `username_attribute` configuration value was added to the `ldap` section. When this value is set, the program will not try to extract the username from DNs; instead, it will look them up and extract the username from the referenced record, using the specified attribute. * The program will no longer exit in error when a group listed in the mapping doesn't exist.
This commit is contained in:
parent
9bec0ad14e
commit
5c014aa951
3 changed files with 82 additions and 39 deletions
|
@ -55,8 +55,6 @@ To Do
|
|||
* Add TLS options (skip checks / specify CA) for the Graylog API.
|
||||
* Read object ownership using `grn_permissions` to preserve privileges on users'
|
||||
own objects
|
||||
* Read group member records from the LDAP server and extract their username
|
||||
from an attribute.
|
||||
* Support granting ownership on objects
|
||||
* Cleaner CLI
|
||||
* Use goroutines ? Maybe.
|
||||
|
|
|
@ -37,6 +37,13 @@ ldap:
|
|||
- uniqueMember
|
||||
- memberUid
|
||||
|
||||
# Username attribute. This is used when group member fields contain the '='
|
||||
# ',' character, in which case the value will be considered a DN and looked up
|
||||
# in the LDAP. The field specified by this configuration value will be read
|
||||
# and used as the login name. If this configuration value is not set, the
|
||||
# first element in the DN will be extracted and used as the username.
|
||||
username_attribute: uid
|
||||
|
||||
# Graylog server info
|
||||
# --------------------
|
||||
graylog:
|
||||
|
|
94
main.go
94
main.go
|
@ -29,10 +29,11 @@ type (
|
|||
Tls string
|
||||
TlsNoVerify bool `yaml:"tls_skip_verify"`
|
||||
TlsAllowCnOnly bool `yaml:"tls_allow_cn_only"`
|
||||
CaChain string
|
||||
CaChain string `yaml:"cachain"`
|
||||
BindUser string `yaml:"bind_user"`
|
||||
BindPassword string `yaml:"bind_password"`
|
||||
MemberFields []string `yaml:"member_fields"`
|
||||
UsernameAttr string `yaml:"username_attribute"`
|
||||
}
|
||||
|
||||
// Graylog server configuration
|
||||
|
@ -194,22 +195,6 @@ func getGraylogUsers(configuration GraylogConfig) (users []GraylogUser) {
|
|||
return
|
||||
}
|
||||
|
||||
// Extract an username from something that may be an username or a DN.
|
||||
func usernameFromMember(member string) string {
|
||||
eqPos := strings.Index(member, "=")
|
||||
if eqPos == -1 {
|
||||
return member
|
||||
}
|
||||
commaPos := strings.Index(member, ",")
|
||||
if commaPos == -1 {
|
||||
return member[eqPos+1:]
|
||||
}
|
||||
if eqPos > commaPos {
|
||||
log.Fatalf("couldn't extract user name from %s", member)
|
||||
}
|
||||
return member[eqPos+1 : commaPos]
|
||||
}
|
||||
|
||||
// Establish a connection to the LDAP server
|
||||
func getLdapConnection(cfg LdapConfig) (conn *ldap.Conn) {
|
||||
tlsConfig := &tls.Config{
|
||||
|
@ -248,27 +233,80 @@ func getLdapConnection(cfg LdapConfig) (conn *ldap.Conn) {
|
|||
return
|
||||
}
|
||||
|
||||
// Read the list of members from a LDAP group
|
||||
func getGroupMembers(group string, conn *ldap.Conn, fields []string) (members []string) {
|
||||
req := ldap.NewSearchRequest(group, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 1, 0, false, "(objectClass=*)", fields, nil)
|
||||
// Run a LDAP query to obtain a single object.
|
||||
func executeQuery(conn *ldap.Conn, dn string, attrs []string) (bool, *ldap.Entry) {
|
||||
req := ldap.NewSearchRequest(
|
||||
dn,
|
||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 1, 0, false,
|
||||
"(objectClass=*)", attrs, nil)
|
||||
res, err := conn.Search(req)
|
||||
if err != nil {
|
||||
log.Fatalf("LDAP search for %s: %v", group, err)
|
||||
ldapError, ok := err.(*ldap.Error)
|
||||
if ok && ldapError.ResultCode == ldap.LDAPResultNoSuchObject {
|
||||
return false, nil
|
||||
}
|
||||
log.Fatalf("LDAP search for %s: %v", dn, err)
|
||||
}
|
||||
if len(res.Entries) > 1 {
|
||||
log.Printf("LDAP search for %s returned more than 1 record", dn)
|
||||
return false, nil
|
||||
}
|
||||
return true, res.Entries[0]
|
||||
}
|
||||
|
||||
for _, entry := range res.Entries {
|
||||
for _, attr := range fields {
|
||||
// Read a username from a LDAP record based on a DN.
|
||||
func readUsernameFromLdap(dn string, conn *ldap.Conn, attr string) (bool, string) {
|
||||
ok, res := executeQuery(conn, dn, []string{attr})
|
||||
if !ok {
|
||||
return false, ""
|
||||
}
|
||||
values := res.GetAttributeValues(attr)
|
||||
if len(values) != 1 {
|
||||
log.Printf("LDAP search for %s: attribute %s has %d values", dn, attr, len(values))
|
||||
return false, ""
|
||||
}
|
||||
return true, values[0]
|
||||
}
|
||||
|
||||
// Extract an username from something that may be an username or a DN.
|
||||
func usernameFromMember(member string, conn *ldap.Conn, config LdapConfig) (bool, string) {
|
||||
eqPos := strings.Index(member, "=")
|
||||
if eqPos == -1 {
|
||||
return true, member
|
||||
}
|
||||
if config.UsernameAttr != "" {
|
||||
return readUsernameFromLdap(member, conn, config.UsernameAttr)
|
||||
}
|
||||
commaPos := strings.Index(member, ",")
|
||||
if commaPos == -1 {
|
||||
return true, member[eqPos+1:]
|
||||
}
|
||||
if eqPos > commaPos {
|
||||
log.Printf("couldn't extract user name from %s", member)
|
||||
return false, ""
|
||||
}
|
||||
return true, member[eqPos+1 : commaPos]
|
||||
}
|
||||
|
||||
// Read the list of members from a LDAP group
|
||||
func getGroupMembers(group string, conn *ldap.Conn, config LdapConfig) (members []string) {
|
||||
ok, entry := executeQuery(conn, group, config.MemberFields)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, attr := range config.MemberFields {
|
||||
values := entry.GetAttributeValues(attr)
|
||||
if len(values) == 0 {
|
||||
continue
|
||||
}
|
||||
members = make([]string, len(values))
|
||||
for i, value := range values {
|
||||
members[i] = usernameFromMember(value)
|
||||
for _, value := range values {
|
||||
ok, name := usernameFromMember(value, conn, config)
|
||||
if ok {
|
||||
members = append(members, name)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -286,7 +324,7 @@ func readLdapGroups(configuration Configuration) (groups GroupMembers) {
|
|||
|
||||
groups = make(GroupMembers)
|
||||
for group := range configuration.Mapping {
|
||||
groups[group] = getGroupMembers(group, conn, configuration.Ldap.MemberFields)
|
||||
groups[group] = getGroupMembers(group, conn, configuration.Ldap)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue