Cache for LDAP username lookups

It is unnecessary to request the same user's username attribute more
than once.
This commit is contained in:
Emmanuel BENOîT 2021-02-11 22:46:29 +01:00
parent 84fc80bf0a
commit c84f52b012
2 changed files with 35 additions and 21 deletions

View file

@ -57,7 +57,6 @@ To Do
* Sending logs to... well, Graylog... through CLI switches. * Sending logs to... well, Graylog... through CLI switches.
* Writing logs to a file. * Writing logs to a file.
* Document command line flags. * Document command line flags.
* Cache LDAP username lookups
* 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

55
ldap.go
View file

@ -15,9 +15,11 @@ type (
// LDAP connection encapsulation. This includes the connection itself, as well as a logger // LDAP connection encapsulation. This includes the connection itself, as well as a logger
// that includes fields related to the LDAP server and a copy of the initial configuration. // that includes fields related to the LDAP server and a copy of the initial configuration.
ldapConn struct { ldapConn struct {
conn *ldap.Conn conn *ldap.Conn
log *logrus.Entry log *logrus.Entry
cfg LdapConfig cfg LdapConfig
usernames map[string]string
counter uint
} }
// LDAP group members // LDAP group members
@ -25,7 +27,7 @@ type (
) )
// Establish a connection to the LDAP server // Establish a connection to the LDAP server
func getLdapConnection(cfg LdapConfig) ldapConn { func getLdapConnection(cfg LdapConfig) *ldapConn {
dest := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) dest := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
log := log.WithFields(logrus.Fields{ log := log.WithFields(logrus.Fields{
"ldap_server": dest, "ldap_server": dest,
@ -77,20 +79,22 @@ func getLdapConnection(cfg LdapConfig) ldapConn {
} }
} }
log.Debug("LDAP connection established") log.Debug("LDAP connection established")
return ldapConn{ return &ldapConn{
conn: lc, conn: lc,
log: log, log: log,
cfg: cfg, cfg: cfg,
usernames: make(map[string]string),
} }
} }
// Run a LDAP query to obtain a single object. // Run a LDAP query to obtain a single object.
func (conn ldapConn) query(dn string, attrs []string) (bool, *ldap.Entry) { func (conn *ldapConn) query(dn string, attrs []string) (bool, *ldap.Entry) {
log := conn.log.WithFields(logrus.Fields{ log := conn.log.WithFields(logrus.Fields{
"dn": dn, "dn": dn,
"attributes": attrs, "attributes": attrs,
}) })
log.Trace("Accessing DN") log.Trace("Accessing DN")
conn.counter++
req := ldap.NewSearchRequest( req := ldap.NewSearchRequest(
dn, dn,
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 1, 0, false, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 1, 0, false,
@ -114,18 +118,15 @@ func (conn ldapConn) query(dn string, attrs []string) (bool, *ldap.Entry) {
} }
// Close a LDAP connection // Close a LDAP connection
func (conn ldapConn) close() { func (conn *ldapConn) close() {
conn.log.Trace("Closing LDAP connection") conn.log.WithField("queries", conn.counter).Debug("Closing LDAP connection")
conn.conn.Close() conn.conn.Close()
} }
// Read a username from a LDAP record based on a DN. // Read a username from a LDAP record based on a DN.
func (conn ldapConn) readUsername(dn string) (bool, string) { func (conn *ldapConn) readUsername(dn string) (bool, string) {
log := conn.log.WithFields(logrus.Fields{ log := conn.log.WithField("dn", dn)
"dn": dn, log.Debug("LDAP username lookup")
"attribute": conn.cfg.UsernameAttr,
})
log.Trace("Converting DN to username")
ok, res := conn.query(dn, []string{conn.cfg.UsernameAttr}) ok, res := conn.query(dn, []string{conn.cfg.UsernameAttr})
if !ok { if !ok {
return false, "" return false, ""
@ -141,7 +142,7 @@ func (conn ldapConn) readUsername(dn string) (bool, string) {
} }
// Extract an username from something that may be an username or a DN. // Extract an username from something that may be an username or a DN.
func (conn ldapConn) usernameFromMember(member string) (bool, string) { func (conn *ldapConn) usernameFromMember(member string) (bool, string) {
eqPos := strings.Index(member, "=") eqPos := strings.Index(member, "=")
if eqPos == -1 { if eqPos == -1 {
return true, member return true, member
@ -160,8 +161,22 @@ func (conn ldapConn) usernameFromMember(member string) (bool, string) {
return true, member[eqPos+1 : commaPos] return true, member[eqPos+1 : commaPos]
} }
// Read a username from the cache. If the username is not cached, extract it or request it from
// the LDAP.
func (conn *ldapConn) getUsername(member string) (bool, string) {
name, ok := conn.usernames[member]
if ok {
return true, name
}
ok, name = conn.usernameFromMember(member)
if ok {
conn.usernames[member] = name
}
return ok, name
}
// Read the list of members from a LDAP group // Read the list of members from a LDAP group
func (conn ldapConn) getGroupMembers(group string) (members []string) { func (conn *ldapConn) getGroupMembers(group string) (members []string) {
log := conn.log.WithField("group", group) log := conn.log.WithField("group", group)
log.Trace("Obtaining group members") log.Trace("Obtaining group members")
ok, entry := conn.query(group, conn.cfg.MemberFields) ok, entry := conn.query(group, conn.cfg.MemberFields)
@ -174,7 +189,7 @@ func (conn ldapConn) getGroupMembers(group string) (members []string) {
continue continue
} }
for _, value := range values { for _, value := range values {
ok, name := conn.usernameFromMember(value) ok, name := conn.getUsername(value)
if ok { if ok {
members = append(members, name) members = append(members, name)
} }