Initial QSfera import
This commit is contained in:
+254
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Copyright 2021 The LibreGraph Authors.
|
||||
*/
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/bombsimon/logrusr/v3"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/libregraph/idm/pkg/ldapserver"
|
||||
"github.com/libregraph/idm/server/handler"
|
||||
"github.com/libregraph/idm/server/handler/boltdb"
|
||||
"github.com/libregraph/idm/server/handler/ldif"
|
||||
)
|
||||
|
||||
const DefaultGeneratedPasswordLength = 16
|
||||
|
||||
// Server is our server implementation.
|
||||
type Server struct {
|
||||
config *Config
|
||||
|
||||
logger logrus.FieldLogger
|
||||
|
||||
LDAPServer *ldapserver.Server
|
||||
LDAPHandler handler.Handler
|
||||
}
|
||||
|
||||
// NewServer constructs a server from the provided parameters.
|
||||
func NewServer(c *Config) (*Server, error) {
|
||||
s := &Server{
|
||||
config: c,
|
||||
|
||||
logger: c.Logger,
|
||||
}
|
||||
|
||||
s.LDAPServer = ldapserver.NewServer()
|
||||
s.LDAPServer.EnforceLDAP = false
|
||||
s.LDAPServer.GeneratedPasswordLength = DefaultGeneratedPasswordLength
|
||||
ldapserver.Logger(logrusr.New(c.Logger))
|
||||
|
||||
var err error
|
||||
switch c.LDAPHandler {
|
||||
case "ldif":
|
||||
ldifHandlerOptions := &ldif.Options{
|
||||
BaseDN: s.config.LDAPBaseDN,
|
||||
AdminDN: s.config.LDAPAdminDN,
|
||||
AllowLocalAnonymousBind: s.config.LDAPAllowLocalAnonymousBind,
|
||||
|
||||
DefaultCompany: s.config.LDIFDefaultCompany,
|
||||
DefaultMailDomain: s.config.LDIFDefaultMailDomain,
|
||||
TemplateExtraVars: s.config.LDIFTemplateExtraVars,
|
||||
|
||||
TemplateDebug: os.Getenv("KIDM_TEMPLATE_DEBUG") != "",
|
||||
}
|
||||
|
||||
s.LDAPHandler, err = ldif.NewLDIFHandler(s.logger, s.config.LDIFMain, ldifHandlerOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create LDIF source handler: %w", err)
|
||||
}
|
||||
if s.config.LDIFConfig != "" {
|
||||
middleware, middlewareErr := ldif.NewLDIFMiddleware(s.logger, s.config.LDIFConfig, ldifHandlerOptions)
|
||||
if middlewareErr != nil {
|
||||
return nil, fmt.Errorf("failed to create LDIF config handler: %w", middlewareErr)
|
||||
}
|
||||
s.LDAPHandler = middleware.WithHandler(s.LDAPHandler)
|
||||
}
|
||||
case "boltdb":
|
||||
boltOptions := &boltdb.Options{
|
||||
BaseDN: s.config.LDAPBaseDN,
|
||||
AdminDN: s.config.LDAPAdminDN,
|
||||
|
||||
AllowLocalAnonymousBind: s.config.LDAPAllowLocalAnonymousBind,
|
||||
}
|
||||
s.LDAPHandler, err = boltdb.NewBoltDBHandler(s.logger, s.config.BoltDBFile, boltOptions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create BoltDB handler: %w", err)
|
||||
}
|
||||
|
||||
// FIXME Let the frontend (LDAPServer) handle filtering and attribute list until we added backend support
|
||||
s.LDAPServer.EnforceLDAP = true
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown LDAPHandler: '%s'", c.LDAPHandler)
|
||||
}
|
||||
|
||||
if c.Metrics != nil {
|
||||
s.LDAPServer.SetStats(true)
|
||||
MustRegister(c.Metrics, NewLDAPServerCollector(s.LDAPServer))
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Serve starts all the accociated servers resources and listeners and blocks
|
||||
// forever until signals or error occurs.
|
||||
func (s *Server) Serve(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
serveCtx, serveCtxCancel := context.WithCancel(ctx)
|
||||
defer serveCtxCancel()
|
||||
|
||||
logger := s.logger
|
||||
|
||||
errCh := make(chan error, 2)
|
||||
exitCh := make(chan struct{}, 1)
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
readyCh := make(chan struct{}, 1)
|
||||
triggerCh := make(chan bool, 1)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-serveCtx.Done():
|
||||
return
|
||||
case <-readyCh:
|
||||
}
|
||||
logger.WithFields(logrus.Fields{}).Infoln("ready")
|
||||
}()
|
||||
|
||||
var serversWg sync.WaitGroup
|
||||
|
||||
// NOTE(rhafer): since v3.4.3 the ldap package allows to set a custom logger.
|
||||
// Set that to use to our logger.
|
||||
loggerWriter := logger.WithField("scope", "ldap").WriterLevel(logrus.DebugLevel)
|
||||
defer loggerWriter.Close()
|
||||
ldap.Logger(log.New(loggerWriter, "", 0))
|
||||
|
||||
ldapHandler := s.LDAPHandler.WithContext(serveCtx)
|
||||
s.LDAPServer.AddFunc("", ldapHandler)
|
||||
s.LDAPServer.BindFunc("", ldapHandler)
|
||||
s.LDAPServer.DeleteFunc("", ldapHandler)
|
||||
s.LDAPServer.ModifyFunc("", ldapHandler)
|
||||
s.LDAPServer.ModifyDNFunc("", ldapHandler)
|
||||
s.LDAPServer.PasswordExOpFunc("", ldapHandler)
|
||||
s.LDAPServer.SearchFunc("", ldapHandler)
|
||||
s.LDAPServer.CloseFunc("", ldapHandler)
|
||||
|
||||
serversWg.Add(1)
|
||||
go func() {
|
||||
defer serversWg.Done()
|
||||
for {
|
||||
select {
|
||||
case <-triggerCh:
|
||||
reloadErr := ldapHandler.Reload(serveCtx)
|
||||
if reloadErr != nil {
|
||||
logger.Debugln("reload error: %w", reloadErr)
|
||||
} else {
|
||||
logger.Debugln("reload complete")
|
||||
}
|
||||
case <-serveCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if s.config.LDAPListenAddr != "" {
|
||||
serversWg.Add(1)
|
||||
go func() {
|
||||
defer serversWg.Done()
|
||||
logger.WithField("listen_addr", s.config.LDAPListenAddr).Infoln("starting LDAP listener")
|
||||
serveErr := s.LDAPServer.ListenAndServe(s.config.LDAPListenAddr)
|
||||
if serveErr != nil {
|
||||
errCh <- serveErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
if s.config.LDAPSListenAddr != "" {
|
||||
serversWg.Add(1)
|
||||
go func() {
|
||||
defer serversWg.Done()
|
||||
logger.WithField("listen_addr_tls", s.config.LDAPSListenAddr).Infoln("starting LDAPS listener")
|
||||
serveErr := s.LDAPServer.ListenAndServeTLS(s.config.LDAPSListenAddr, s.config.TLSCertFile, s.config.TLSKeyFile)
|
||||
if serveErr != nil {
|
||||
errCh <- serveErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
serversWg.Wait()
|
||||
logger.Debugln("server listeners stopped")
|
||||
close(exitCh)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
close(readyCh) // TODO(longsleep): Implement real ready.
|
||||
if s.config.OnReady != nil {
|
||||
go s.config.OnReady(s)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for exit or error, with support for HUP to reload
|
||||
err = func() error {
|
||||
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
for {
|
||||
select {
|
||||
case errFromChannel := <-errCh:
|
||||
return errFromChannel
|
||||
case reason := <-signalCh:
|
||||
if reason == syscall.SIGHUP {
|
||||
logger.Infoln("reload signal received")
|
||||
select {
|
||||
case triggerCh <- true:
|
||||
default:
|
||||
}
|
||||
continue
|
||||
}
|
||||
logger.WithField("signal", reason).Warnln("received signal")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Shutdown, server will stop to accept new connections, requires Go 1.8+.
|
||||
logger.Infoln("clean server shutdown start")
|
||||
_, shutdownCtxCancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
go func() {
|
||||
close(s.LDAPServer.Quit)
|
||||
}()
|
||||
|
||||
// Cancel our own context,
|
||||
serveCtxCancel()
|
||||
func() {
|
||||
for {
|
||||
select {
|
||||
case <-exitCh:
|
||||
logger.Infoln("clean server shutdown complete, exiting")
|
||||
return
|
||||
default:
|
||||
// Services have not quit yet.
|
||||
logger.Info("waiting for services to exit")
|
||||
}
|
||||
select {
|
||||
case reason := <-signalCh:
|
||||
logger.WithField("signal", reason).Warn("received signal")
|
||||
return
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}()
|
||||
shutdownCtxCancel() // Prevents leak.
|
||||
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user