From 82a3d0778cefe696643ba98c5db7e410a54bc85d Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Tue, 30 Jan 2024 22:46:18 +0100 Subject: [PATCH 1/5] revamp listener config in prepartion of ldap support --- cmd/whawty-auth/{web_config.go => config.go} | 36 +++- cmd/whawty-auth/main.go | 179 ++++++++++++------- cmd/whawty-auth/web_api.go | 52 ++++-- contrib/listeners-config.yaml | 57 ++++++ contrib/web-config.yaml | 40 ----- 5 files changed, 235 insertions(+), 129 deletions(-) rename cmd/whawty-auth/{web_config.go => config.go} (68%) create mode 100644 contrib/listeners-config.yaml delete mode 100644 contrib/web-config.yaml diff --git a/cmd/whawty-auth/web_config.go b/cmd/whawty-auth/config.go similarity index 68% rename from cmd/whawty-auth/web_config.go rename to cmd/whawty-auth/config.go index 1d6660d..e94db10 100644 --- a/cmd/whawty-auth/web_config.go +++ b/cmd/whawty-auth/config.go @@ -38,11 +38,39 @@ import ( "gopkg.in/yaml.v3" ) -type webConfig struct { - TLS *tlsconfig.TLSConfig `yaml:"tls"` +type saslauthdConfig struct { + Listen []string `yaml:"listen"` } -func readWebConfig(configfile string) (*webConfig, error) { +type httpConfig struct { + Listen []string `yaml:"listen"` +} + +type httpsConfig struct { + Listen []string `yaml:"listen"` + TLS *tlsconfig.TLSConfig `yaml:"tls"` +} + +// type ldapConfig struct { +// Listen []string `yaml:"listen"` +// StartTLS bool `yaml:"start-tls"` +// TLS *tlsconfig.TLSConfig `yaml:"tls"` +// } + +// type ldapsConfig struct { +// Listen []string `yaml:"listen"` +// TLS *tlsconfig.TLSConfig `yaml:"tls"` +// } + +type listenerConfig struct { + SASLAuthd *saslauthdConfig `yaml:"saslauthd"` + HTTP *httpConfig `yaml:"http"` + HTTPs *httpsConfig `yaml:"https"` + // LDAP *ldapConfig `yaml:"ldap"` + // LDAPs *ldapsConfig `yaml:"ldaps"` +} + +func readListenerConfig(configfile string) (*listenerConfig, error) { file, err := os.Open(configfile) if err != nil { return nil, err @@ -52,7 +80,7 @@ func readWebConfig(configfile string) (*webConfig, error) { decoder := yaml.NewDecoder(file) decoder.KnownFields(true) - c := &webConfig{} + c := &listenerConfig{} if err = decoder.Decode(c); err != nil { return nil, fmt.Errorf("Error parsing config file: %s", err) } diff --git a/cmd/whawty-auth/main.go b/cmd/whawty-auth/main.go index 0cca5dd..68c6fae 100644 --- a/cmd/whawty-auth/main.go +++ b/cmd/whawty-auth/main.go @@ -365,36 +365,50 @@ func cmdRun(c *cli.Context) error { return cli.NewExitError(err.Error(), 3) } - var webc *webConfig - if c.String("web-config") != "" { - webc, err = readWebConfig(c.String("web-config")) + var lc *listenerConfig + if c.String("listener") != "" { + lc, err = readListenerConfig(c.String("listener")) if err != nil { return cli.NewExitError(err.Error(), 1) } } - webAddrs := c.StringSlice("web-addr") - saslPaths := c.StringSlice("sock") var wg sync.WaitGroup - for _, webAddr := range webAddrs { - a := webAddr - wg.Add(1) - go func() { - defer wg.Done() - if err := runWebAddr(a, webc, s.GetInterface()); err != nil { - fmt.Printf("warning running web interface(%s) failed: %s\n", a, err) - } - }() - } - for _, path := range saslPaths { - p := path - wg.Add(1) - go func() { - defer wg.Done() - if err := runSaslAuthSocket(p, s.GetInterface()); err != nil { - fmt.Printf("warning running auth agent(%s) failed: %s\n", p, err) - } - }() + if lc.SASLAuthd != nil { + for _, path := range lc.SASLAuthd.Listen { + p := path + wg.Add(1) + go func() { + defer wg.Done() + if err := runSaslAuthSocket(p, s.GetInterface()); err != nil { + fmt.Printf("warning running auth-socket failed: %s\n", err) + } + }() + } + } + if lc.HTTP != nil { + for _, addr := range lc.HTTP.Listen { + a := addr + wg.Add(1) + go func() { + defer wg.Done() + if err := runHTTPAddr(a, lc.HTTP, s.GetInterface()); err != nil { + fmt.Printf("warning running web-api failed: %s\n", err) + } + }() + } + } + if lc.HTTPs != nil { + for _, addr := range lc.HTTPs.Listen { + a := addr + wg.Add(1) + go func() { + defer wg.Done() + if err := runHTTPsAddr(a, lc.HTTPs, s.GetInterface()); err != nil { + fmt.Printf("warning running web-api failed: %s\n", err) + } + }() + } } wg.Wait() @@ -407,50 +421,83 @@ func cmdRunSa(c *cli.Context) error { return cli.NewExitError(err.Error(), 3) } - var webc *webConfig - if c.String("web-config") != "" { - webc, err = readWebConfig(c.String("web-config")) + var lc *listenerConfig + if c.String("listener") != "" { + lc, err = readListenerConfig(c.String("listener")) if err != nil { return cli.NewExitError(err.Error(), 1) } } - listeners, err := activation.Listeners() + listenerGroups, err := activation.ListenersWithNames() if err != nil { return cli.NewExitError(fmt.Sprintf("fetching socket listeners from systemd failed: %s", err), 2) } - fmt.Printf("got %d sockets from systemd\n", len(listeners)) - if len(listeners) == 0 { + fmt.Printf("got %d listener-groups from systemd\n", len(listenerGroups)) + if len(listenerGroups) == 0 { return cli.NewExitError("shutting down since there are no sockets to lissten on.", 2) } var wg sync.WaitGroup - for idx, listener := range listeners { - switch listener.(type) { - case *net.UnixListener: - fmt.Printf("listener[%d]: is a UNIX socket (-> saslauthd)\n", idx) - wg.Add(1) - ln := listener.(*net.UnixListener) - go func() { - defer wg.Done() - if err := runSaslAuthSocketListener(ln, s.GetInterface()); err != nil { - fmt.Printf("warning running auth agent failed: %s\n", err) + for name, listeners := range listenerGroups { + switch name { + case "saslauthd": + if lc.SASLAuthd == nil { + fmt.Printf("ingoring unexpected socket for saslauthd-compatible listener (no config found in listener-config)\n") + continue + } + for _, listener := range listeners { + ln, ok := listener.(*net.UnixListener) + if !ok { + fmt.Printf("ingoring invalid socket type %T for saslauthd-compatible listener\n", listener) } - }() - case *net.TCPListener: - fmt.Printf("listener[%d]: is a TCP socket (-> HTTP)\n", idx) - wg.Add(1) - ln := listener.(*net.TCPListener) - go func() { - defer wg.Done() - if err := runWebListener(ln, webc, s.GetInterface()); err != nil { - fmt.Printf("error running web-api: %s", err) + wg.Add(1) + go func() { + defer wg.Done() + if err := runSaslAuthSocketListener(ln, s.GetInterface()); err != nil { + fmt.Printf("warning running auth-socket failed: %s\n", err) + } + }() + } + case "http": + if lc.HTTP == nil { + fmt.Printf("ingoring unexpected socket for HTTP listener (no config found in listener-config)\n") + continue + } + for _, listener := range listeners { + ln, ok := listener.(*net.TCPListener) + if !ok { + fmt.Printf("ingoring invalid socket type %T for HTTP listener\n", listener) } - }() - default: - fmt.Printf("listener[%d]: has type %T (ingnoring)\n", idx, listener) + wg.Add(1) + go func() { + defer wg.Done() + if err := runHTTPListener(ln, lc.HTTP, s.GetInterface()); err != nil { + fmt.Printf("warning running web-api failed: %s\n", err) + } + }() + } + case "https": + if lc.HTTPs == nil { + fmt.Printf("ingoring unexpected socket for HTTPs listener (no config found in listener-config)\n") + continue + } + for _, listener := range listeners { + ln, ok := listener.(*net.TCPListener) + if !ok { + fmt.Printf("ingoring invalid socket type %T for HTTPs listener\n", listener) + } + wg.Add(1) + go func() { + defer wg.Done() + if err := runHTTPsListener(ln, lc.HTTPs, s.GetInterface()); err != nil { + fmt.Printf("warning running web-api failed: %s\n", err) + } + }() + } } + } wg.Wait() @@ -558,27 +605,25 @@ func main() { Usage: "run the auth agent", Flags: []cli.Flag{ cli.StringFlag{ - Name: "web-config", + Name: "listener", Value: "", - Usage: "path to the web configuration file", - EnvVar: "WHAWTY_AUTH_WEB_CONFIG", - }, - cli.StringSliceFlag{ - Name: "sock", - Usage: "path to saslauthd compatible unix socket interface", - EnvVar: "WHAWTY_AUTH_SASL_SOCK", - }, - cli.StringSliceFlag{ - Name: "web-addr", - Usage: "address to listen on for web API", - EnvVar: "WHAWTY_AUTH_WEB_ADDR", + Usage: "path to the listener configuration file", + EnvVar: "WHAWTY_AUTH_LISTENER_CONFIG", }, }, Action: cmdRun, }, { - Name: "runsa", - Usage: "run the auth agent (using systemd socket-activation)", + Name: "runsa", + Usage: "run the auth agent (using systemd socket-activation)", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "listener", + Value: "", + Usage: "path to the listener configuration file", + EnvVar: "WHAWTY_AUTH_LISTENER_CONFIG", + }, + }, Action: cmdRunSa, }, } diff --git a/cmd/whawty-auth/web_api.go b/cmd/whawty-auth/web_api.go index b7b5eeb..ead8a0c 100644 --- a/cmd/whawty-auth/web_api.go +++ b/cmd/whawty-auth/web_api.go @@ -477,13 +477,13 @@ func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { return tc, nil } -func runWebApi(listener *net.TCPListener, config *webConfig, store *Store) (err error) { +func newWebHandler(store *Store) (mux *http.ServeMux, err error) { var sessions *webSessionFactory if sessions, err = NewWebSessionFactory(600 * time.Second); err != nil { // TODO: hardcoded value - return err + return } - mux := http.NewServeMux() + mux = http.NewServeMux() mux.Handle("/api/authenticate", webHandler{store, sessions, handleWebAuthenticate}) mux.Handle("/api/add", webHandler{store, sessions, handleWebAdd}) mux.Handle("/api/remove", webHandler{store, sessions, handleWebRemove}) @@ -493,7 +493,6 @@ func runWebApi(listener *net.TCPListener, config *webConfig, store *Store) (err mux.Handle("/api/list-full", webHandler{store, sessions, handleWebListFull}) mux.Handle("/admin/", http.StripPrefix("/admin/", http.FileServer(http.FS(ui.Assets)))) - mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) @@ -501,32 +500,49 @@ func runWebApi(listener *net.TCPListener, config *webConfig, store *Store) (err } http.Redirect(w, r, "/admin/", http.StatusTemporaryRedirect) }) + return +} - server := &http.Server{Handler: mux, ReadTimeout: 60 * time.Second, WriteTimeout: 60 * time.Second} - if config != nil && config.TLS != nil { - server.TLSConfig, err = config.TLS.ToGoTLSConfig() - if err != nil { - return - } - wl.Printf("web-api: listening on '%s' using TLS", listener.Addr()) - return server.ServeTLS(tcpKeepAliveListener{listener}, "", "") +func runHTTPsListener(listener *net.TCPListener, config *httpsConfig, store *Store) (err error) { + server := &http.Server{ReadTimeout: 60 * time.Second, WriteTimeout: 60 * time.Second} + if server.Handler, err = newWebHandler(store); err != nil { + return + } + if server.TLSConfig, err = config.TLS.ToGoTLSConfig(); err != nil { + return + } + wl.Printf("web-api: listening on '%s' using TLS", listener.Addr()) + return server.ServeTLS(tcpKeepAliveListener{listener}, "", "") +} + +func runHTTPsAddr(addr string, config *httpsConfig, store *Store) error { + if addr == "" { + addr = ":https" + } + listener, err := net.Listen("tcp", addr) + if err != nil { + return err + } + return runHTTPsListener(listener.(*net.TCPListener), config, store) +} +func runHTTPListener(listener *net.TCPListener, config *httpConfig, store *Store) (err error) { + server := &http.Server{ReadTimeout: 60 * time.Second, WriteTimeout: 60 * time.Second} + if server.Handler, err = newWebHandler(store); err != nil { + return } wl.Printf("web-api: listening on '%s'", listener.Addr()) return server.Serve(tcpKeepAliveListener{listener}) } -func runWebAddr(addr string, config *webConfig, store *Store) (err error) { +func runHTTPAddr(addr string, config *httpConfig, store *Store) error { if addr == "" { addr = ":http" } - ln, err := net.Listen("tcp", addr) + listener, err := net.Listen("tcp", addr) if err != nil { return err } - return runWebApi(ln.(*net.TCPListener), config, store) -} -func runWebListener(listener *net.TCPListener, config *webConfig, store *Store) (err error) { - return runWebApi(listener, config, store) + return runHTTPListener(listener.(*net.TCPListener), config, store) } diff --git a/contrib/listeners-config.yaml b/contrib/listeners-config.yaml new file mode 100644 index 0000000..75d8889 --- /dev/null +++ b/contrib/listeners-config.yaml @@ -0,0 +1,57 @@ +--- +saslauthd: + listen: + - /run/whawty/auth.sock +https: + listen: + - 127.0.0.1:443 + tls: + certificate: "/path/to/server-crt.pem" + certificate-key: "/path/to/server-key.pem" + min-protocol-version: "TLSv1.2" + # max-protocol-version: "TLSv1.3" + ciphers: + # - RSA_WITH_RC4_128_SHA + # - RSA_WITH_3DES_EDE_CBC_SHA + # - RSA_WITH_AES_128_CBC_SHA + # - RSA_WITH_AES_256_CBC_SHA + # - RSA_WITH_AES_128_CBC_SHA256 + # - RSA_WITH_AES_128_GCM_SHA256 + # - RSA_WITH_AES_256_GCM_SHA384 + # - ECDHE_ECDSA_WITH_RC4_128_SHA + # - ECDHE_ECDSA_WITH_AES_128_CBC_SHA + # - ECDHE_ECDSA_WITH_AES_256_CBC_SHA + # - ECDHE_RSA_WITH_RC4_128_SHA + # - ECDHE_RSA_WITH_3DES_EDE_CBC_SHA + # - ECDHE_RSA_WITH_AES_128_CBC_SHA + # - ECDHE_RSA_WITH_AES_256_CBC_SHA + # - ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 + # - ECDHE_RSA_WITH_AES_128_CBC_SHA256 + - ECDHE_RSA_WITH_AES_128_GCM_SHA256 + # - ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 + - ECDHE_RSA_WITH_AES_256_GCM_SHA384 + # - ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 + - ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + # - ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 + - TLS_AES_128_GCM_SHA256 + - TLS_AES_256_GCM_SHA384 + - TLS_CHACHA20_POLY1305_SHA256 + prefer-server-ciphers: true + # ecdh-curves: + # - secp256r1 + # - secp384r1 + # - secp521r1 + # - x25519 + # session-tickets: true + # session-ticket-key: "b947e39f50e20351bdd81046e20fff7948d359a3aec391719d60645c5972cc77" +# ldap: +# listen: +# - 127.0.0.1:389 +# start-tls: true +# ldaps: +# listen: +# - 127.0.0.1:636 +# tls: +# certificate: "/path/to/server-crt.pem" +# certificate-key: "/path/to/server-key.pem" +# min-protocol-version: "TLSv1.2" diff --git a/contrib/web-config.yaml b/contrib/web-config.yaml deleted file mode 100644 index 2fad12a..0000000 --- a/contrib/web-config.yaml +++ /dev/null @@ -1,40 +0,0 @@ ---- -tls: - certificate: "/path/to/server-crt.pem" - certificate-key: "/path/to/server-key.pem" - min-protocol-version: "TLSv1.2" - # max-protocol-version: "TLSv1.3" - ciphers: - # - RSA_WITH_RC4_128_SHA - # - RSA_WITH_3DES_EDE_CBC_SHA - # - RSA_WITH_AES_128_CBC_SHA - # - RSA_WITH_AES_256_CBC_SHA - # - RSA_WITH_AES_128_CBC_SHA256 - # - RSA_WITH_AES_128_GCM_SHA256 - # - RSA_WITH_AES_256_GCM_SHA384 - # - ECDHE_ECDSA_WITH_RC4_128_SHA - # - ECDHE_ECDSA_WITH_AES_128_CBC_SHA - # - ECDHE_ECDSA_WITH_AES_256_CBC_SHA - # - ECDHE_RSA_WITH_RC4_128_SHA - # - ECDHE_RSA_WITH_3DES_EDE_CBC_SHA - # - ECDHE_RSA_WITH_AES_128_CBC_SHA - # - ECDHE_RSA_WITH_AES_256_CBC_SHA - # - ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 - # - ECDHE_RSA_WITH_AES_128_CBC_SHA256 - - ECDHE_RSA_WITH_AES_128_GCM_SHA256 - # - ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 - - ECDHE_RSA_WITH_AES_256_GCM_SHA384 - # - ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 - - ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 - # - ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 - - TLS_AES_128_GCM_SHA256 - - TLS_AES_256_GCM_SHA384 - - TLS_CHACHA20_POLY1305_SHA256 - prefer-server-ciphers: true - # ecdh-curves: - # - secp256r1 - # - secp384r1 - # - secp521r1 - # - x25519 - # session-tickets: true - # session-ticket-key: "b947e39f50e20351bdd81046e20fff7948d359a3aec391719d60645c5972cc77" From 49db14b70108002a2db4aee312637d3431070f60 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Wed, 31 Jan 2024 00:03:49 +0100 Subject: [PATCH 2/5] cosmetic fix --- contrib/{listeners-config.yaml => listener-cfg.yml} | 0 contrib/{sample-cfg.yaml => store-cfg.yml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename contrib/{listeners-config.yaml => listener-cfg.yml} (100%) rename contrib/{sample-cfg.yaml => store-cfg.yml} (100%) diff --git a/contrib/listeners-config.yaml b/contrib/listener-cfg.yml similarity index 100% rename from contrib/listeners-config.yaml rename to contrib/listener-cfg.yml diff --git a/contrib/sample-cfg.yaml b/contrib/store-cfg.yml similarity index 100% rename from contrib/sample-cfg.yaml rename to contrib/store-cfg.yml From f876db8b7667cfbdadc4dc5e76a3f9931ce89918 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Wed, 31 Jan 2024 01:09:37 +0100 Subject: [PATCH 3/5] add new example systemd units --- contrib/systemd/whawty-auth.socket | 13 ------------- contrib/systemd/whawty-auth_http.socket | 10 ++++++++++ contrib/systemd/whawty-auth_https.socket | 11 +++++++++++ contrib/systemd/whawty-auth_saslauthd.socket | 15 +++++++++++++++ 4 files changed, 36 insertions(+), 13 deletions(-) delete mode 100644 contrib/systemd/whawty-auth.socket create mode 100644 contrib/systemd/whawty-auth_http.socket create mode 100644 contrib/systemd/whawty-auth_https.socket create mode 100644 contrib/systemd/whawty-auth_saslauthd.socket diff --git a/contrib/systemd/whawty-auth.socket b/contrib/systemd/whawty-auth.socket deleted file mode 100644 index 5d29d3f..0000000 --- a/contrib/systemd/whawty-auth.socket +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=whawty.auth authentication agent sockets - -[Socket] -#ListenStream=127.0.0.1:888 -ListenStream=/var/run/whawty/auth.sock -RemoveOnStop=true -SocketUser=whawty-auth -SocketGroup=whawty-auth -SocketMode=0660 - -[Install] -WantedBy=sockets.target diff --git a/contrib/systemd/whawty-auth_http.socket b/contrib/systemd/whawty-auth_http.socket new file mode 100644 index 0000000..b4994da --- /dev/null +++ b/contrib/systemd/whawty-auth_http.socket @@ -0,0 +1,10 @@ +[Unit] +Description=whawty.auth authentication agent http sockets + +[Socket] +Service=whawty-auth.service +FileDescriptorName=http +ListenStream=127.0.0.1:8080 + +[Install] +WantedBy=sockets.target diff --git a/contrib/systemd/whawty-auth_https.socket b/contrib/systemd/whawty-auth_https.socket new file mode 100644 index 0000000..4a1cbee --- /dev/null +++ b/contrib/systemd/whawty-auth_https.socket @@ -0,0 +1,11 @@ +[Unit] +Description=whawty.auth authentication agent https sockets + +[Socket] +Service=whawty-auth.service +FileDescriptorName=https +ListenStream=192.0.2.0:8443 +ListenStream=127.0.0.1:8443 + +[Install] +WantedBy=sockets.target diff --git a/contrib/systemd/whawty-auth_saslauthd.socket b/contrib/systemd/whawty-auth_saslauthd.socket new file mode 100644 index 0000000..e75dca7 --- /dev/null +++ b/contrib/systemd/whawty-auth_saslauthd.socket @@ -0,0 +1,15 @@ +[Unit] +Description=whawty.auth authentication agent saslauthd-compatible sockets + +[Socket] +Service=whawty-auth.service +FileDescriptorName=saslauthd +ListenStream=/run/whawty/auth.sock +ListenStream=/var/spool/postfix/run/whawty/auth.sock +RemoveOnStop=true +SocketUser=whawty-auth +SocketGroup=whawty-auth +SocketMode=0660 + +[Install] +WantedBy=sockets.target From d11a977579a4801567492fb16cce6776f0aa9a66 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Wed, 31 Jan 2024 20:04:04 +0100 Subject: [PATCH 4/5] add support for ldap bind requests --- cmd/whawty-auth/config.go | 21 ++++----- cmd/whawty-auth/ldap.go | 97 ++++++++++++++++++++++++++++++++++++++ cmd/whawty-auth/main.go | 60 +++++++++++++++++++++++ cmd/whawty-auth/web_api.go | 1 - contrib/listener-cfg.yml | 25 +++++----- go.mod | 2 + go.sum | 4 ++ 7 files changed, 187 insertions(+), 23 deletions(-) create mode 100644 cmd/whawty-auth/ldap.go diff --git a/cmd/whawty-auth/config.go b/cmd/whawty-auth/config.go index e94db10..95bc2c6 100644 --- a/cmd/whawty-auth/config.go +++ b/cmd/whawty-auth/config.go @@ -51,23 +51,22 @@ type httpsConfig struct { TLS *tlsconfig.TLSConfig `yaml:"tls"` } -// type ldapConfig struct { -// Listen []string `yaml:"listen"` -// StartTLS bool `yaml:"start-tls"` -// TLS *tlsconfig.TLSConfig `yaml:"tls"` -// } +type ldapConfig struct { + Listen []string `yaml:"listen"` + TLS *tlsconfig.TLSConfig `yaml:"tls"` +} -// type ldapsConfig struct { -// Listen []string `yaml:"listen"` -// TLS *tlsconfig.TLSConfig `yaml:"tls"` -// } +type ldapsConfig struct { + Listen []string `yaml:"listen"` + TLS *tlsconfig.TLSConfig `yaml:"tls"` +} type listenerConfig struct { SASLAuthd *saslauthdConfig `yaml:"saslauthd"` HTTP *httpConfig `yaml:"http"` HTTPs *httpsConfig `yaml:"https"` - // LDAP *ldapConfig `yaml:"ldap"` - // LDAPs *ldapsConfig `yaml:"ldaps"` + LDAP *ldapConfig `yaml:"ldap"` + LDAPs *ldapsConfig `yaml:"ldaps"` } func readListenerConfig(configfile string) (*listenerConfig, error) { diff --git a/cmd/whawty-auth/ldap.go b/cmd/whawty-auth/ldap.go new file mode 100644 index 0000000..4f3d5cf --- /dev/null +++ b/cmd/whawty-auth/ldap.go @@ -0,0 +1,97 @@ +// +// Copyright (c) 2016 whawty contributors (see AUTHORS file) +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * Neither the name of whawty.auth nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +package main + +import ( + "crypto/tls" + "net" + + "github.com/glauth/ldap" +) + +type ldapHandler struct { + store *Store +} + +func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) { + if ok, _, _, _ := h.store.Authenticate(bindDN, bindSimplePw); !ok { + return ldap.LDAPResultInvalidCredentials, nil + } + return ldap.LDAPResultSuccess, nil +} + +func runLDAPsListener(listener *net.TCPListener, config *ldapsConfig, store *Store) error { + server := ldap.NewServer() + server.BindFunc("", ldapHandler{store: store}) + + tlsConfig, err := config.TLS.ToGoTLSConfig() + if err != nil { + return err + } + wl.Printf("ldap: listening on '%s' using TLS", listener.Addr()) + return server.Serve(tls.NewListener(listener, tlsConfig)) +} + +func runLDAPsAddr(addr string, config *ldapsConfig, store *Store) error { + if addr == "" { + addr = ":ldaps" + } + listener, err := net.Listen("tcp", addr) + if err != nil { + return err + } + return runLDAPsListener(listener.(*net.TCPListener), config, store) +} + +func runLDAPListener(listener *net.TCPListener, config *ldapConfig, store *Store) (err error) { + server := ldap.NewServer() + server.BindFunc("", ldapHandler{store: store}) + if config.TLS != nil { + if server.TLSConfig, err = config.TLS.ToGoTLSConfig(); err != nil { + return err + } + wl.Printf("ldap: listening on '%s' with StartTLS", listener.Addr()) + } else { + wl.Printf("ldap: listening on '%s'", listener.Addr()) + } + return server.Serve(listener) +} + +func runLDAPAddr(addr string, config *ldapConfig, store *Store) error { + if addr == "" { + addr = ":ldap" + } + listener, err := net.Listen("tcp", addr) + if err != nil { + return err + } + return runLDAPListener(listener.(*net.TCPListener), config, store) +} diff --git a/cmd/whawty-auth/main.go b/cmd/whawty-auth/main.go index 68c6fae..a9a1119 100644 --- a/cmd/whawty-auth/main.go +++ b/cmd/whawty-auth/main.go @@ -410,6 +410,30 @@ func cmdRun(c *cli.Context) error { }() } } + if lc.LDAP != nil { + for _, addr := range lc.LDAP.Listen { + a := addr + wg.Add(1) + go func() { + defer wg.Done() + if err := runLDAPAddr(a, lc.LDAP, s.GetInterface()); err != nil { + fmt.Printf("warning running web-api failed: %s\n", err) + } + }() + } + } + if lc.LDAPs != nil { + for _, addr := range lc.LDAPs.Listen { + a := addr + wg.Add(1) + go func() { + defer wg.Done() + if err := runLDAPsAddr(a, lc.LDAPs, s.GetInterface()); err != nil { + fmt.Printf("warning running web-api failed: %s\n", err) + } + }() + } + } wg.Wait() return cli.NewExitError(fmt.Sprintf("shutting down since all auth sockets have closed."), 0) @@ -496,6 +520,42 @@ func cmdRunSa(c *cli.Context) error { } }() } + case "ldap": + if lc.LDAP == nil { + fmt.Printf("ingoring unexpected socket for LDAP listener (no config found in listener-config)\n") + continue + } + for _, listener := range listeners { + ln, ok := listener.(*net.TCPListener) + if !ok { + fmt.Printf("ingoring invalid socket type %T for LDAP listener\n", listener) + } + wg.Add(1) + go func() { + defer wg.Done() + if err := runLDAPListener(ln, lc.LDAP, s.GetInterface()); err != nil { + fmt.Printf("warning running web-api failed: %s\n", err) + } + }() + } + case "ldaps": + if lc.LDAPs == nil { + fmt.Printf("ingoring unexpected socket for LDAPs listener (no config found in listener-config)\n") + continue + } + for _, listener := range listeners { + ln, ok := listener.(*net.TCPListener) + if !ok { + fmt.Printf("ingoring invalid socket type %T for LDAPs listener\n", listener) + } + wg.Add(1) + go func() { + defer wg.Done() + if err := runLDAPsListener(ln, lc.LDAPs, s.GetInterface()); err != nil { + fmt.Printf("warning running web-api failed: %s\n", err) + } + }() + } } } diff --git a/cmd/whawty-auth/web_api.go b/cmd/whawty-auth/web_api.go index ead8a0c..b47448b 100644 --- a/cmd/whawty-auth/web_api.go +++ b/cmd/whawty-auth/web_api.go @@ -543,6 +543,5 @@ func runHTTPAddr(addr string, config *httpConfig, store *Store) error { if err != nil { return err } - return runHTTPListener(listener.(*net.TCPListener), config, store) } diff --git a/contrib/listener-cfg.yml b/contrib/listener-cfg.yml index 75d8889..03015e7 100644 --- a/contrib/listener-cfg.yml +++ b/contrib/listener-cfg.yml @@ -44,14 +44,17 @@ https: # - x25519 # session-tickets: true # session-ticket-key: "b947e39f50e20351bdd81046e20fff7948d359a3aec391719d60645c5972cc77" -# ldap: -# listen: -# - 127.0.0.1:389 -# start-tls: true -# ldaps: -# listen: -# - 127.0.0.1:636 -# tls: -# certificate: "/path/to/server-crt.pem" -# certificate-key: "/path/to/server-key.pem" -# min-protocol-version: "TLSv1.2" +ldap: + listen: + - 127.0.0.1:389 + tls: ## if set start-tls is enabled + certificate: "/path/to/server-crt.pem" + certificate-key: "/path/to/server-key.pem" + min-protocol-version: "TLSv1.2" +ldaps: + listen: + - 127.0.0.1:636 + tls: + certificate: "/path/to/server-crt.pem" + certificate-key: "/path/to/server-key.pem" + min-protocol-version: "TLSv1.2" diff --git a/go.mod b/go.mod index 92cb399..0edfe9d 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,8 @@ require ( github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/fatih/color v1.15.0 // indirect + github.com/glauth/ldap v0.0.0-20231210225823-b9bf4d1baf6e // indirect + github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/kr/pretty v0.2.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.18 // indirect diff --git a/go.sum b/go.sum index 4e2935b..52edc0a 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/glauth/ldap v0.0.0-20231210225823-b9bf4d1baf6e h1:ohe1O2DUo198delHsQvA8O+CgAPP8wv1SmSxLPlO9ao= +github.com/glauth/ldap v0.0.0-20231210225823-b9bf4d1baf6e/go.mod h1:EHMcFuVSIs0huelHOhaGtj9IKMB/Dmxo+jDheipjtYA= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= github.com/howeyc/gopass v0.0.0-20210920133722-c8aef6fb66ef h1:A9HsByNhogrvm9cWb28sjiS3i7tcKCkflWFEkHfuAgM= From 8d3e5e24ba408b8ec5ee641857b73ac5e7495335 Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Wed, 31 Jan 2024 20:33:42 +0100 Subject: [PATCH 5/5] add sample ldap systemd socket units --- contrib/systemd/whawty-auth_ldap.socket | 10 ++++++++++ contrib/systemd/whawty-auth_ldaps.socket | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 contrib/systemd/whawty-auth_ldap.socket create mode 100644 contrib/systemd/whawty-auth_ldaps.socket diff --git a/contrib/systemd/whawty-auth_ldap.socket b/contrib/systemd/whawty-auth_ldap.socket new file mode 100644 index 0000000..1427726 --- /dev/null +++ b/contrib/systemd/whawty-auth_ldap.socket @@ -0,0 +1,10 @@ +[Unit] +Description=whawty.auth authentication agent ldap sockets + +[Socket] +Service=whawty-auth.service +FileDescriptorName=ldap +ListenStream=127.0.0.1:8389 + +[Install] +WantedBy=sockets.target diff --git a/contrib/systemd/whawty-auth_ldaps.socket b/contrib/systemd/whawty-auth_ldaps.socket new file mode 100644 index 0000000..744edb8 --- /dev/null +++ b/contrib/systemd/whawty-auth_ldaps.socket @@ -0,0 +1,10 @@ +[Unit] +Description=whawty.auth authentication agent ldaps sockets + +[Socket] +Service=whawty-auth.service +FileDescriptorName=ldaps +ListenStream=127.0.0.1:8636 + +[Install] +WantedBy=sockets.target