mirror of
https://github.com/glauth/glauth.git
synced 2026-10-25 05:30:47 +00:00
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:
committed by
GitHub
parent
9b90466286
commit
558c7de15a
+1
-1
@@ -10,7 +10,7 @@ certs/
|
||||
# Built binaries
|
||||
bin
|
||||
|
||||
bindata.go
|
||||
pkg/assets/bindata.go
|
||||
|
||||
.*.swp
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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])")
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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=
|
||||
@@ -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
|
||||
}
|
||||
@@ -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) }
|
||||
@@ -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)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package handler
|
||||
|
||||
import "github.com/nmcclain/ldap"
|
||||
|
||||
// interface for backend handler
|
||||
type Handler interface {
|
||||
ldap.Binder
|
||||
ldap.Searcher
|
||||
ldap.Closer
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
)
|
||||
@@ -0,0 +1,5 @@
|
||||
package stats
|
||||
|
||||
type Stringer string
|
||||
|
||||
func (s Stringer) String() string { return string(s) }
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user