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.
* Writing logs to a file.
* Document command line flags.
* Cache LDAP username lookups
* Add TLS options (skip checks / specify CA) for the Graylog API.
* Read object ownership using `grn_permissions` to preserve privileges on users'
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
// that includes fields related to the LDAP server and a copy of the initial configuration.
ldapConn struct {
conn *ldap.Conn
log *logrus.Entry
cfg LdapConfig
conn *ldap.Conn
log *logrus.Entry
cfg LdapConfig
usernames map[string]string
counter uint
}
// LDAP group members
@ -25,7 +27,7 @@ type (
)
// 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)
log := log.WithFields(logrus.Fields{
"ldap_server": dest,
@ -77,20 +79,22 @@ func getLdapConnection(cfg LdapConfig) ldapConn {
}
}
log.Debug("LDAP connection established")
return ldapConn{
conn: lc,
log: log,
cfg: cfg,
return &ldapConn{
conn: lc,
log: log,
cfg: cfg,
usernames: make(map[string]string),
}
}
// 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{
"dn": dn,
"attributes": attrs,
})
log.Trace("Accessing DN")
conn.counter++
req := ldap.NewSearchRequest(
dn,
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
func (conn ldapConn) close() {
conn.log.Trace("Closing LDAP connection")
func (conn *ldapConn) close() {
conn.log.WithField("queries", conn.counter).Debug("Closing LDAP connection")
conn.conn.Close()
}
// Read a username from a LDAP record based on a DN.
func (conn ldapConn) readUsername(dn string) (bool, string) {
log := conn.log.WithFields(logrus.Fields{
"dn": dn,
"attribute": conn.cfg.UsernameAttr,
})
log.Trace("Converting DN to username")
func (conn *ldapConn) readUsername(dn string) (bool, string) {
log := conn.log.WithField("dn", dn)
log.Debug("LDAP username lookup")
ok, res := conn.query(dn, []string{conn.cfg.UsernameAttr})
if !ok {
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.
func (conn ldapConn) usernameFromMember(member string) (bool, string) {
func (conn *ldapConn) usernameFromMember(member string) (bool, string) {
eqPos := strings.Index(member, "=")
if eqPos == -1 {
return true, member
@ -160,8 +161,22 @@ func (conn ldapConn) usernameFromMember(member string) (bool, string) {
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
func (conn ldapConn) getGroupMembers(group string) (members []string) {
func (conn *ldapConn) getGroupMembers(group string) (members []string) {
log := conn.log.WithField("group", group)
log.Trace("Obtaining group members")
ok, entry := conn.query(group, conn.cfg.MemberFields)
@ -174,7 +189,7 @@ func (conn ldapConn) getGroupMembers(group string) (members []string) {
continue
}
for _, value := range values {
ok, name := conn.usernameFromMember(value)
ok, name := conn.getUsername(value)
if ok {
members = append(members, name)
}