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.
|
* Add TLS options (skip checks / specify CA) for the Graylog API.
|
||||||
* Read object ownership using `grn_permissions` to preserve privileges on users'
|
* Read object ownership using `grn_permissions` to preserve privileges on users'
|
||||||
own objects
|
own objects
|
||||||
* Read group member records from the LDAP server and extract their username
|
|
||||||
from an attribute.
|
|
||||||
* Support granting ownership on objects
|
* Support granting ownership on objects
|
||||||
* Cleaner CLI
|
* Cleaner CLI
|
||||||
* Use goroutines ? Maybe.
|
* Use goroutines ? Maybe.
|
||||||
|
|
|
@ -37,6 +37,13 @@ ldap:
|
||||||
- uniqueMember
|
- uniqueMember
|
||||||
- memberUid
|
- 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 server info
|
||||||
# --------------------
|
# --------------------
|
||||||
graylog:
|
graylog:
|
||||||
|
|
94
main.go
94
main.go
|
@ -29,10 +29,11 @@ type (
|
||||||
Tls string
|
Tls string
|
||||||
TlsNoVerify bool `yaml:"tls_skip_verify"`
|
TlsNoVerify bool `yaml:"tls_skip_verify"`
|
||||||
TlsAllowCnOnly bool `yaml:"tls_allow_cn_only"`
|
TlsAllowCnOnly bool `yaml:"tls_allow_cn_only"`
|
||||||
CaChain string
|
CaChain string `yaml:"cachain"`
|
||||||
BindUser string `yaml:"bind_user"`
|
BindUser string `yaml:"bind_user"`
|
||||||
BindPassword string `yaml:"bind_password"`
|
BindPassword string `yaml:"bind_password"`
|
||||||
MemberFields []string `yaml:"member_fields"`
|
MemberFields []string `yaml:"member_fields"`
|
||||||
|
UsernameAttr string `yaml:"username_attribute"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graylog server configuration
|
// Graylog server configuration
|
||||||
|
@ -194,22 +195,6 @@ func getGraylogUsers(configuration GraylogConfig) (users []GraylogUser) {
|
||||||
return
|
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
|
// Establish a connection to the LDAP server
|
||||||
func getLdapConnection(cfg LdapConfig) (conn *ldap.Conn) {
|
func getLdapConnection(cfg LdapConfig) (conn *ldap.Conn) {
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
|
@ -248,27 +233,80 @@ func getLdapConnection(cfg LdapConfig) (conn *ldap.Conn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the list of members from a LDAP group
|
// Run a LDAP query to obtain a single object.
|
||||||
func getGroupMembers(group string, conn *ldap.Conn, fields []string) (members []string) {
|
func executeQuery(conn *ldap.Conn, dn string, attrs []string) (bool, *ldap.Entry) {
|
||||||
req := ldap.NewSearchRequest(group, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 1, 0, false, "(objectClass=*)", fields, nil)
|
req := ldap.NewSearchRequest(
|
||||||
|
dn,
|
||||||
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 1, 0, false,
|
||||||
|
"(objectClass=*)", attrs, nil)
|
||||||
res, err := conn.Search(req)
|
res, err := conn.Search(req)
|
||||||
if err != nil {
|
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 {
|
// Read a username from a LDAP record based on a DN.
|
||||||
for _, attr := range fields {
|
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)
|
values := entry.GetAttributeValues(attr)
|
||||||
if len(values) == 0 {
|
if len(values) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
members = make([]string, len(values))
|
for _, value := range values {
|
||||||
for i, value := range values {
|
ok, name := usernameFromMember(value, conn, config)
|
||||||
members[i] = usernameFromMember(value)
|
if ok {
|
||||||
|
members = append(members, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,7 +324,7 @@ func readLdapGroups(configuration Configuration) (groups GroupMembers) {
|
||||||
|
|
||||||
groups = make(GroupMembers)
|
groups = make(GroupMembers)
|
||||||
for group := range configuration.Mapping {
|
for group := range configuration.Mapping {
|
||||||
groups[group] = getGroupMembers(group, conn, configuration.Ldap.MemberFields)
|
groups[group] = getGroupMembers(group, conn, configuration.Ldap)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue