mirror of
https://github.com/glauth/glauth.git
synced 2026-10-24 21:23:11 +00:00
use functional options pattern to inject logr (#124)
* use functional options pattern to inject logr Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de> * cleanup log formatting Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
committed by
GitHub
parent
a92f9a7028
commit
592fdf4e79
@@ -156,7 +156,7 @@ GLAuth can store a user's SSH authorized keys. Add one or more keys per user as
|
||||
### Two Factor Authentication
|
||||
GLAuth can be configured to accept OTP tokens as appended to a users password. Support is added for both **TOTP tokens** (often known by it's most prominent implementation, "Google Authenticator") and **Yubikey OTP tokens**.
|
||||
|
||||
When using 2FA, append the 2FA code to the end of the password when authenticating. For example, if your password is "monkey" and your otp is "123456", enter "monkey123456" as your password.
|
||||
When using 2FA, append the 2FA code to the end of the password when authenticating. For example, if your password is "monkey" and your otp is "123456", enter "monkey123456" as your password.
|
||||
|
||||
#### TOTP Configuration
|
||||
To enable TOTP authentication on a user, you can use a tool [like this](https://freeotp.github.io/qrcode.html) to generate a QR code (pick 'Timeout' and optionally let it generate a random secret for you), which can be scanned and used with the [Google Authenticator](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en) app. To enable TOTP authentication, configure the `otpsecret` for the user with the TOTP secret.
|
||||
@@ -194,7 +194,7 @@ Any of the architectures above will work for production. Just remember:
|
||||
A small note about other architectures: while I expect the code is, for the most part, system-independent, there is not a good (and free) CI system which can be easily used to continuously test releases on ARM, BSD, Linux-32bit, and Windows. As such, all of the non-linux-64bit packages are provided as is. The extent of testing on these packages consists solely of cross-compiling for these architectures from a linux 64 bit system.
|
||||
|
||||
We will accept PRs which fix bugs on these platforms, but be aware these binaries will not be tested regularly, and instead are provided for the convenience of those who feel comfortable with this.
|
||||
|
||||
|
||||
### Building:
|
||||
You'll need go-bindata to build GLAuth. Then use the Makefile.
|
||||
```unix
|
||||
@@ -202,6 +202,20 @@ go get github.com/jteeuwen/go-bindata/...
|
||||
make all
|
||||
```
|
||||
|
||||
# Logging
|
||||
- using logr with increasing verbosity
|
||||
- 0 you always want to see this
|
||||
- 1 common logging that you might *possibly* want to turn off (error)
|
||||
- 2 warn
|
||||
- 3 notice
|
||||
- 4 info
|
||||
- 6 debug
|
||||
- 8 trace
|
||||
- 10 I would like to performance test your log collection stack
|
||||
- errors really are errors that cannot be handled or returned
|
||||
- returning a proper ldep error code is handling an error
|
||||
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
[](https://starcharts.herokuapp.com/glauth/glauth)
|
||||
|
||||
@@ -11,8 +11,10 @@ import (
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/frontend"
|
||||
gologgingr "github.com/glauth/glauth/pkg/gologgingr"
|
||||
"github.com/glauth/glauth/pkg/server"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/jinzhu/copier"
|
||||
logging "github.com/op/go-logging"
|
||||
"gopkg.in/amz.v1/aws"
|
||||
@@ -46,7 +48,7 @@ Options:
|
||||
`
|
||||
|
||||
var (
|
||||
log *logging.Logger
|
||||
log logr.Logger
|
||||
args map[string]interface{}
|
||||
stderr *logging.LogBackend
|
||||
yubiAuth *yubigo.YubiAuth
|
||||
@@ -98,13 +100,15 @@ func getVersionString() string {
|
||||
|
||||
func main() {
|
||||
stderr = initLogging()
|
||||
log.Debug("AP start")
|
||||
log.V(6).Info("AP start")
|
||||
|
||||
if err := parseArgs(); err != nil {
|
||||
log.Fatalf("Could not parse command-line arguments", err.Error())
|
||||
log.Error(err, "Could not parse command-line arguments")
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := doConfig(); err != nil {
|
||||
log.Fatalf("Configuration file error: %s", err.Error())
|
||||
log.Error(err, "Configuration file error")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
startService()
|
||||
@@ -116,19 +120,27 @@ func startService() {
|
||||
|
||||
// web API
|
||||
if activeConfig.API.Enabled {
|
||||
log.Debug("Web API enabled")
|
||||
go frontend.RunAPI(log, activeConfig)
|
||||
log.V(6).Info("Web API enabled")
|
||||
go frontend.RunAPI(
|
||||
frontend.Logger(log),
|
||||
frontend.Config(&activeConfig.API),
|
||||
)
|
||||
}
|
||||
|
||||
startConfigWatcher()
|
||||
|
||||
s, err := server.NewServer(log, activeConfig)
|
||||
s, err := server.NewServer(
|
||||
server.Logger(log),
|
||||
server.Config(activeConfig),
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start server: %s", err.Error())
|
||||
log.Error(err, "Could not start server")
|
||||
os.Exit(1)
|
||||
}
|
||||
s.ListenAndServe()
|
||||
|
||||
log.Critical("AP exit")
|
||||
log.V(0).Info("AP exit")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func startConfigWatcher() {
|
||||
@@ -140,7 +152,7 @@ func startConfigWatcher() {
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatalf("Could not start config-watcher: %s", err.Error())
|
||||
log.Error(err, "Could not start config-watcher")
|
||||
}
|
||||
|
||||
go func() {
|
||||
@@ -150,15 +162,15 @@ func startConfigWatcher() {
|
||||
if activeConfig.WatchConfig {
|
||||
if event.Op.String() == "WRITE" {
|
||||
if err := doConfig(); err != nil {
|
||||
log.Warningf("Could not reload config: %sHolding on to old config", err.Error())
|
||||
log.V(2).Info("Could not reload config.Holding on to old config", "error", err.Error())
|
||||
} else {
|
||||
log.Notice("Config was reloaded")
|
||||
log.V(3).Info("Config was reloaded")
|
||||
}
|
||||
}
|
||||
}
|
||||
case err := <-watcher.Errors:
|
||||
if activeConfig.WatchConfig {
|
||||
log.Info("Error!", err)
|
||||
log.Error(err, "Error!")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,7 +249,7 @@ func handleConfig(cfg config.Config) (*config.Config, error) {
|
||||
|
||||
if len(cfg.Frontend.Listen) > 0 {
|
||||
// We're going with old format - parse it into new
|
||||
log.Warning("Config [frontend] is deprecated - please move to [ldap] and [ldaps] as-per documentation")
|
||||
log.V(2).Info("Config [frontend] is deprecated - please move to [ldap] and [ldaps] as-per documentation")
|
||||
|
||||
cfg.LDAP.Enabled = !cfg.Frontend.TLS
|
||||
cfg.LDAPS.Enabled = cfg.Frontend.TLS
|
||||
@@ -328,7 +340,7 @@ func doConfig() error {
|
||||
|
||||
if activeConfig.Debug {
|
||||
logging.SetLevel(logging.DEBUG, programName)
|
||||
log.Debug("Debugging enabled")
|
||||
log.V(6).Info("Debugging enabled")
|
||||
}
|
||||
if activeConfig.Syslog {
|
||||
enableSyslog(stderr)
|
||||
@@ -339,7 +351,14 @@ func doConfig() error {
|
||||
|
||||
// initLogging sets up logging to stderr
|
||||
func initLogging() *logging.LogBackend {
|
||||
log = logging.MustGetLogger(programName)
|
||||
|
||||
l := logging.MustGetLogger(programName)
|
||||
l.ExtraCalldepth = 2 // add extra call depth for the logr wrapper
|
||||
|
||||
log = gologgingr.New(
|
||||
gologgingr.Logger(l),
|
||||
)
|
||||
gologgingr.SetVerbosity(10) // do not filter by verbosity. glauth uses the go-logging lib to filter the levels
|
||||
|
||||
format := "%{color}%{time:15:04:05.000000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}"
|
||||
logBackend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
@@ -356,10 +375,11 @@ func enableSyslog(stderrBackend *logging.LogBackend) {
|
||||
logging.SetFormatter(logging.MustStringFormatter(format))
|
||||
syslogBackend, err := logging.NewSyslogBackend("")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
log.Error(err, "Could not create new syslog backend")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
logging.SetBackend(stderrBackend, syslogBackend)
|
||||
|
||||
log.Debug("Syslog enabled")
|
||||
log.V(6).Info("Syslog enabled")
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ require (
|
||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-logr/logr v0.1.0
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect
|
||||
github.com/kr/pretty v0.2.0
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
|
||||
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e h1:Bqtt5C+uVk+vH/t5dmB47uDCTwxw16EYHqvJnmY2aQc=
|
||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e/go.mod h1:njRCDrl+1RQ/A/+KVU8Ho2EWAxUSkohOWczdW3dzDG0=
|
||||
@@ -8,16 +9,18 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
|
||||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
|
||||
github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts=
|
||||
github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 h1:D9EvfGQvlkKaDr2CRKN++7HbSXbefUNDrPq60T+g24s=
|
||||
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484/go.mod h1:O1EljZ+oHprtxDDPHiMWVo/5dBT6PlvWX5PSwj80aBA=
|
||||
@@ -25,15 +28,18 @@ github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3 h1:NNis9uuNpG5h97Dvx
|
||||
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3/go.mod h1:YtrVB1/v9Td9SyjXpjYVmbdKgj9B0nPTBsdGUxy0i8U=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
|
||||
github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc h1:ejaC8rvIvCWmsaFrvmGOxhBuMxxhBB1xRshuM98XQ7M=
|
||||
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc/go.mod h1:tso14hwzqX4VbnWTNsxiL0DvMb2OwbGISFA7jDibdWc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c h1:jceGD5YNJGgGMkJz79agzOln1K9TaZUjv5ird16qniQ=
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger logr.Logger
|
||||
Config *config.API
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val logr.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the config option.
|
||||
func Config(val *config.API) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
|
||||
// Context provides a function to set the context option.
|
||||
func Context(val context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = val
|
||||
}
|
||||
}
|
||||
+21
-15
@@ -8,48 +8,54 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/glauth/glauth/pkg/assets"
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
logging "github.com/op/go-logging"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// RunAPI provides a basic REST API
|
||||
func RunAPI(log *logging.Logger, cfg *config.Config) {
|
||||
func RunAPI(opts ...Option) {
|
||||
options := newOptions(opts...)
|
||||
log := options.Logger
|
||||
cfg := options.Config
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Debug(fmt.Sprintf("Web: %s", r.URL.Path))
|
||||
log.V(6).Info("Web", "path", r.URL.Path)
|
||||
if r.URL.Path != "/" {
|
||||
log.Debug("Web 404")
|
||||
log.V(6).Info("Web 404")
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
pageTemplate, err := assets.Asset("assets/index.html")
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Sprintf("Error with HTTP server template asset: %s", err.Error()))
|
||||
log.Error(err, "Error with HTTP server template asset")
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, string(pageTemplate))
|
||||
})
|
||||
http.HandleFunc("/assets/", webStaticHandler(log))
|
||||
if cfg.API.TLS {
|
||||
log.Notice(fmt.Sprintf("Starting HTTPS server on %s", cfg.API.Listen))
|
||||
err := http.ListenAndServeTLS(cfg.API.Listen, cfg.API.Cert, cfg.API.Key, nil)
|
||||
if cfg.TLS {
|
||||
log.V(3).Info("Starting HTTPS server", "address", cfg.Listen)
|
||||
err := http.ListenAndServeTLS(cfg.Listen, cfg.Cert, cfg.Key, nil)
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Sprintf("Error starting HTTPS server: %s", err.Error()))
|
||||
log.Error(err, "Error starting HTTPS server")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.Notice(fmt.Sprintf("Starting HTTP server on %s", cfg.API.Listen))
|
||||
err := http.ListenAndServe(cfg.API.Listen, nil)
|
||||
log.V(3).Info("Starting HTTP server", "address", cfg.Listen)
|
||||
err := http.ListenAndServe(cfg.Listen, nil)
|
||||
if err != nil {
|
||||
log.Fatal(fmt.Sprintf("Error starting HTTP server: %s", err.Error()))
|
||||
log.Error(err, "Error starting HTTP server")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// webStaticHandler serves embedded static web files (js&css)
|
||||
func webStaticHandler(log *logging.Logger) func(http.ResponseWriter, *http.Request) {
|
||||
func webStaticHandler(log logr.Logger) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
assetPath := r.URL.Path[1:]
|
||||
staticAsset, err := assets.Asset(assetPath)
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
log.Error(err, "Cannot access asset")
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
// Package gologgingr implements github.com/go-logr/logr.Logger in terms of
|
||||
// github.com/op/go-logging.
|
||||
package gologgingr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// The global verbosity level. See SetVerbosity().
|
||||
var globalVerbosity int = 0
|
||||
|
||||
// SetVerbosity sets the global level against which all info logs will be
|
||||
// compared. If this is greater than or equal to the "V" of the logger, the
|
||||
// message will be logged. A higher value here means more logs will be written.
|
||||
// The previous verbosity value is returned. This is not concurrent-safe -
|
||||
// callers must be sure to call it from only one goroutine.
|
||||
func SetVerbosity(v int) int {
|
||||
old := globalVerbosity
|
||||
globalVerbosity = v
|
||||
return old
|
||||
}
|
||||
|
||||
// New returns a logr.Logger which is implemented by Go's standard
|
||||
// log package, or something like it. See New for details.
|
||||
func New(opts ...Option) logr.Logger {
|
||||
options := newOptions(opts...)
|
||||
|
||||
l := logger{
|
||||
llog: options.Logger,
|
||||
level: 0,
|
||||
prefix: "",
|
||||
values: nil,
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// LeveledLogger is the subset of the Go stdlib log.Logger API that is needed for
|
||||
// this adapter.
|
||||
type LeveledLogger interface {
|
||||
Fatal(args ...interface{})
|
||||
Error(args ...interface{})
|
||||
Warning(args ...interface{})
|
||||
Notice(args ...interface{})
|
||||
Info(args ...interface{})
|
||||
Debug(args ...interface{})
|
||||
}
|
||||
|
||||
type logger struct {
|
||||
llog LeveledLogger
|
||||
level int
|
||||
prefix string
|
||||
values []interface{}
|
||||
depth int
|
||||
}
|
||||
|
||||
func (l logger) clone() logger {
|
||||
out := l
|
||||
l.values = copySlice(l.values)
|
||||
return out
|
||||
}
|
||||
|
||||
func copySlice(in []interface{}) []interface{} {
|
||||
out := make([]interface{}, len(in))
|
||||
copy(out, in)
|
||||
return out
|
||||
}
|
||||
|
||||
// Magic string for intermediate frames that we should ignore.
|
||||
const autogeneratedFrameName = "<autogenerated>"
|
||||
|
||||
type kvPair struct {
|
||||
key string
|
||||
val interface{}
|
||||
}
|
||||
|
||||
func flatten(kvList ...interface{}) string {
|
||||
keys := make([]string, 0, len(kvList))
|
||||
vals := make(map[string]interface{}, len(kvList))
|
||||
for i := 0; i < len(kvList); i += 2 {
|
||||
k, ok := kvList[i].(string)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("key is not a string: %s", pretty(kvList[i])))
|
||||
}
|
||||
var v interface{}
|
||||
if i+1 < len(kvList) {
|
||||
v = kvList[i+1]
|
||||
}
|
||||
keys = append(keys, k)
|
||||
vals[k] = v
|
||||
}
|
||||
sort.Strings(keys)
|
||||
buf := bytes.Buffer{}
|
||||
for i, k := range keys {
|
||||
v := vals[k]
|
||||
if i > 0 {
|
||||
buf.WriteRune(' ')
|
||||
}
|
||||
buf.WriteString(pretty(k))
|
||||
buf.WriteString("=")
|
||||
buf.WriteString(pretty(v))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func pretty(value interface{}) string {
|
||||
jb, _ := json.Marshal(value)
|
||||
return string(jb)
|
||||
}
|
||||
|
||||
func (l logger) Info(msg string, kvList ...interface{}) {
|
||||
if l.Enabled() {
|
||||
lvlStr := flatten("level", l.level)
|
||||
msgStr := flatten("msg", msg)
|
||||
fixedStr := flatten(l.values...)
|
||||
userStr := flatten(kvList...)
|
||||
l.output(fmt.Sprintln(l.prefix, lvlStr, msgStr, fixedStr, userStr))
|
||||
}
|
||||
}
|
||||
|
||||
func (l logger) Enabled() bool {
|
||||
return globalVerbosity >= l.level
|
||||
}
|
||||
|
||||
func (l logger) Error(err error, msg string, kvList ...interface{}) {
|
||||
msgStr := flatten("msg", msg)
|
||||
var loggableErr interface{}
|
||||
if err != nil {
|
||||
loggableErr = err.Error()
|
||||
}
|
||||
errStr := flatten("error", loggableErr)
|
||||
fixedStr := flatten(l.values...)
|
||||
userStr := flatten(kvList...)
|
||||
l.output(fmt.Sprintln(l.prefix, errStr, msgStr, fixedStr, userStr))
|
||||
}
|
||||
|
||||
func (l logger) output(s string) {
|
||||
switch l.level {
|
||||
case 0:
|
||||
l.llog.Error(s)
|
||||
case 1:
|
||||
l.llog.Error(s)
|
||||
case 2:
|
||||
l.llog.Warning(s)
|
||||
case 3:
|
||||
l.llog.Notice(s)
|
||||
case 4:
|
||||
l.llog.Info(s)
|
||||
case 5:
|
||||
l.llog.Info(s)
|
||||
default:
|
||||
l.llog.Debug(s)
|
||||
}
|
||||
}
|
||||
|
||||
func (l logger) V(level int) logr.InfoLogger {
|
||||
new := l.clone()
|
||||
new.level = level
|
||||
return new
|
||||
}
|
||||
|
||||
// WithName returns a new logr.Logger with the specified name appended. stdr
|
||||
// uses '/' characters to separate name elements. Callers should not pass '/'
|
||||
// in the provided name string, but this library does not actually enforce that.
|
||||
func (l logger) WithName(name string) logr.Logger {
|
||||
new := l.clone()
|
||||
if len(l.prefix) > 0 {
|
||||
new.prefix = l.prefix + "/"
|
||||
}
|
||||
new.prefix += name
|
||||
return new
|
||||
}
|
||||
|
||||
func (l logger) WithValues(kvList ...interface{}) logr.Logger {
|
||||
new := l.clone()
|
||||
new.values = append(new.values, kvList...)
|
||||
return new
|
||||
}
|
||||
|
||||
var _ logr.Logger = logger{}
|
||||
var _ logr.InfoLogger = logger{}
|
||||
@@ -0,0 +1,27 @@
|
||||
package gologgingr
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger LeveledLogger
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val LeveledLogger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
+27
-26
@@ -11,37 +11,41 @@ import (
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
"github.com/pquerna/otp/totp"
|
||||
)
|
||||
|
||||
type configHandler struct {
|
||||
log *logging.Logger
|
||||
log logr.Logger
|
||||
cfg *config.Config
|
||||
yubikeyAuth *yubigo.YubiAuth
|
||||
}
|
||||
|
||||
func NewConfigHandler(log *logging.Logger, cfg *config.Config, yubikeyAuth *yubigo.YubiAuth) Handler {
|
||||
// NewConfigHandler creates a new config backed handler
|
||||
func NewConfigHandler(opts ...Option) Handler {
|
||||
options := newOptions(opts...)
|
||||
|
||||
handler := configHandler{
|
||||
log: log,
|
||||
cfg: cfg,
|
||||
yubikeyAuth: yubikeyAuth}
|
||||
log: options.Logger,
|
||||
cfg: options.Config,
|
||||
yubikeyAuth: options.YubiAuth,
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
//
|
||||
// Bind implements a bind request against the config file
|
||||
func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
|
||||
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
|
||||
h.log.V(6).Info("Bind request", "binddn", bindDN, "basedn", h.cfg.Backend.BaseDN, "src", conn.RemoteAddr())
|
||||
|
||||
stats.Frontend.Add("bind_reqs", 1)
|
||||
|
||||
// parse the bindDN - ensure that the bindDN ends with the BaseDN
|
||||
if !strings.HasSuffix(bindDN, baseDN) {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
|
||||
h.log.V(2).Info("BindDN not part of our BaseDN", "binddn", bindDN, "basedn", h.cfg.Backend.BaseDN)
|
||||
// h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, baseDN))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
@@ -54,7 +58,7 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
|
||||
userName = strings.TrimPrefix(parts[0], h.cfg.Backend.NameFormat+"=")
|
||||
groupName = strings.TrimPrefix(parts[1], h.cfg.Backend.GroupFormat+"=")
|
||||
} else {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
|
||||
h.log.V(2).Info("BindDN should have only one or two parts", "binddn", bindDN, "numparts", len(parts))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
@@ -68,7 +72,7 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: User %s not found.", userName))
|
||||
h.log.V(2).Info(fmt.Sprintf("User not found", "username", userName))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
// find the group
|
||||
@@ -81,12 +85,12 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: Group %s not found.", groupName))
|
||||
h.log.V(2).Info("Group not found", "groupname", groupName)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
// validate group membership
|
||||
if user.PrimaryGroup != group.UnixID {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: User %s primary group is not %s.", userName, groupName))
|
||||
h.log.V(2).Info("primary group mismatch", "username", userName, "primarygroup", user.PrimaryGroup, "groupid", group.UnixID)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
@@ -134,10 +138,10 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
|
||||
for index, appPw := range user.PassAppSHA256 {
|
||||
|
||||
if appPw != hex.EncodeToString(hashFull.Sum(nil)) {
|
||||
h.log.Debug(fmt.Sprintf("Attempted to bind app pw #%d - failure as %s from %s", index, bindDN, conn.RemoteAddr().String()))
|
||||
h.log.V(6).Info("Attempt to bind app pw failed", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
|
||||
} else {
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.log.Debug("Bind success using app pw #%d as %s from %s", index, bindDN, conn.RemoteAddr().String())
|
||||
h.log.V(6).Info("Bind success using app pw", "index", index, "binddn", bindDN, "src", conn.RemoteAddr())
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
@@ -149,27 +153,27 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
|
||||
|
||||
// Then ensure the OTP is valid before checking
|
||||
if !validotp {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
h.log.V(2).Info("invalid OTP token", "binddn", bindDN, "src", conn.RemoteAddr())
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
// Now, check the hash
|
||||
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: invalid credentials as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
h.log.V(2).Info("invalid credentials", "binddn", bindDN, "src", conn.RemoteAddr())
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
h.log.V(6).Info("Bind success", "binddn", bindDN, "src", conn.RemoteAddr())
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Search implements a search request against the config file
|
||||
func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter))
|
||||
h.log.V(6).Info("Search request", "binddn", bindDN, "src", conn.RemoteAddr(), "filter", searchReq.Filter)
|
||||
stats.Frontend.Add("search_reqs", 1)
|
||||
|
||||
// validate the user is authenticated and has appropriate access
|
||||
@@ -266,17 +270,16 @@ func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn
|
||||
}
|
||||
}
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
|
||||
h.log.V(6).Info("AP: Search OK", "filter", searchReq.Filter)
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Close does not actually close anything, because the config data is kept in memory
|
||||
func (h configHandler) Close(boundDn string, conn net.Conn) error {
|
||||
stats.Frontend.Add("closes", 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupMembers(gid int) []string {
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
@@ -317,7 +320,6 @@ func (h configHandler) getGroupMembers(gid int) []string {
|
||||
return m
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
members := make(map[string]bool)
|
||||
for _, u := range h.cfg.Users {
|
||||
@@ -336,7 +338,7 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
|
||||
if gid == g.UnixID {
|
||||
for _, includegroupid := range g.IncludeGroups {
|
||||
if includegroupid == gid {
|
||||
h.log.Warning(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
|
||||
h.log.V(2).Info("Ignoring myself as included group", "groupid", includegroupid)
|
||||
} else {
|
||||
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
|
||||
|
||||
@@ -390,7 +392,6 @@ func (h configHandler) getGroupDNs(gids []int) []string {
|
||||
return g
|
||||
}
|
||||
|
||||
//
|
||||
func (h configHandler) getGroupName(gid int) string {
|
||||
for _, g := range h.cfg.Groups {
|
||||
if g.UnixID == gid {
|
||||
|
||||
@@ -2,7 +2,7 @@ package handler
|
||||
|
||||
import "github.com/nmcclain/ldap"
|
||||
|
||||
// interface for backend handler
|
||||
// Handler is the common interface for all datastores
|
||||
type Handler interface {
|
||||
ldap.Binder
|
||||
ldap.Searcher
|
||||
|
||||
+38
-27
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -14,14 +15,13 @@ import (
|
||||
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/kr/pretty"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
type ldapHandler struct {
|
||||
doPing chan bool
|
||||
log *logging.Logger
|
||||
log logr.Logger
|
||||
cfg *config.Config
|
||||
lock *sync.Mutex // for sessions and servers
|
||||
sessions map[string]ldapSession
|
||||
@@ -51,19 +51,23 @@ type ldapBackend struct {
|
||||
Ping time.Duration
|
||||
}
|
||||
|
||||
func NewLdapHandler(log *logging.Logger, cfg *config.Config) Handler {
|
||||
func NewLdapHandler(opts ...Option) Handler {
|
||||
options := newOptions(opts...)
|
||||
|
||||
handler := ldapHandler{ // set non-zero-value defaults here
|
||||
sessions: make(map[string]ldapSession),
|
||||
doPing: make(chan bool),
|
||||
log: log,
|
||||
cfg: cfg,
|
||||
log: options.Logger,
|
||||
cfg: options.Config,
|
||||
lock: &ldaplock,
|
||||
}
|
||||
// parse LDAP URLs
|
||||
for _, ldapurl := range cfg.Backend.Servers {
|
||||
for _, ldapurl := range handler.cfg.Backend.Servers {
|
||||
l, err := parseURL(ldapurl)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
handler.log.Error(err, "could not parse url")
|
||||
os.Exit(1)
|
||||
// TODO log error and os exit
|
||||
}
|
||||
handler.servers = append(handler.servers, l)
|
||||
}
|
||||
@@ -76,27 +80,28 @@ func NewLdapHandler(log *logging.Logger, cfg *config.Config) Handler {
|
||||
|
||||
//
|
||||
func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
|
||||
h.log.Debug(fmt.Sprintf("Bind request as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
h.log.V(6).Info("Bind request", "binddn", bindDN, "src", conn.RemoteAddr())
|
||||
|
||||
stats.Frontend.Add("bind_reqs", 1)
|
||||
s, err := h.getSession(conn)
|
||||
if err != nil {
|
||||
stats.Frontend.Add("bind_ldapSession_errors", 1)
|
||||
h.log.Debug(fmt.Sprintf("Bind ops error as %s from %s == %s", bindDN, conn.RemoteAddr().String(), err.Error()))
|
||||
h.log.V(6).Info("could not get session", "binddn", bindDN, "src", conn.RemoteAddr(), "error", err)
|
||||
return ldap.LDAPResultOperationsError, err
|
||||
}
|
||||
if err := s.ldap.Bind(bindDN, bindSimplePw); err != nil {
|
||||
stats.Frontend.Add("bind_errors", 1)
|
||||
h.log.Debug(fmt.Sprintf("Bind invalid creds as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
h.log.V(6).Info("invalid creds", "binddn", bindDN, "src", conn.RemoteAddr())
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
h.log.V(6).Info("bind success", "binddn", bindDN, "src", conn.RemoteAddr())
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
//
|
||||
func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
|
||||
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s", boundDN, conn.RemoteAddr().String(), searchReq.Filter))
|
||||
h.log.V(6).Info("Search request", "binddn", boundDN, "src", conn.RemoteAddr(), "filter", searchReq.Filter)
|
||||
stats.Frontend.Add("search_reqs", 1)
|
||||
s, err := h.getSession(conn)
|
||||
if err != nil {
|
||||
@@ -115,24 +120,24 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
|
||||
searchReq.Controls,
|
||||
)
|
||||
|
||||
h.log.Debug(fmt.Sprintf("Search req to backend: %# v", pretty.Formatter(search)))
|
||||
h.log.V(6).Info("Search request to backend", "request", search)
|
||||
sr, err := s.ldap.Search(search)
|
||||
h.log.Debug(fmt.Sprintf("Backend Search result: %# v", pretty.Formatter(sr)))
|
||||
h.log.V(6).Info("Backend Search result", "result", sr)
|
||||
ssr := ldap.ServerSearchResult{
|
||||
Entries: sr.Entries,
|
||||
Referrals: sr.Referrals,
|
||||
Controls: sr.Controls,
|
||||
}
|
||||
h.log.Debug(fmt.Sprintf("Frontend Search result: %# v", pretty.Formatter(ssr)))
|
||||
h.log.V(6).Info("Frontend Search result", "result", ssr)
|
||||
if err != nil {
|
||||
e := err.(*ldap.Error)
|
||||
h.log.Debug(fmt.Sprintf("Search Err: %# v", pretty.Formatter(err)))
|
||||
h.log.V(6).Info("Search Err", "error", err)
|
||||
stats.Frontend.Add("search_errors", 1)
|
||||
ssr.ResultCode = ldap.LDAPResultCode(e.ResultCode)
|
||||
return ssr, err
|
||||
}
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.log.Debug(fmt.Sprintf("AP: Search OK: %s -> num of entries = %d\n", search.Filter, len(ssr.Entries)))
|
||||
h.log.V(6).Info("AP: Search OK", "filter", search.Filter, "numentries", len(ssr.Entries))
|
||||
return ssr, nil
|
||||
}
|
||||
func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
|
||||
@@ -149,22 +154,28 @@ func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
|
||||
func (h *ldapHandler) monitorServers() {
|
||||
err := h.ping()
|
||||
if err != nil {
|
||||
h.log.Fatal(err)
|
||||
h.log.Error(err, "could not ping server")
|
||||
os.Exit(1)
|
||||
// TODO return error
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-h.doPing:
|
||||
h.log.Notice("doPing requested due to server failure")
|
||||
h.log.V(3).Info("doPing requested due to server failure")
|
||||
err = h.ping()
|
||||
if err != nil {
|
||||
h.log.Fatal(err)
|
||||
h.log.Error(err, "could not ping server")
|
||||
os.Exit(1)
|
||||
// TODO return error
|
||||
}
|
||||
case <-time.NewTimer(60 * time.Second).C:
|
||||
h.log.Debug("doPing after timeout")
|
||||
h.log.V(6).Info("doPing after timeout")
|
||||
err = h.ping()
|
||||
if err != nil {
|
||||
h.log.Fatal(err)
|
||||
h.log.Error(err, "could not ping server")
|
||||
os.Exit(1)
|
||||
// TODO return error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,7 +239,7 @@ func (h ldapHandler) ping() error {
|
||||
elapsed := time.Since(start)
|
||||
h.lock.Lock()
|
||||
if err != nil || l == nil {
|
||||
h.log.Error(fmt.Sprintf("Server %s:%d ping failed: %s", s.Hostname, s.Port, err.Error()))
|
||||
h.log.V(1).Info("Server ping failed", "hostname", s.Hostname, "port", s.Port, "error", err)
|
||||
h.servers[k].Ping = 0
|
||||
h.servers[k].Status = Down
|
||||
} else {
|
||||
@@ -239,10 +250,10 @@ func (h ldapHandler) ping() error {
|
||||
}
|
||||
h.lock.Unlock()
|
||||
}
|
||||
h.log.Debug(fmt.Sprintf("Server health: %# v", pretty.Formatter(h.servers)))
|
||||
h.log.V(6).Info("Server health", "servers", h.servers)
|
||||
b, err := json.Marshal(h.servers)
|
||||
if err != nil {
|
||||
h.log.Error(fmt.Sprintf("Error encoding tail data: %s", err.Error()))
|
||||
h.log.V(1).Info("Error encoding tail data", "error", err)
|
||||
}
|
||||
stats.Backend.Set("servers", stats.Stringer(string(b)))
|
||||
if healthy == false {
|
||||
@@ -268,7 +279,7 @@ func (h ldapHandler) getBestServer() (ldapBackend, error) {
|
||||
if bestping == forever {
|
||||
return ldapBackend{}, fmt.Errorf("No healthy servers found")
|
||||
}
|
||||
h.log.Debug(fmt.Sprintf("Best server: %# v", pretty.Formatter(favorite)))
|
||||
h.log.V(6).Info("Best server", "favorite", favorite)
|
||||
return favorite, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/GeertJohan/yubigo"
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger logr.Logger
|
||||
Config *config.Config
|
||||
Context *context.Context
|
||||
YubiAuth *yubigo.YubiAuth
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val logr.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the config option.
|
||||
func Config(val *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
|
||||
// Context provides a function to set the context option.
|
||||
func Context(val *context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = val
|
||||
}
|
||||
}
|
||||
|
||||
// YubiAuth provides a function to set the yubiauth option.
|
||||
func YubiAuth(val *yubigo.YubiAuth) Option {
|
||||
return func(o *Options) {
|
||||
o.YubiAuth = val
|
||||
}
|
||||
}
|
||||
+18
-16
@@ -13,20 +13,20 @@ import (
|
||||
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/nmcclain/ldap"
|
||||
"github.com/op/go-logging"
|
||||
msgraph "github.com/yaegashi/msgraph.go/v1.0"
|
||||
)
|
||||
|
||||
type ownCloudSession struct {
|
||||
log *logging.Logger
|
||||
log logr.Logger
|
||||
user string
|
||||
password string
|
||||
baseUrl string
|
||||
useGraphAPI bool
|
||||
}
|
||||
type ownCloudHandler struct {
|
||||
log *logging.Logger
|
||||
log logr.Logger
|
||||
cfg *config.Config
|
||||
meUrl string
|
||||
sessions map[string]ownCloudSession
|
||||
@@ -37,25 +37,25 @@ func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
|
||||
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
|
||||
h.log.V(6).Info("Bind request", "binddn", bindDN, "basedn", h.cfg.Backend.BaseDN, "src", conn.RemoteAddr())
|
||||
|
||||
stats.Frontend.Add("bind_reqs", 1)
|
||||
|
||||
// parse the bindDN - ensure that the bindDN ends with the BaseDN
|
||||
if !strings.HasSuffix(bindDN, baseDN) {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
|
||||
h.log.V(2).Info("BindDN not part of our BaseDN", "binddn", bindDN, "basedn", h.cfg.Backend.BaseDN)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
parts := strings.Split(strings.TrimSuffix(bindDN, baseDN), ",")
|
||||
if len(parts) > 2 {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
|
||||
h.log.V(2).Info("BindDN should have only one or two parts", "binddn", bindDN, "numparts", len(parts))
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
userName := strings.TrimPrefix(parts[0], "cn=")
|
||||
|
||||
// try to login
|
||||
if !h.login(userName, bindSimplePw) {
|
||||
h.log.Warning(fmt.Sprintf("Bind Error: User %s login failed", userName))
|
||||
h.log.V(2).Info("Login failed", "username", userName, "basedn", h.cfg.Backend.BaseDN)
|
||||
return ldap.LDAPResultInvalidCredentials, nil
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.
|
||||
h.lock.Unlock()
|
||||
|
||||
stats.Frontend.Add("bind_successes", 1)
|
||||
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
|
||||
h.log.V(6).Info("Login failed", "binddn", bindDN, "basedn", h.cfg.Backend.BaseDN, "src", conn.RemoteAddr())
|
||||
return ldap.LDAPResultSuccess, nil
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
|
||||
bindDN = strings.ToLower(bindDN)
|
||||
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
|
||||
searchBaseDN := strings.ToLower(searchReq.BaseDN)
|
||||
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s on %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter, searchBaseDN))
|
||||
h.log.V(6).Info("Search request", "binddn", bindDN, "src", conn.RemoteAddr(), "filter", searchReq.Filter)
|
||||
stats.Frontend.Add("search_reqs", 1)
|
||||
|
||||
// validate the user is authenticated and has appropriate access
|
||||
@@ -138,7 +138,7 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
|
||||
}
|
||||
users, err := session.getUsers(userName)
|
||||
if err != nil {
|
||||
h.log.Debug(err)
|
||||
h.log.V(6).Info("Could not get user", "username", userName, "err", err)
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("search error: error getting users")
|
||||
}
|
||||
for _, u := range users {
|
||||
@@ -160,7 +160,7 @@ func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
|
||||
}
|
||||
}
|
||||
stats.Frontend.Add("search_successes", 1)
|
||||
h.log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
|
||||
h.log.V(6).Info("AP: Search OK", "filter", searchReq.Filter)
|
||||
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
|
||||
}
|
||||
|
||||
@@ -301,17 +301,19 @@ func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
|
||||
}
|
||||
|
||||
func (s ownCloudSession) redirectPolicyFunc(req *http.Request, via []*http.Request) error {
|
||||
s.log.Debug("Setting user and password")
|
||||
s.log.V(6).Info("Setting user and password", "username", s.user)
|
||||
req.SetBasicAuth(s.user, s.password)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewOwnCloudHandler(log *logging.Logger, cfg *config.Config) Handler {
|
||||
meUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/user?format=json", cfg.Backend.Servers[0])
|
||||
func NewOwnCloudHandler(opts ...Option) Handler {
|
||||
options := newOptions(opts...)
|
||||
|
||||
meUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/user?format=json", options.Config.Backend.Servers[0])
|
||||
|
||||
handler := ownCloudHandler{
|
||||
log: log,
|
||||
cfg: cfg,
|
||||
log: options.Logger,
|
||||
cfg: options.Config,
|
||||
meUrl: meUrl,
|
||||
sessions: make(map[string]ownCloudSession),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/glauth/glauth/pkg/config"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger logr.Logger
|
||||
Config *config.Config
|
||||
Context context.Context
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
func newOptions(opts ...Option) Options {
|
||||
opt := Options{}
|
||||
|
||||
for _, o := range opts {
|
||||
o(&opt)
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
// Logger provides a function to set the logger option.
|
||||
func Logger(val logr.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = val
|
||||
}
|
||||
}
|
||||
|
||||
// Config provides a function to set the config option.
|
||||
func Config(val *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = val
|
||||
}
|
||||
}
|
||||
|
||||
// Context provides a function to set the context option.
|
||||
func Context(val context.Context) Option {
|
||||
return func(o *Options) {
|
||||
o.Context = val
|
||||
}
|
||||
}
|
||||
+33
-17
@@ -3,32 +3,34 @@ package server
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
"github.com/op/go-logging"
|
||||
)
|
||||
|
||||
type LdapSvc struct {
|
||||
log *logging.Logger
|
||||
log logr.Logger
|
||||
c *config.Config
|
||||
yubiAuth *yubigo.YubiAuth
|
||||
l *ldap.Server
|
||||
}
|
||||
|
||||
func NewServer(log *logging.Logger, cfg *config.Config) (*LdapSvc, error) {
|
||||
func NewServer(opts ...Option) (*LdapSvc, error) {
|
||||
options := newOptions(opts...)
|
||||
|
||||
s := LdapSvc{
|
||||
log: log,
|
||||
c: cfg,
|
||||
log: options.Logger,
|
||||
c: options.Config,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
|
||||
s.yubiAuth, err = yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
|
||||
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")
|
||||
@@ -39,17 +41,27 @@ func NewServer(log *logging.Logger, cfg *config.Config) (*LdapSvc, error) {
|
||||
s.l = ldap.NewServer()
|
||||
s.l.EnforceLDAP = true
|
||||
var h handler.Handler
|
||||
switch cfg.Backend.Datastore {
|
||||
switch s.c.Backend.Datastore {
|
||||
case "ldap":
|
||||
h = handler.NewLdapHandler(log, cfg)
|
||||
h = handler.NewLdapHandler(
|
||||
handler.Logger(s.log),
|
||||
handler.Config(s.c),
|
||||
)
|
||||
case "owncloud":
|
||||
h = handler.NewOwnCloudHandler(log, cfg)
|
||||
h = handler.NewOwnCloudHandler(
|
||||
handler.Logger(s.log),
|
||||
handler.Config(s.c),
|
||||
)
|
||||
case "config":
|
||||
h = handler.NewConfigHandler(log, cfg, s.yubiAuth)
|
||||
h = handler.NewConfigHandler(
|
||||
handler.Logger(s.log),
|
||||
handler.Config(s.c),
|
||||
handler.YubiAuth(s.yubiAuth),
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported backend %s - must be 'config' or 'ldap'", cfg.Backend.Datastore)
|
||||
return nil, fmt.Errorf("unsupported backend %s - must be 'config' or 'ldap'", s.c.Backend.Datastore)
|
||||
}
|
||||
log.Notice(fmt.Sprintf("Using %s backend", cfg.Backend.Datastore))
|
||||
s.log.V(3).Info("Using backend", "datastore", s.c.Backend.Datastore)
|
||||
s.l.BindFunc("", h)
|
||||
s.l.SearchFunc("", h)
|
||||
s.l.CloseFunc("", h)
|
||||
@@ -77,15 +89,19 @@ func (s *LdapSvc) ListenAndServe() {
|
||||
}
|
||||
|
||||
func (s *LdapSvc) startLDAP() {
|
||||
s.log.Notice(fmt.Sprintf("LDAP server listening on %s", s.c.LDAP.Listen))
|
||||
s.log.V(3).Info("LDAP server listening", "address", s.c.LDAP.Listen)
|
||||
if err := s.l.ListenAndServe(s.c.LDAP.Listen); err != nil {
|
||||
s.log.Fatalf("LDAP Server Failed: %s", err.Error())
|
||||
s.log.Error(err, "LDAP Server Failed")
|
||||
os.Exit(1)
|
||||
// TODO return error
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LdapSvc) startLDAPS() {
|
||||
s.log.Notice(fmt.Sprintf("LDAPS server listening on %s", s.c.LDAPS.Listen))
|
||||
s.log.V(3).Info("LDAPS server listening", "address", s.c.LDAPS.Listen)
|
||||
if err := s.l.ListenAndServeTLS(s.c.LDAPS.Listen, s.c.LDAPS.Cert, s.c.LDAPS.Key); err != nil {
|
||||
s.log.Fatalf("LDAP Server Failed: %s", err.Error())
|
||||
s.log.Error(err, "LDAP Server Failed")
|
||||
os.Exit(1)
|
||||
// TODO return error
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user