feat(auth): AD-Authenticator mit Zwei-Schritt-Bind, DC-Failover und verschachtelter Gruppenprüfung
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
This commit is contained in:
parent
2eb69bdcfe
commit
99ee8758cc
8 changed files with 908 additions and 1 deletions
78
internal/auth/dial.go
Normal file
78
internal/auth/dial.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
// conn ist der Ausschnitt der LDAP-Verbindung, den der Authenticator braucht.
|
||||
// Das Interface existiert, damit Tests ohne echten Verzeichnisdienst laufen.
|
||||
type conn interface {
|
||||
Bind(username, password string) error
|
||||
Search(req *ldap.SearchRequest) (*ldap.SearchResult, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ldapConn adaptiert *ldap.Conn an conn (Close hat dort keine Fehlerrückgabe).
|
||||
type ldapConn struct{ *ldap.Conn }
|
||||
|
||||
func (c ldapConn) Close() error { c.Conn.Close(); return nil }
|
||||
|
||||
// tlsConfigFor baut die TLS-Konfiguration. Verifikation ist immer aktiv —
|
||||
// für LDAP gibt es bewusst keine Insecure-Option.
|
||||
func tlsConfigFor(server, caFile string) (*tls.Config, error) {
|
||||
cfg := &tls.Config{ServerName: server, MinVersion: tls.VersionTLS12}
|
||||
if caFile == "" {
|
||||
return cfg, nil
|
||||
}
|
||||
pem, err := os.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ad.ca_file %s: %w", caFile, err)
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(pem) {
|
||||
return nil, fmt.Errorf("ad.ca_file %s enthält kein gültiges PEM-Zertifikat", caFile)
|
||||
}
|
||||
cfg.RootCAs = pool
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
// realDialer erzeugt die Dial-Funktion für den Produktivbetrieb.
|
||||
func realDialer(port int, tlsMode, caFile string, timeout time.Duration) func(context.Context, string) (conn, error) {
|
||||
return func(ctx context.Context, server string) (conn, error) {
|
||||
tlsCfg, err := tlsConfigFor(server, caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr := net.JoinHostPort(server, strconv.Itoa(port))
|
||||
dialer := &net.Dialer{Timeout: timeout}
|
||||
|
||||
var c *ldap.Conn
|
||||
if tlsMode == "starttls" {
|
||||
c, err = ldap.DialURL("ldap://"+addr, ldap.DialWithDialer(dialer))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Verbindung zu %s: %w", addr, err)
|
||||
}
|
||||
if err := c.StartTLS(tlsCfg); err != nil {
|
||||
c.Close()
|
||||
return nil, fmt.Errorf("StartTLS zu %s: %w", addr, err)
|
||||
}
|
||||
} else {
|
||||
c, err = ldap.DialURL("ldaps://"+addr,
|
||||
ldap.DialWithDialer(dialer), ldap.DialWithTLSConfig(tlsCfg))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("LDAPS-Verbindung zu %s: %w", addr, err)
|
||||
}
|
||||
}
|
||||
c.SetTimeout(timeout)
|
||||
return ldapConn{c}, nil
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue