Refactor packages (#105)

* Pulling in latest changes and fixes for  1.1.1 (#56)

* Adding a few test dockerfiles

* Continued work on docker builds

* Basic example of docker build and run works - see build.sh for now

* Continuing to work on docker build

* Removing testing from build script, since it belongs later in process

* Implementing dumb init

* Docker build fully switched to alpine

* Setting up alpine build

* Cleaning up dockerfile

* Cleaning up repo in prep for merge

* Updating permissions for clean merge

* Removing old ansible cruft, as per #12

* Removing binaries (since we now build)

* Updating sample config to use new fields now available

* Testing travis

* another travis test

* Still working on travis builds

* Fix missing amazon packages

* Merge in Travis config feature branch (#26)

* Adding version string

* Fixing and cleaning up docker build

* Adding image hashing and verification to 'make all'

* Testing build

* Running gofmt to fix build

* Testing switching travis to make

* Testing build again

* Merging in Fixes from travis-build feature branch (#33)

This includes an integration test which runs glauth and compares ldapsearch snapshot output stored in the repo compared to the snapshot output of the glauth.

Additionally, removing old versions of go, and adding windows and linuxar builds (but not tests) to the makefile, and consequently, the travis build.

While the mac, linux-arm, and windows binaries are not able to be run in travis, they are able to be at least compiled.

* Remove needless comment from integration test

* Adding 'is-process-running' check before integration tests run, to clearly note if the program crashes on run

* Switching from 1 sec to 2 sec timeout for integration test

* Adding back config file to fix build. Previous commit intended to see if build failed (and it did)

* Add support for including groups in groups (#23)

* Add support for including groups in groups
* Pulling in Makefile structure to allow easier testing and builds

* Forgot to remove spare test

* Add Version Info at Buildtime  (#39)

Adds the build info to the binary at buildtime via buildtime variables. This is shown by `./glauth --version`.

Example output for a non-release:
```
GLauth
Non-release build from branch feature/buildversion

Build time: 20180531_181011Z
Commit: 07ba631c2f
```

And example of a built release:
```
GLauth v2.3.4

Build time: 20180601_041330Z
Commit: 931d666262

```

* Fixing Broken Docker Build (#41)

Forgot to include go-bindata run in Dockerfile.

* Removing leftover TODO from merge

* Testing releases

* Testing releases

* Testing releases

* Adjusting logging

* Add new group tests to integration tests

* add cleanup to test process

* Add documentation about otherGroups, which was a previously undocumented field.

* Auto fetching bindata during build so it's not needed to be done manually

* Syntax fix

* starting framework for unit tests

* Merging in ongoing progress from Feature/travis build (#44)

Fixes a few minor issues:

#42 - now uses the makefile in the docker build, which means version info is correctly embedded at runtime - also now outputs version data at the top of the log when the container starts

Fixing a simple issue found by go vet. Need to do more work on fixing issues found in go vet.

* Forgot to run 'go fmt'

* Adding codecov for go now that a single test is written

* Testing not quite ready yet, removing from build

* Add Support For 2 Factor Authentication

Merging in feature from @ryskov (PR #24) adding 2FA support during LDAP binds. This is accomplished by concatenating the code to the end of the password. 

Also added integration tests for the TOTP method to run in CI. Could not, however, add automated tests for the yubikey, due the physical nature.

* Expose LDAPS ports in Docker container (#49)

Currently, the LDAPS ports are not exposed in the docker container.

* Adding better logging to docker start script

* Add more logging info to docker startup script

* Fix Arm32 Build (#52)

As discussed in issue #51, Arm32 builds were using 368 (intel/amd) arch accidentally, generating linux 32 bit binaries instead of arm 32 bit binaries. This commit fixes this in the Makefile, which will fix both the local builds, as well as the travis CI and release builds.

fixes #52

* Update docker hub image badge to a working one

previous one simply returned 0.

* Removing 3893 - fixes #50

* Fixing readme MD formatting

* Adding travis_retry to fix against intermittent network outages

* Add TLS options for running both with TLS and without on the same time (#27)

* Add TLS options for running both with TLS and without on the same time.

This commit expands on the settings available for using TLS. It puts TLS settings under the [frontend.tls] section and adds a new setting to [frontend] called TLSExclusive (bool).
TLSExclusive specifies whether or not to only run TLS when it is enabled, and is 'true' by default. Setting it to 'false' and having TLS enabled, causes the server to start both a LDAP and LDAPS server,
and therefore requires to seperate 'listen' options (to run on different ports) - the Frontend.Listen and the Frontend.TLS.Listen. If TLSExclusive is set to 'true' and no Frontend.TLS.Listen is specified, it will use the Frontend.Listen.

* Adding PR template and improving integration test tooling

* Updating formatting

* Add get dependencies step to makefile setup

* Add go 1.11

* Add App Password Support (#60)

App passwords can now be used to allow easier OTP use alongside applications which need to bind with a static password. Use the key `passappsha256` and specify an array of password hashes. See the readme and sample configuration file for more information.

Fixes #54

* Adding NCoC as official project code of conduct

We happily accept contributions based on the merit of the contributions.

* Properly handling paramaters in logs - fixes #64

* Adding ldapsearch for healthchecks

* fixed quoting in docker startup script (#77)

Thanks @cxcv cxcv

* Minor tweak of help text

* More durable Dockerfile (#92)

* refactor with latest dev

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

* fix modules, update .gitignore

* pass in logger

Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>

Co-authored-by: Ben Yanke <ben@benyanke.com>
Co-authored-by: Andrea Cervesato <andrea.cervesato@gmail.com>
This commit is contained in:
Jörn Friedrich Dreyer
2020-02-21 17:10:41 +01:00
committed by GitHub
parent 9b90466286
commit 558c7de15a
16 changed files with 465 additions and 293 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ certs/
# Built binaries
bin
bindata.go
pkg/assets/bindata.go
.*.swp
+1 -1
View File
@@ -24,7 +24,7 @@ RUN apk add --no-cache git bzr make
RUN go get -d -v ./...
# Run go-bindata to embed data for API
RUN go get -u github.com/jteeuwen/go-bindata/... && $GOPATH/bin/go-bindata -pkg=main assets && gofmt -w bindata.go
RUN go get -u github.com/jteeuwen/go-bindata/... && $GOPATH/bin/go-bindata -pkg=assets -o=pkg/assets/bindata.go assets && gofmt -w pkg/assets/bindata.go
# Build and copy final result
RUN make linux64 && cp ./bin/glauth64 /app/glauth
+3 -3
View File
@@ -15,7 +15,7 @@ GIT_BRANCH=$(shell git rev-parse --abbrev-ref HEAD)
# Build variables
BUILD_VARS=-X main.GitCommit=${GIT_COMMIT} -X main.GitBranch=${GIT_BRANCH} -X main.BuildTime=${BUILD_TIME} -X main.GitClean=${GIT_CLEAN} -X main.LastGitTag=${LAST_GIT_TAG} -X main.GitTagIsCommit=${GIT_IS_TAG_COMMIT}
BUILD_FILES=glauth.go bindata.go ldapbackend.go webapi.go configbackend.go owncloudbackend.go
BUILD_FILES=glauth.go
TRIM_FLAGS=-gcflags "all=-trimpath=${PWD}" -asmflags "all=-trimpath=${PWD}"
#####################
@@ -56,11 +56,11 @@ updatetest:
./scripts/travis/integration-test.sh
bindata:
go get -u github.com/jteeuwen/go-bindata/... && ${GOPATH}/bin/go-bindata -pkg=main assets && gofmt -w bindata.go
go get -u github.com/jteeuwen/go-bindata/... && ${GOPATH}/bin/go-bindata -pkg=assets -o=pkg/assets/bindata.go assets && gofmt -w pkg/assets/bindata.go
cleanup:
rm bindata.go
rm pkg/assets/bindata.go
format:
go fmt
+14 -144
View File
@@ -1,7 +1,6 @@
package main
import (
"expvar"
"fmt"
"os"
"strings"
@@ -10,8 +9,11 @@ import (
"github.com/GeertJohan/yubigo"
docopt "github.com/docopt/docopt-go"
"github.com/fsnotify/fsnotify"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/frontend"
"github.com/glauth/glauth/pkg/server"
"github.com/glauth/glauth/pkg/stats"
"github.com/jinzhu/copier"
"github.com/nmcclain/ldap"
logging "github.com/op/go-logging"
"gopkg.in/amz.v1/aws"
"gopkg.in/amz.v1/s3"
@@ -48,99 +50,10 @@ var (
args map[string]interface{}
stderr *logging.LogBackend
yubiAuth *yubigo.YubiAuth
// exposed expvar variables
// TODO: Should be renamed according to golang naming conventions for exported vars, StatsFrontend, StatsBackend, StatsGeneral
stats_frontend = expvar.NewMap("proxy_frontend")
stats_backend = expvar.NewMap("proxy_backend")
stats_general = expvar.NewMap("proxy")
activeConfig = &config{}
activeConfig = &config.Config{}
)
// interface for backend handler
type Backend interface {
ldap.Binder
ldap.Searcher
ldap.Closer
}
// config file
type configBackend struct {
BaseDN string
Datastore string
Insecure bool // For LDAP backend only
Servers []string // For LDAP and ownCloud backend only
NameFormat string
GroupFormat string
SSHKeyAttr string
UseGraphAPI bool // For ownCloud backend only
}
type configFrontend struct {
AllowedBaseDNs []string // For LDAP backend only
Listen string
Cert string
Key string
TLS bool
}
type configLDAP struct {
Enabled bool
Listen string
}
type configLDAPS struct {
Enabled bool
Listen string
Cert string
Key string
}
type configAPI struct {
Cert string
Enabled bool
Key string
Listen string
SecretToken string
TLS bool
}
type configUser struct {
Name string
OtherGroups []int
PassSHA256 string
PassAppSHA256 []string
PrimaryGroup int
SSHKeys []string
OTPSecret string
Yubikey string
Disabled bool
UnixID int
Mail string
LoginShell string
GivenName string
SN string
Homedir string
}
type configGroup struct {
Name string
UnixID int
IncludeGroups []int
}
type config struct {
API configAPI
Backend configBackend
Debug bool
WatchConfig bool
YubikeyClientID string
YubikeySecret string
Frontend configFrontend
LDAP configLDAP
LDAPS configLDAPS
Groups []configGroup
Syslog bool
Users []configUser
ConfigFile string
AwsAccessKeyId string
AwsSecretAccessKey string
AwsRegion string
}
// Reads builtime vars and returns a full string containing info about
// the currently running version of the software. Primarily used by the
// --version flag at runtime.
@@ -199,50 +112,21 @@ func main() {
func startService() {
// stats
stats_general.Set("version", stringer(LastGitTag))
stats.General.Set("version", stats.Stringer(LastGitTag))
// web API
if activeConfig.API.Enabled {
log.Debug("Web API enabled")
go RunAPI(activeConfig)
go frontend.RunAPI(log, activeConfig)
}
startConfigWatcher()
// configure the backend
s := ldap.NewServer()
s.EnforceLDAP = true
var handler Backend
switch activeConfig.Backend.Datastore {
case "ldap":
handler = newLdapHandler(activeConfig)
case "owncloud":
handler = newOwnCloudHandler(activeConfig)
case "config":
handler = newConfigHandler(activeConfig, yubiAuth)
default:
log.Fatalf("Unsupported backend %s - must be 'config' or 'ldap'.", activeConfig.Backend.Datastore)
}
log.Notice(fmt.Sprintf("Using %s backend", activeConfig.Backend.Datastore))
s.BindFunc("", handler)
s.SearchFunc("", handler)
s.CloseFunc("", handler)
if activeConfig.LDAP.Enabled {
// Dont block if also starting a LDAPS server afterwards
shouldBlock := !activeConfig.LDAPS.Enabled
if shouldBlock {
startLDAP(&activeConfig.LDAP, s)
} else {
go startLDAP(&activeConfig.LDAP, s)
}
}
if activeConfig.LDAPS.Enabled {
// Always block here
startLDAPS(&activeConfig.LDAPS, s)
s, err := server.NewServer(log, activeConfig)
if err != nil {
log.Fatalf("Could not start server: %s", err.Error())
}
s.ListenAndServe()
log.Critical("AP exit")
}
@@ -283,20 +167,6 @@ func startConfigWatcher() {
watcher.Add(configFileLocation)
}
func startLDAP(ldapConfig *configLDAP, server *ldap.Server) {
log.Notice(fmt.Sprintf("LDAP server listening on %s", ldapConfig.Listen))
if err := server.ListenAndServe(ldapConfig.Listen); err != nil {
log.Fatalf("LDAP Server Failed: %s", err.Error())
}
}
func startLDAPS(ldapsConfig *configLDAPS, server *ldap.Server) {
log.Notice(fmt.Sprintf("LDAPS server listening on %s", ldapsConfig.Listen))
if err := server.ListenAndServeTLS(ldapsConfig.Listen, ldapsConfig.Cert, ldapsConfig.Key); err != nil {
log.Fatalf("LDAP Server Failed: %s", err.Error())
}
}
func parseArgs() error {
var err error
@@ -311,8 +181,8 @@ func getConfigLocation() string {
return args["--config"].(string)
}
func parseConfigFile(configFileLocation string) (*config, error) {
cfg := config{}
func parseConfigFile(configFileLocation string) (*config.Config, error) {
cfg := config.Config{}
// setup defaults
cfg.LDAP.Enabled = false
cfg.LDAPS.Enabled = true
@@ -359,7 +229,7 @@ func parseConfigFile(configFileLocation string) (*config, error) {
return &cfg, nil
}
func handleConfig(cfg config) (*config, error) {
func handleConfig(cfg config.Config) (*config.Config, error) {
if len(cfg.Frontend.Listen) > 0 && (len(cfg.LDAP.Listen) > 0 || len(cfg.LDAPS.Listen) > 0) {
// Both old server-config and new - dont allow
return &cfg, fmt.Errorf("Both old and new server-config in use - please remove old format ([frontend]) and migrate to new format ([ldap], [ldaps])")
+20
View File
@@ -0,0 +1,20 @@
module github.com/glauth/glauth
go 1.13
require (
github.com/BurntSushi/toml v0.3.1
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/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/kr/pretty v0.2.0
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
github.com/pquerna/otp v1.2.0
github.com/yaegashi/msgraph.go v0.1.1-0.20200221123608-2d438cf2a7cc
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c // indirect
gopkg.in/amz.v1 v1.0.0-20150111123259-ad23e96a31d2
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
)
+44
View File
@@ -0,0 +1,44 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
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=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
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/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/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/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=
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3 h1:NNis9uuNpG5h97Dvxxo53Scg02qBg+3Nfabg6zjFGu8=
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/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/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/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=
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/amz.v1 v1.0.0-20150111123259-ad23e96a31d2 h1:FMrsB0OTjHsPDA1NM7AhRmmZzkBPu3iGdxK/5MFfBmk=
gopkg.in/amz.v1 v1.0.0-20150111123259-ad23e96a31d2/go.mod h1:F0YaN4yi2XekmElKkPYfybh7pReQE9Ph48lSgeCkzyc=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+78
View File
@@ -0,0 +1,78 @@
package config
// config file
type Backend struct {
BaseDN string
Datastore string
Insecure bool // For LDAP backend only
Servers []string // For LDAP backend only
NameFormat string
GroupFormat string
SSHKeyAttr string
UseGraphAPI bool // For ownCloud backend only
}
type Frontend struct {
AllowedBaseDNs []string // For LDAP backend only
Listen string
Cert string
Key string
TLS bool
}
type LDAP struct {
Enabled bool
Listen string
}
type LDAPS struct {
Enabled bool
Listen string
Cert string
Key string
}
type API struct {
Cert string
Enabled bool
Key string
Listen string
SecretToken string
TLS bool
}
type User struct {
Name string
OtherGroups []int
PassSHA256 string
PassAppSHA256 []string
PrimaryGroup int
SSHKeys []string
OTPSecret string
Yubikey string
Disabled bool
UnixID int
Mail string
LoginShell string
GivenName string
SN string
Homedir string
}
type Group struct {
Name string
UnixID int
IncludeGroups []int
}
type Config struct {
API API
Backend Backend
Debug bool
WatchConfig bool
YubikeyClientID string
YubikeySecret string
Frontend Frontend
LDAP LDAP
LDAPS LDAPS
Groups []Group
Syslog bool
Users []User
ConfigFile string
AwsAccessKeyId string
AwsSecretAccessKey string
AwsRegion string
}
+24 -22
View File
@@ -1,4 +1,4 @@
package main
package frontend
import (
"bytes"
@@ -6,10 +6,14 @@ import (
"io"
"net/http"
"strings"
"github.com/glauth/glauth/pkg/assets"
"github.com/glauth/glauth/pkg/config"
logging "github.com/op/go-logging"
)
// RunAPI provides a basic REST API
func RunAPI(cfg *config) {
func RunAPI(log *logging.Logger, cfg *config.Config) {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
log.Debug(fmt.Sprintf("Web: %s", r.URL.Path))
if r.URL.Path != "/" {
@@ -17,13 +21,13 @@ func RunAPI(cfg *config) {
http.NotFound(w, r)
return
}
pageTemplate, err := Asset("assets/index.html")
pageTemplate, err := assets.Asset("assets/index.html")
if err != nil {
log.Fatal(fmt.Sprintf("Error with HTTP server template asset: %s", err.Error()))
}
fmt.Fprintf(w, string(pageTemplate))
})
http.HandleFunc("/assets/", webStaticHandler)
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)
@@ -40,23 +44,21 @@ func RunAPI(cfg *config) {
}
// webStaticHandler serves embedded static web files (js&css)
func webStaticHandler(w http.ResponseWriter, r *http.Request) {
assetPath := r.URL.Path[1:]
staticAsset, err := Asset(assetPath)
if err != nil {
log.Error(err.Error())
http.NotFound(w, r)
return
func webStaticHandler(log *logging.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())
http.NotFound(w, r)
return
}
headers := w.Header()
if strings.HasSuffix(assetPath, ".js") {
headers["Content-Type"] = []string{"application/javascript"}
} else if strings.HasSuffix(assetPath, ".css") {
headers["Content-Type"] = []string{"text/css"}
}
io.Copy(w, bytes.NewReader(staticAsset))
}
headers := w.Header()
if strings.HasSuffix(assetPath, ".js") {
headers["Content-Type"] = []string{"application/javascript"}
} else if strings.HasSuffix(assetPath, ".css") {
headers["Content-Type"] = []string{"text/css"}
}
io.Copy(w, bytes.NewReader(staticAsset))
}
type stringer string
func (s stringer) String() string { return string(s) }
+31 -26
View File
@@ -1,4 +1,4 @@
package main
package handler
import (
"crypto/sha256"
@@ -9,17 +9,22 @@ import (
"strings"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/nmcclain/ldap"
"github.com/op/go-logging"
"github.com/pquerna/otp/totp"
)
type configHandler struct {
cfg *config
log *logging.Logger
cfg *config.Config
yubikeyAuth *yubigo.YubiAuth
}
func newConfigHandler(cfg *config, yubikeyAuth *yubigo.YubiAuth) Backend {
func NewConfigHandler(log *logging.Logger, cfg *config.Config, yubikeyAuth *yubigo.YubiAuth) Handler {
handler := configHandler{
log: log,
cfg: cfg,
yubikeyAuth: yubikeyAuth}
return handler
@@ -30,14 +35,14 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
stats_frontend.Add("bind_reqs", 1)
stats.Frontend.Add("bind_reqs", 1)
// parse the bindDN - ensure that the bindDN ends with the BaseDN
if !strings.HasSuffix(bindDN, baseDN) {
log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
// log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, baseDN))
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
// h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, baseDN))
return ldap.LDAPResultInvalidCredentials, nil
}
parts := strings.Split(strings.TrimSuffix(bindDN, baseDN), ",")
@@ -49,12 +54,12 @@ 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 {
log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
return ldap.LDAPResultInvalidCredentials, nil
}
// find the user
user := configUser{}
user := config.User{}
found := false
for _, u := range h.cfg.Users {
if u.Name == userName {
@@ -63,11 +68,11 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
}
}
if !found {
log.Warning(fmt.Sprintf("Bind Error: User %s not found.", userName))
h.log.Warning(fmt.Sprintf("Bind Error: User %s not found.", userName))
return ldap.LDAPResultInvalidCredentials, nil
}
// find the group
group := configGroup{}
group := config.Group{}
found = false
for _, g := range h.cfg.Groups {
if g.Name == groupName {
@@ -76,12 +81,12 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
}
}
if !found {
log.Warning(fmt.Sprintf("Bind Error: Group %s not found.", groupName))
h.log.Warning(fmt.Sprintf("Bind Error: Group %s not found.", groupName))
return ldap.LDAPResultInvalidCredentials, nil
}
// validate group membership
if user.PrimaryGroup != group.UnixID {
log.Warning(fmt.Sprintf("Bind Error: User %s primary group is not %s.", userName, groupName))
h.log.Warning(fmt.Sprintf("Bind Error: User %s primary group is not %s.", userName, groupName))
return ldap.LDAPResultInvalidCredentials, nil
}
@@ -129,10 +134,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)) {
log.Debug(fmt.Sprintf("Attempted to bind app pw #%d - failure as %s from %s", index, bindDN, conn.RemoteAddr().String()))
h.log.Debug(fmt.Sprintf("Attempted to bind app pw #%d - failure as %s from %s", index, bindDN, conn.RemoteAddr().String()))
} else {
stats_frontend.Add("bind_successes", 1)
log.Debug("Bind success using app pw #%d as %s from %s", index, bindDN, conn.RemoteAddr().String())
stats.Frontend.Add("bind_successes", 1)
h.log.Debug("Bind success using app pw #%d as %s from %s", index, bindDN, conn.RemoteAddr().String())
return ldap.LDAPResultSuccess, nil
}
@@ -144,18 +149,18 @@ func (h configHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultC
// Then ensure the OTP is valid before checking
if !validotp {
log.Warning(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
h.log.Warning(fmt.Sprintf("Bind Error: invalid OTP token as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultInvalidCredentials, nil
}
// Now, check the hash
if user.PassSHA256 != hex.EncodeToString(hash.Sum(nil)) {
log.Warning(fmt.Sprintf("Bind Error: invalid credentials as %s from %s", bindDN, conn.RemoteAddr().String()))
h.log.Warning(fmt.Sprintf("Bind Error: invalid credentials as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultInvalidCredentials, nil
}
stats_frontend.Add("bind_successes", 1)
log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
stats.Frontend.Add("bind_successes", 1)
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultSuccess, nil
}
@@ -164,8 +169,8 @@ func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
searchBaseDN := strings.ToLower(searchReq.BaseDN)
log.Debug(fmt.Sprintf("Search request as %s from %s for %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter))
stats_frontend.Add("search_reqs", 1)
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter))
stats.Frontend.Add("search_reqs", 1)
// validate the user is authenticated and has appropriate access
if len(bindDN) < 1 {
@@ -260,14 +265,14 @@ func (h configHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
}
stats_frontend.Add("search_successes", 1)
log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
stats.Frontend.Add("search_successes", 1)
h.log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
//
func (h configHandler) Close(boundDn string, conn net.Conn) error {
stats_frontend.Add("closes", 1)
stats.Frontend.Add("closes", 1)
return nil
}
@@ -331,7 +336,7 @@ func (h configHandler) getGroupMemberIDs(gid int) []string {
if gid == g.UnixID {
for _, includegroupid := range g.IncludeGroups {
if includegroupid == gid {
log.Warning(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
h.log.Warning(fmt.Sprintf("Group: %d - Ignoring myself as included group", includegroupid))
} else {
includegroupmemberids := h.getGroupMemberIDs(includegroupid)
+10
View File
@@ -0,0 +1,10 @@
package handler
import "github.com/nmcclain/ldap"
// interface for backend handler
type Handler interface {
ldap.Binder
ldap.Searcher
ldap.Closer
}
+38 -33
View File
@@ -1,4 +1,4 @@
package main
package handler
import (
"crypto/sha256"
@@ -12,13 +12,17 @@ import (
"sync"
"time"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/kr/pretty"
"github.com/nmcclain/ldap"
"github.com/op/go-logging"
)
type ldapHandler struct {
doPing chan bool
cfg *config
log *logging.Logger
cfg *config.Config
lock *sync.Mutex // for sessions and servers
sessions map[string]ldapSession
servers []ldapBackend
@@ -47,10 +51,11 @@ type ldapBackend struct {
Ping time.Duration
}
func newLdapHandler(cfg *config) Backend {
func NewLdapHandler(log *logging.Logger, cfg *config.Config) Handler {
handler := ldapHandler{ // set non-zero-value defaults here
sessions: make(map[string]ldapSession),
doPing: make(chan bool),
log: log,
cfg: cfg,
lock: &ldaplock,
}
@@ -71,31 +76,31 @@ func newLdapHandler(cfg *config) Backend {
//
func (h ldapHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (resultCode ldap.LDAPResultCode, err error) {
log.Debug(fmt.Sprintf("Bind request as %s from %s", bindDN, conn.RemoteAddr().String()))
stats_frontend.Add("bind_reqs", 1)
h.log.Debug(fmt.Sprintf("Bind request as %s from %s", bindDN, conn.RemoteAddr().String()))
stats.Frontend.Add("bind_reqs", 1)
s, err := h.getSession(conn)
if err != nil {
stats_frontend.Add("bind_ldapSession_errors", 1)
log.Debug(fmt.Sprintf("Bind ops error as %s from %s == %s", bindDN, conn.RemoteAddr().String(), err.Error()))
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()))
return ldap.LDAPResultOperationsError, err
}
if err := s.ldap.Bind(bindDN, bindSimplePw); err != nil {
stats_frontend.Add("bind_errors", 1)
log.Debug(fmt.Sprintf("Bind invalid creds as %s from %s", bindDN, conn.RemoteAddr().String()))
stats.Frontend.Add("bind_errors", 1)
h.log.Debug(fmt.Sprintf("Bind invalid creds as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultInvalidCredentials, nil
}
stats_frontend.Add("bind_successes", 1)
log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
stats.Frontend.Add("bind_successes", 1)
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultSuccess, nil
}
//
func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn net.Conn) (result ldap.ServerSearchResult, err error) {
log.Debug(fmt.Sprintf("Search request as %s from %s for %s", boundDN, conn.RemoteAddr().String(), searchReq.Filter))
stats_frontend.Add("search_reqs", 1)
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s", boundDN, conn.RemoteAddr().String(), searchReq.Filter))
stats.Frontend.Add("search_reqs", 1)
s, err := h.getSession(conn)
if err != nil {
stats_frontend.Add("search_ldapSession_errors", 1)
stats.Frontend.Add("search_ldapSession_errors", 1)
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, nil
}
search := ldap.NewSearchRequest(
@@ -110,24 +115,24 @@ func (h ldapHandler) Search(boundDN string, searchReq ldap.SearchRequest, conn n
searchReq.Controls,
)
log.Debug(fmt.Sprintf("Search req to backend: %# v", pretty.Formatter(search)))
h.log.Debug(fmt.Sprintf("Search req to backend: %# v", pretty.Formatter(search)))
sr, err := s.ldap.Search(search)
log.Debug(fmt.Sprintf("Backend Search result: %# v", pretty.Formatter(sr)))
h.log.Debug(fmt.Sprintf("Backend Search result: %# v", pretty.Formatter(sr)))
ssr := ldap.ServerSearchResult{
Entries: sr.Entries,
Referrals: sr.Referrals,
Controls: sr.Controls,
}
log.Debug(fmt.Sprintf("Frontend Search result: %# v", pretty.Formatter(ssr)))
h.log.Debug(fmt.Sprintf("Frontend Search result: %# v", pretty.Formatter(ssr)))
if err != nil {
e := err.(*ldap.Error)
log.Debug(fmt.Sprintf("Search Err: %# v", pretty.Formatter(err)))
stats_frontend.Add("search_errors", 1)
h.log.Debug(fmt.Sprintf("Search Err: %# v", pretty.Formatter(err)))
stats.Frontend.Add("search_errors", 1)
ssr.ResultCode = ldap.LDAPResultCode(e.ResultCode)
return ssr, err
}
stats_frontend.Add("search_successes", 1)
log.Debug(fmt.Sprintf("AP: Search OK: %s -> num of entries = %d\n", search.Filter, len(ssr.Entries)))
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)))
return ssr, nil
}
func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
@@ -135,8 +140,8 @@ func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
h.lock.Lock()
defer h.lock.Unlock()
delete(h.sessions, connID(conn))
stats_frontend.Add("closes", 1)
stats_backend.Add("closes", 1)
stats.Frontend.Add("closes", 1)
stats.Backend.Add("closes", 1)
return nil
}
@@ -144,22 +149,22 @@ func (h ldapHandler) Close(boundDn string, conn net.Conn) error {
func (h *ldapHandler) monitorServers() {
err := h.ping()
if err != nil {
log.Fatal(err)
h.log.Fatal(err)
}
go func() {
for {
select {
case <-h.doPing:
log.Notice("doPing requested due to server failure")
h.log.Notice("doPing requested due to server failure")
err = h.ping()
if err != nil {
log.Fatal(err)
h.log.Fatal(err)
}
case <-time.NewTimer(60 * time.Second).C:
log.Debug("doPing after timeout")
h.log.Debug("doPing after timeout")
err = h.ping()
if err != nil {
log.Fatal(err)
h.log.Fatal(err)
}
}
}
@@ -223,7 +228,7 @@ func (h ldapHandler) ping() error {
elapsed := time.Since(start)
h.lock.Lock()
if err != nil || l == nil {
log.Error(fmt.Sprintf("Server %s:%d ping failed: %s", s.Hostname, s.Port, err.Error()))
h.log.Error(fmt.Sprintf("Server %s:%d ping failed: %s", s.Hostname, s.Port, err.Error()))
h.servers[k].Ping = 0
h.servers[k].Status = Down
} else {
@@ -234,12 +239,12 @@ func (h ldapHandler) ping() error {
}
h.lock.Unlock()
}
log.Debug(fmt.Sprintf("Server health: %# v", pretty.Formatter(h.servers)))
h.log.Debug(fmt.Sprintf("Server health: %# v", pretty.Formatter(h.servers)))
b, err := json.Marshal(h.servers)
if err != nil {
log.Error(fmt.Sprintf("Error encoding tail data: %s", err.Error()))
h.log.Error(fmt.Sprintf("Error encoding tail data: %s", err.Error()))
}
stats_backend.Set("servers", stringer(string(b)))
stats.Backend.Set("servers", stats.Stringer(string(b)))
if healthy == false {
return fmt.Errorf("No healthy servers")
}
@@ -263,7 +268,7 @@ func (h ldapHandler) getBestServer() (ldapBackend, error) {
if bestping == forever {
return ldapBackend{}, fmt.Errorf("No healthy servers found")
}
log.Debug(fmt.Sprintf("Best server: %# v", pretty.Formatter(favorite)))
h.log.Debug(fmt.Sprintf("Best server: %# v", pretty.Formatter(favorite)))
return favorite, nil
}
+73 -63
View File
@@ -1,89 +1,96 @@
package main
package handler
import (
"context"
"encoding/json"
"errors"
"fmt"
msgraph "github.com/DeepDiver1975/msgraph.go/v1.0"
"github.com/nmcclain/ldap"
"io/ioutil"
"net"
"net/http"
"strings"
"sync"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/stats"
"github.com/nmcclain/ldap"
"github.com/op/go-logging"
msgraph "github.com/yaegashi/msgraph.go/v1.0"
)
type ownCloudSession struct {
log *logging.Logger
user string
password string
baseUrl string
useGraphAPI bool
}
type ownCloudHandler struct {
cfg *config
log *logging.Logger
cfg *config.Config
meUrl string
sessions map[string]ownCloudSession
lock sync.Mutex
}
func (o ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
func (h ownCloudHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + o.cfg.Backend.BaseDN)
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, o.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
h.log.Debug(fmt.Sprintf("Bind request: bindDN: %s, BaseDN: %s, source: %s", bindDN, h.cfg.Backend.BaseDN, conn.RemoteAddr().String()))
stats_frontend.Add("bind_reqs", 1)
stats.Frontend.Add("bind_reqs", 1)
// parse the bindDN - ensure that the bindDN ends with the BaseDN
if !strings.HasSuffix(bindDN, baseDN) {
log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, o.cfg.Backend.BaseDN))
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s not our BaseDN %s", bindDN, h.cfg.Backend.BaseDN))
return ldap.LDAPResultInvalidCredentials, nil
}
parts := strings.Split(strings.TrimSuffix(bindDN, baseDN), ",")
if len(parts) > 2 {
log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
h.log.Warning(fmt.Sprintf("Bind Error: BindDN %s should have only one or two parts (has %d)", bindDN, len(parts)))
return ldap.LDAPResultInvalidCredentials, nil
}
userName := strings.TrimPrefix(parts[0], "cn=")
// try to login
if !o.login(userName, bindSimplePw) {
log.Warning(fmt.Sprintf("Bind Error: User %s login failed", userName))
if !h.login(userName, bindSimplePw) {
h.log.Warning(fmt.Sprintf("Bind Error: User %s login failed", userName))
return ldap.LDAPResultInvalidCredentials, nil
}
id := connID(conn)
o.lock.Lock()
o.sessions[id] = ownCloudSession{
h.lock.Lock()
h.sessions[id] = ownCloudSession{
log: h.log,
user: userName,
password: bindSimplePw,
baseUrl: o.cfg.Backend.Servers[0],
useGraphAPI: o.cfg.Backend.UseGraphAPI,
baseUrl: h.cfg.Backend.Servers[0],
useGraphAPI: h.cfg.Backend.UseGraphAPI,
}
o.lock.Unlock()
h.lock.Unlock()
stats_frontend.Add("bind_successes", 1)
log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
stats.Frontend.Add("bind_successes", 1)
h.log.Debug(fmt.Sprintf("Bind success as %s from %s", bindDN, conn.RemoteAddr().String()))
return ldap.LDAPResultSuccess, nil
}
func (o ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
func (h ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
bindDN = strings.ToLower(bindDN)
baseDN := strings.ToLower("," + o.cfg.Backend.BaseDN)
baseDN := strings.ToLower("," + h.cfg.Backend.BaseDN)
searchBaseDN := strings.ToLower(searchReq.BaseDN)
log.Debug(fmt.Sprintf("Search request as %s from %s for %s on %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter, searchBaseDN))
stats_frontend.Add("search_reqs", 1)
h.log.Debug(fmt.Sprintf("Search request as %s from %s for %s on %s", bindDN, conn.RemoteAddr().String(), searchReq.Filter, searchBaseDN))
stats.Frontend.Add("search_reqs", 1)
// validate the user is authenticated and has appropriate access
if len(bindDN) < 1 {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: Anonymous BindDN not allowed %s", bindDN)
}
if !strings.HasSuffix(bindDN, baseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: BindDN %s not in our BaseDN %s", bindDN, o.cfg.Backend.BaseDN)
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: BindDN %s not in our BaseDN %s", bindDN, h.cfg.Backend.BaseDN)
}
if !strings.HasSuffix(searchBaseDN, o.cfg.Backend.BaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: search BaseDN %s is not in our BaseDN %s", searchBaseDN, o.cfg.Backend.BaseDN)
if !strings.HasSuffix(searchBaseDN, h.cfg.Backend.BaseDN) {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, fmt.Errorf("search error: search BaseDN %s is not in our BaseDN %s", searchBaseDN, h.cfg.Backend.BaseDN)
}
// return all users in the config file - the LDAP library will filter results for us
entries := []*ldap.Entry{}
@@ -91,10 +98,10 @@ func (o ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("search error: error parsing filter: %s", searchReq.Filter)
}
o.lock.Lock()
h.lock.Lock()
id := connID(conn)
session := o.sessions[id]
o.lock.Unlock()
session := h.sessions[id]
h.lock.Unlock()
switch filterEntity {
default:
@@ -118,12 +125,12 @@ func (o ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
attrs = append(attrs, &ldap.EntryAttribute{Name: "memberUid", Values: members})
}
dn := fmt.Sprintf("cn=%s,%s=groups,%s", *g.ID, o.cfg.Backend.GroupFormat, o.cfg.Backend.BaseDN)
dn := fmt.Sprintf("cn=%s,%s=groups,%s", *g.ID, h.cfg.Backend.GroupFormat, h.cfg.Backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
case "posixaccount", "":
userName := ""
if searchBaseDN != strings.ToLower(o.cfg.Backend.BaseDN) {
if searchBaseDN != strings.ToLower(h.cfg.Backend.BaseDN) {
parts := strings.Split(strings.TrimSuffix(searchBaseDN, baseDN), ",")
if len(parts) == 1 {
userName = strings.TrimPrefix(parts[0], "cn=")
@@ -131,7 +138,7 @@ func (o ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
}
users, err := session.getUsers(userName)
if err != nil {
log.Debug(err)
h.log.Debug(err)
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, errors.New("search error: error getting users")
}
for _, u := range users {
@@ -148,27 +155,27 @@ func (o ownCloudHandler) Search(bindDN string, searchReq ldap.SearchRequest, con
attrs = append(attrs, &ldap.EntryAttribute{Name: "objectClass", Values: []string{"posixAccount"}})
attrs = append(attrs, &ldap.EntryAttribute{Name: "description", Values: []string{fmt.Sprintf("%s from ownCloud", *u.ID)}})
dn := fmt.Sprintf("%s=%s,%s=%s,%s", o.cfg.Backend.NameFormat, *u.ID, o.cfg.Backend.GroupFormat, "users", o.cfg.Backend.BaseDN)
dn := fmt.Sprintf("%s=%s,%s=%s,%s", h.cfg.Backend.NameFormat, *u.ID, h.cfg.Backend.GroupFormat, "users", h.cfg.Backend.BaseDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
}
stats_frontend.Add("search_successes", 1)
log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
stats.Frontend.Add("search_successes", 1)
h.log.Debug(fmt.Sprintf("AP: Search OK: %s", searchReq.Filter))
return ldap.ServerSearchResult{Entries: entries, Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess}, nil
}
func (o ownCloudHandler) Close(boundDN string, conn net.Conn) error {
func (h ownCloudHandler) Close(boundDN string, conn net.Conn) error {
conn.Close() // close connection to the server when then client is closed
o.lock.Lock()
defer o.lock.Unlock()
delete(o.sessions, connID(conn))
stats_frontend.Add("closes", 1)
stats_backend.Add("closes", 1)
h.lock.Lock()
defer h.lock.Unlock()
delete(h.sessions, connID(conn))
stats.Frontend.Add("closes", 1)
stats.Backend.Add("closes", 1)
return nil
}
func (o ownCloudHandler) login(name, pw string) bool {
req, _ := http.NewRequest("GET", o.meUrl, nil)
func (h ownCloudHandler) login(name, pw string) bool {
req, _ := http.NewRequest("GET", h.meUrl, nil)
req.SetBasicAuth(name, pw)
resp, err := http.DefaultClient.Do(req)
if err != nil {
@@ -191,22 +198,22 @@ type OCSGroupsResponse struct {
} `json:"ocs"`
}
func (o ownCloudSession) RoundTrip(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(o.user, o.password)
func (s ownCloudSession) RoundTrip(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(s.user, s.password)
return http.DefaultTransport.RoundTrip(req)
}
func (o ownCloudSession) getGroups() ([]msgraph.Group, error) {
if o.useGraphAPI {
func (s ownCloudSession) getGroups() ([]msgraph.Group, error) {
if s.useGraphAPI {
ctx := context.Background()
req := o.NewClient().Groups().Request()
req := s.NewClient().Groups().Request()
req.Expand("members")
return req.Get(ctx)
}
groupsUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/groups?format=json", o.baseUrl)
groupsUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/groups?format=json", s.baseUrl)
req, _ := http.NewRequest("GET", groupsUrl, nil)
req.SetBasicAuth(o.user, o.password)
req.SetBasicAuth(s.user, s.password)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
@@ -243,21 +250,21 @@ type OCSUsersResponse struct {
}
// NewClient returns GraphService request builder with default base URL
func (o ownCloudSession) NewClient() *msgraph.GraphServiceRequestBuilder {
graphAPIBaseUrl := fmt.Sprintf("%s/index.php/apps/graphapi/v1.0/", o.baseUrl)
func (s ownCloudSession) NewClient() *msgraph.GraphServiceRequestBuilder {
graphAPIBaseUrl := fmt.Sprintf("%s/index.php/apps/graphapi/v1.0/", s.baseUrl)
httpClient := &http.Client{
Transport: o,
Transport: s,
}
g := msgraph.NewClient(httpClient)
g.SetURL(graphAPIBaseUrl)
return g
}
func (o ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
if o.useGraphAPI {
func (s ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
if s.useGraphAPI {
ctx := context.Background()
req := o.NewClient().Users()
req := s.NewClient().Users()
if len(userName) > 0 {
u, err := req.ID(userName).Request().Get(ctx)
if err != nil {
@@ -267,10 +274,10 @@ func (o ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
}
return req.Request().Get(ctx)
}
usersUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/users?format=json", o.baseUrl)
usersUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/users?format=json", s.baseUrl)
req, _ := http.NewRequest("GET", usersUrl, nil)
req.SetBasicAuth(o.user, o.password)
req.SetBasicAuth(s.user, s.password)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
@@ -293,16 +300,19 @@ func (o ownCloudSession) getUsers(userName string) ([]msgraph.User, error) {
return ret, nil
}
func (o ownCloudSession) redirectPolicyFunc(req *http.Request, via []*http.Request) error {
log.Debug("Setting user and password")
req.SetBasicAuth(o.user, o.password)
func (s ownCloudSession) redirectPolicyFunc(req *http.Request, via []*http.Request) error {
s.log.Debug("Setting user and password")
req.SetBasicAuth(s.user, s.password)
return nil
}
func newOwnCloudHandler(cfg *config) Backend {
func NewOwnCloudHandler(log *logging.Logger, cfg *config.Config) Handler {
meUrl := fmt.Sprintf("%s/ocs/v2.php/cloud/user?format=json", cfg.Backend.Servers[0])
handler := ownCloudHandler{cfg: cfg, meUrl: meUrl,
handler := ownCloudHandler{
log: log,
cfg: cfg,
meUrl: meUrl,
sessions: make(map[string]ownCloudSession),
}
return handler
+91
View File
@@ -0,0 +1,91 @@
package server
import (
"errors"
"fmt"
"github.com/GeertJohan/yubigo"
"github.com/glauth/glauth/pkg/config"
"github.com/glauth/glauth/pkg/handler"
"github.com/nmcclain/ldap"
"github.com/op/go-logging"
)
type LdapSvc struct {
log *logging.Logger
c *config.Config
yubiAuth *yubigo.YubiAuth
l *ldap.Server
}
func NewServer(log *logging.Logger, cfg *config.Config) (*LdapSvc, error) {
s := LdapSvc{
log: log,
c: cfg,
}
var err error
if len(cfg.YubikeyClientID) > 0 && len(cfg.YubikeySecret) > 0 {
s.yubiAuth, err = yubigo.NewYubiAuth(cfg.YubikeyClientID, cfg.YubikeySecret)
if err != nil {
return nil, errors.New("Yubikey Auth failed")
}
}
// configure the backend
s.l = ldap.NewServer()
s.l.EnforceLDAP = true
var h handler.Handler
switch cfg.Backend.Datastore {
case "ldap":
h = handler.NewLdapHandler(log, cfg)
case "owncloud":
h = handler.NewOwnCloudHandler(log, cfg)
case "config":
h = handler.NewConfigHandler(log, cfg, s.yubiAuth)
default:
return nil, fmt.Errorf("unsupported backend %s - must be 'config' or 'ldap'", cfg.Backend.Datastore)
}
log.Notice(fmt.Sprintf("Using %s backend", cfg.Backend.Datastore))
s.l.BindFunc("", h)
s.l.SearchFunc("", h)
s.l.CloseFunc("", h)
return &s, nil
}
func (s *LdapSvc) ListenAndServe() {
if s.c.LDAP.Enabled {
// Dont block if also starting a LDAPS server afterwards
shouldBlock := !s.c.LDAPS.Enabled
if shouldBlock {
s.startLDAP()
} else {
go s.startLDAP()
}
}
if s.c.LDAPS.Enabled {
// Always block here
s.startLDAPS()
}
}
func (s *LdapSvc) startLDAP() {
s.log.Notice(fmt.Sprintf("LDAP server listening on %s", s.c.LDAP.Listen))
if err := s.l.ListenAndServe(s.c.LDAP.Listen); err != nil {
s.log.Fatalf("LDAP Server Failed: %s", err.Error())
}
}
func (s *LdapSvc) startLDAPS() {
s.log.Notice(fmt.Sprintf("LDAPS server listening on %s", 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())
}
}
+10
View File
@@ -0,0 +1,10 @@
package stats
import "expvar"
// exposed expvar variables
var (
Frontend = expvar.NewMap("proxy_frontend")
Backend = expvar.NewMap("proxy_backend")
General = expvar.NewMap("proxy")
)
+5
View File
@@ -0,0 +1,5 @@
package stats
type Stringer string
func (s Stringer) String() string { return string(s) }
+22
View File
@@ -0,0 +1,22 @@
#################
# General configuration.
debug = true
#################
# Server configuration.
# This ([ldap] and [ldaps]) is the new server-config format
[ldap]
enabled = true
listen = "localhost:3893"
[ldaps]
enabled = false
#################
# The backend section controls the data store.
[backend]
datastore = "owncloud"
baseDN = "dc=owncloud,dc=com"
servers = ["https://cloud.example.com"]
UseGraphAPI = true