internal/ldap: Implement search for entities

This commit is contained in:
Michael Aldridge 2020-08-23 00:14:41 -07:00
parent 2b8ce28a83
commit 2cf2849cea
4 changed files with 115 additions and 34 deletions

1
go.mod
View file

@ -6,6 +6,7 @@ require (
github.com/hashicorp/go-hclog v0.9.2 github.com/hashicorp/go-hclog v0.9.2
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 // indirect
github.com/netauth/netauth v0.3.4 github.com/netauth/netauth v0.3.4
github.com/netauth/protocol v0.0.0-20191124005711-167b58b61c72
github.com/ps78674/goldap v0.0.0-20200721080011-cd2e7ee23841 github.com/ps78674/goldap v0.0.0-20200721080011-cd2e7ee23841
github.com/ps78674/ldapserver v0.0.0-20200521101606-2395f680392c github.com/ps78674/ldapserver v0.0.0-20200521101606-2395f680392c
github.com/spf13/viper v1.3.2 github.com/spf13/viper v1.3.2

View file

@ -17,20 +17,18 @@ func New(l hclog.Logger, nacl naClient) *server {
x.c = nacl x.c = nacl
x.Server = ldap.NewServer() x.Server = ldap.NewServer()
routes := ldap.NewRouteMux() x.routes = ldap.NewRouteMux()
routes.NotFound(x.handleNotFound) x.routes.NotFound(x.handleNotFound)
routes.Abandon(x.handleAbandon) x.routes.Abandon(x.handleAbandon)
routes.Bind(x.handleBind) x.routes.Bind(x.handleBind)
routes.Search(x.handleSearchDSE). x.routes.Search(x.handleSearchDSE).
BaseDn(""). BaseDn("").
Scope(ldap.SearchRequestScopeBaseObject). Scope(ldap.SearchRequestScopeBaseObject).
Filter("(objectclass=*)"). Filter("(objectclass=*)").
Label("Search - ROOT DSE") Label("Search - ROOT DSE")
routes.Search(x.handleSearch).Label("Search - Generic") x.Handle(x.routes)
x.Handle(routes)
return x return x
} }
@ -88,4 +86,12 @@ func (s *server) SetDomain(domain string) {
for i := range s.nc { for i := range s.nc {
s.nc[i] = strings.TrimSpace(s.nc[i]) s.nc[i] = strings.TrimSpace(s.nc[i])
} }
// Register routes that are dependent on the namingConvention
entitySearchDN := "ou=entities," + strings.Join(s.nc, ",")
s.routes.Search(s.handleSearchEntities).
BaseDn(entitySearchDN).
Scope(ldap.SearchRequestHomeSubtree).
Label("Search - Entities")
} }

View file

