mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-27 08:10:35 -05:00
255 lines
6.4 KiB
Go
255 lines
6.4 KiB
Go
/*
|
|
* 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
|
|
}
|