Files
glauth/pkg/server/server.go
T
Chris F Ravenscroft 2eb2215ae4 Database plugins (#133)
This commit actually covers a few items. In future commits, I will keep features distinct. This is only happening this time around due to how long it took to merge this branch.

Covered:

- Database plugins (at this time: SQLite, MySQL, Postgres)
- Backends acting as middleware: added the [[Backends]] configuration directive while retaining backward compatibility with [Backend]
- Schema introspection (root DSE query with base scope)
- When proxying, insert queried attribute back in upstream response, if absent, so that the LDAP library does not filter out all entries
- When chaining backends, any backend can be used to inject OTP value in password, before reaching a non-OTP-aware backend (currently guarded by a True statement in case we find an issue (I did thoroughly test but you never know))
- Handling of special "1.1" attributes filter meaning "I do not want attributes" (RFC 4511, 4.5.1.8)
- Support for "want types only" queries, even when proxying
2021-07-17 11:45:36 -07:00

170 lines
4.6 KiB
Go

package server
import (
"errors"
"fmt"
"plugin"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/handler"
"github.com/go-logr/logr"
"github.com/nmcclain/ldap"
)
type LdapSvc struct {
log logr.Logger
c *config.Config
yubiAuth *yubigo.YubiAuth
l *ldap.Server
}
func NewServer(opts ...Option) (*LdapSvc, error) {
options := newOptions(opts...)
s := LdapSvc{
log: options.Logger,
c: options.Config,
}
var err error
if len(s.c.YubikeyClientID) > 0 && len(s.c.YubikeySecret) > 0 {
s.yubiAuth, err = yubigo.NewYubiAuth(s.c.YubikeyClientID, s.c.YubikeySecret)
if err != nil {
return nil, errors.New("Yubikey Auth failed")
}
}
var helper handler.Handler
// instantiate the helper, if any
if s.c.Helper.Enabled {
switch s.c.Helper.Datastore {
case "config":
helper = handler.NewConfigHandler(
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
)
case "plugin":
plug, err := plugin.Open(s.c.Helper.Plugin)
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to load specified helper plugin: %s", err))
}
nph, err := plug.Lookup(s.c.Helper.PluginHandler)
if err != nil {
return nil, errors.New("Unable to find 'NewPluginHandler' in loaded helper plugin")
}
initFunc, ok := nph.(func(...handler.Option) handler.Handler)
if !ok {
return nil, errors.New("Loaded helper plugin lacks a proper NewPluginHandler function")
}
// Normally, here, we would somehow have imported our plugin into our
// handler namespace. Oops?
helper = initFunc(
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
)
default:
return nil, fmt.Errorf("unsupported helper %s - must be one of 'config', 'plugin'", s.c.Helper.Datastore)
}
s.log.V(3).Info("Using helper", "datastore", s.c.Helper.Datastore)
}
backendCounter := -1
allHandlers := handler.HandlerWrapper{Handlers: make([]handler.Handler, 10), Count: &backendCounter}
// configure the backends
s.l = ldap.NewServer()
s.l.EnforceLDAP = true
for i, backend := range s.c.Backends {
var h handler.Handler
switch backend.Datastore {
case "ldap":
h = handler.NewLdapHandler(
handler.Backend(backend),
handler.Handlers(allHandlers),
handler.Logger(s.log),
handler.Config(s.c),
handler.Helper(helper),
)
case "owncloud":
h = handler.NewOwnCloudHandler(
handler.Backend(backend),
handler.Logger(s.log),
handler.Config(s.c),
)
case "config":
h = handler.NewConfigHandler(
handler.Backend(backend),
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
)
case "plugin":
plug, err := plugin.Open(backend.Plugin)
if err != nil {
return nil, errors.New(fmt.Sprintf("Unable to load specified backend plugin: %s", err))
}
nph, err := plug.Lookup(backend.PluginHandler)
if err != nil {
return nil, errors.New("Unable to find 'NewPluginHandler' in loaded backend plugin")
}
initFunc, ok := nph.(func(...handler.Option) handler.Handler)
if !ok {
return nil, errors.New("Loaded backend plugin lacks a proper NewPluginHandler function")
}
// Normally, here, we would somehow have imported our plugin into our
// handler namespace. Oops?
h = initFunc(
handler.Backend(backend),
handler.Logger(s.log),
handler.Config(s.c),
handler.YubiAuth(s.yubiAuth),
)
default:
return nil, fmt.Errorf("unsupported backend %s - must be one of 'config', 'ldap','owncloud' or 'plugin'", backend.Datastore)
}
s.log.V(3).Info("Loading backend", "datastore", backend.Datastore, "position", i)
// Only our first backend will answer proper LDAP queries.
// Note that this could evolve towars something nicer where we would maintain
// multiple binders in addition to the existing multiple LDAP backends
if i == 0 {
s.l.BindFunc("", h)
s.l.SearchFunc("", h)
s.l.CloseFunc("", h)
}
allHandlers.Handlers[i] = h
backendCounter++
}
return &s, nil
}
// ListenAndServe listens on the TCP network address s.c.LDAP.Listen
func (s *LdapSvc) ListenAndServe() error {
s.log.V(3).Info("LDAP server listening", "address", s.c.LDAP.Listen)
return s.l.ListenAndServe(s.c.LDAP.Listen)
}
// ListenAndServeTLS listens on the TCP network address s.c.LDAPS.Listen
func (s *LdapSvc) ListenAndServeTLS() error {
s.log.V(3).Info("LDAPS server listening", "address", s.c.LDAPS.Listen)
return s.l.ListenAndServeTLS(
s.c.LDAPS.Listen,
s.c.LDAPS.Cert,
s.c.LDAPS.Key,
)
}
// Shutdown ends listeners by sending true to the ldap serves quit channel
func (s *LdapSvc) Shutdown() {
s.l.Quit <- true
}