@ -1,13 +1,17 @@
package ldap package ldap
import ( import (
"log" "context"
"errors"
"strconv"
"strings" "strings"
"github.com/ps78674/goldap/message" "github.com/ps78674/goldap/message"
ldap "github.com/ps78674/ldapserver" ldap "github.com/ps78674/ldapserver"
"github.com/netauth/ldap/internal/buildinfo" "github.com/netauth/ldap/internal/buildinfo"
pb "github.com/netauth/protocol"
) )
func (s *server) handleSearchDSE(w ldap.ResponseWriter, m *ldap.Message) { func (s *server) handleSearchDSE(w ldap.ResponseWriter, m *ldap.Message) {
@ -25,37 +29,101 @@ func (s *server) handleSearchDSE(w ldap.ResponseWriter, m *ldap.Message) {
w.Write(res) w.Write(res)
} }
func (s *server) handleSearch(w ldap.ResponseWriter, m *ldap.Message) { func (s *server) handleSearchEntities(w ldap.ResponseWriter, m *ldap.Message) {
r := m.GetSearchRequest() ctx := context.Background()
log.Printf("Request BaseDn=%s", r.BaseObject()) s.l.Debug("Search Entities")
log.Printf("Request Filter=%s", r.Filter())
log.Printf("Request FilterString=%s", r.FilterString())
log.Printf("Request Attributes=%s", r.Attributes())
log.Printf("Request TimeLimit=%d", r.TimeLimit().Int())
// Handle Stop Signal (server stop / client disconnected / Abandoned request....) r := m.GetSearchRequest()
select {
case <-m.Done: // This switch performs stage one of mapping from an ldap
log.Print("Leaving handleSearch...") // search expression to a NetAuth search expression. The
return // second phase of the mapping happens in another function.
var expr string
var err error
switch r.Filter().(type) {
case message.FilterEqualityMatch:
f := r.Filter().(message.FilterEqualityMatch)
expr, err = entitySearchExprHelper(string(f.AttributeDesc()), "=", string(f.AssertionValue()))
default: default:
err = errors.New("unsupported filter type")
}
if err != nil {
// If err is non-nil at this point it must mean that
// the above match didn't find a supported filter.
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultUnwillingToPerform)
res.SetDiagnosticMessage("Filter type not supported")
w.Write(res)
return
} }
e := ldap.NewSearchResultEntry("cn=Valere JEANTET, " + string(r.BaseObject())) s.l.Debug("Searching entities", "query", expr)
e.AddAttribute("mail", "valere.jeantet@gmail.com", "mail@vjeantet.fr")
e.AddAttribute("company", "SODADI")
e.AddAttribute("department", "DSI/SEC")
e.AddAttribute("l", "Ferrieres en brie")
e.AddAttribute("mobile", "0612324567")
e.AddAttribute("telephoneNumber", "0612324567")
e.AddAttribute("cn", "Valère JEANTET")
w.Write(e)
e = ldap.NewSearchResultEntry("cn=Claire Thomas, " + string(r.BaseObject())) ents, err := s.c.EntitySearch(ctx, expr)
e.AddAttribute("mail", "claire.thomas@gmail.com") if err != nil {
e.AddAttribute("cn", "Claire THOMAS") res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError)
w.Write(e) res.SetDiagnosticMessage(err.Error())
w.Write(res)
return
}
for i := range ents {
e, err := s.entitySearchResult(ctx, ents[i], r.BaseObject(), r.Attributes())
if err != nil {
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultOperationsError)
res.SetDiagnosticMessage(err.Error())
w.Write(res)
return
}
w.Write(e)
}
s.l.Debug("Entities", "res", ents)
res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess) res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
w.Write(res) w.Write(res)
} }
// entitySearchResult maps an entity onto a SearchResultEntry,
// performing the additional lookup for groups to populate the
// memberOf attribute. Though not implemented, the attrs list is
// plumbed down to this level to permit attribute filtering in the
// future.
func (s *server) entitySearchResult(ctx context.Context, e *pb.Entity, dn message.LDAPDN, attrs message.AttributeSelection) (message.SearchResultEntry, error) {
res := ldap.NewSearchResultEntry("uid=" + e.GetID() + "," + string(dn))
res.AddAttribute("uid", message.AttributeValue(e.GetID()))
res.AddAttribute("uidNumber", message.AttributeValue(strconv.Itoa(int(e.GetNumber()))))
grps, err := s.c.EntityGroups(ctx, e.GetID())
if err != nil {
return res, err
}
for i := range grps {
g := "cn=" + grps[i].GetName() + ",ou=groups," + strings.Join(s.nc, ",")
res.AddAttribute("memberOf", message.AttributeValue(g))
}
return res, nil
}
// entitySearchExprHelper helps in mapping ldap search expressions to
// search expressions that NetAuth understands.
func entitySearchExprHelper(attr, op, val string) (string, error) {
var predicate, operator string
switch attr {
case "uid":
predicate = "ID"
default:
return "", errors.New("search attribute is unsupported")
}
switch op {
case "=":
operator = "="
default:
return "", errors.New("search comparison is unsupported")
}
return predicate + operator + val, nil
}

View file

@ -5,15 +5,21 @@ import (
"github.com/hashicorp/go-hclog" "github.com/hashicorp/go-hclog"
ldap "github.com/ps78674/ldapserver" ldap "github.com/ps78674/ldapserver"
pb "github.com/netauth/protocol"
) )
type naClient interface { type naClient interface {
AuthEntity(context.Context, string, string) error AuthEntity(context.Context, string, string) error
EntitySearch(context.Context, string) ([]*pb.Entity, error)
EntityGroups(context.Context, string) ([]*pb.Group, error)
} }
type server struct { type server struct {
*ldap.Server *ldap.Server
routes *ldap.RouteMux
c naClient c naClient
l hclog.Logger l hclog.Logger