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:
Jörn Friedrich Dreyer
2020-02-26 15:01:31 +01:00
committed by GitHub
parent a92f9a7028
commit 592fdf4e79
15 changed files with 574 additions and 126 deletions
+16 -2
View File
@@ -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
[![Stargazers over time](https://starcharts.herokuapp.com/glauth/glauth.svg)](https://starcharts.herokuapp.com/glauth/glauth)
+38 -18
View File
@@ -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")
}
+2 -2
View File
@@ -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
+8 -2
View File
@@ -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=
+50
View File
@@ -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
View File
@@ -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
}
+186
View File
@@ -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{}
+27
View File
@@ -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
View File
@@ -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 {
+1 -1
View File
@@ -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
View File
@@ -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
}
+59
View File
@@ -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
View File
@@ -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),
}
+50
View File
@@ -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
View File
@@ -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
}
}